我们知道在使用MyBatis开发时,只需要添加DAO接口和对应的映射XML文件,不需要写DAO的实现类,其实底层是通过动态代理实现。
本文将使用前几篇文章的知识点实现一个纯面向接口编程的简单框架,与MyBatis实现DAO实现类相似,主要采用注解、反射、动态代理、工厂模式等。具体功能:
- 接口添加自定义类注解,动态生成接口的实现类
- 通过可配置的方式实现接口行为,如在网络传输中使用TCP或UDP协议,在数据库中配置不同的数据库类型等
- 方法上添加自定义方法和参数注解,控制接口具体实现
- 底层通过代理模拟网络的创建与关闭(可以修改为数据库操作等其他功能)
1.框架实现
(1)注解定义,包含类注解、方法注解、参数类型注解
/**
* 运行的类
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ToClass{
public Class<?> clazz();
}
/**
* 执行的方法
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ToMethod{
public String method();
}
/**
* 执行方法的参数类型
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ParamType{
public Class<?>[] param();
}
(2)动态生成接口实现类
动态生成接口实现类,并返回对象实例,开发者可以直接调方法使用。
class MyLocator implements InvocationHandler{
//接口类对象
private Class<?> clazz;
private static MyLocator instance = new MyLocator();
public static MyLocator getInstance(){
return instance;
}
public <T> T lookup(Class<T> clazz){
this.clazz = clazz;
//返回动态代理类
return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
}
//根据接口的注解,动态调用不同的实现类,实现不同的逻辑
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable{
//默认实现类
ToClass classAnno = clazz.getAnnotation(ToClass.class);
Class defaultClass = classAnno == null ? null :classAnno.clazz();
//获取具体方法和方法参数类型
String mthd = null;
List<Class<?>> paramType = new ArrayList<Class<?>>();
Annotation[] annos = method.getDeclaredAnnotations();
for(Annotation anno : annos){
//类注解
if(anno instanceof ToClass){
defaultClass = ((ToClass) anno).clazz();
}
//方法注解
if(anno instanceof ToMethod){
mthd = ((ToMethod) anno).method();
}
//参数类型注解
if(anno instanceof ParamType){
Class[] p = ((ParamType) anno).param();
for(Class c : p){
paramType.add(c);
}
}
}
// Object realObj = defaultClass.getDeclaredConstructor().newInstance();
if(defaultClass != null && mthd != null){
//通过实例化工厂获取defaultClass对象,该对象是被底层代理后的对象
Object obj = InstanceFactory.getInstance(defaultClass);
//获取实际调用的方法。注意:对包含参数的方法,必须声明参数类型,并且需要与方法声明顺序保持一致
Method m = obj.getClass().getDeclaredMethod(mthd, paramType.toArray(new Class[0]));
//执行方法调用。注意:第一个参数obj必须是上一步获取方法m的声明类,声明类与代理类是不相同的,
//如果使用注掉的realObj获取m,会报该方法不是类声明的方法。
return m.invoke(obj, args);
}
return null;
}
}
(3)类实例化工厂
/**
* 工厂
*/
class InstanceFactory{
private InstanceFactory(){}
public static <T> T getInstance(Class<T> clazz){
try{
//代理+反射实例化
return (T)new MyProxy().proxy(clazz.getDeclaredConstructor().newInstance(), clazz);
}catch (Exception e){
return null;
}
}
}
(4)底层动态代理类,执行方法具体逻辑
/**
* 底层动态代理类,执行方法具体逻辑
*/
class MyProxy implements InvocationHandler{
//被代理对象
private Object target;
public <T> T proxy(Object target, Class<T> clazz){
this.target = target;
return (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
try {
if(this.connect()){
//实际方法执行
return method.invoke(target, args);
}
}finally {
this.close();
}
return null;
}
private boolean connect(){
System.out.println("创建连接");
return true;
}
private void close(){
System.out.println("关闭连接");
}
}
2.用例测试
(1)模拟网络传输TCP和UDP方式
interface Msg{
void send(String msg, int seq);
String recv(String msg);
}
class TCPMsgImpl implements Msg{
public void send(String msg,int seq) {
System.out.println("TCP消息发送:"+msg);
System.out.println("TCP消息序号:"+seq);
}
public String recv(String msg) {
System.out.println("TCP消息接收:"+msg);
return msg;
}
}
class UDPMsgImpl implements Msg{
public void send(String msg, int seq) {
System.out.println("UDP消息发送:"+msg);
System.out.println("UDP消息序号:"+seq);
}
public String recv(String msg) {
System.out.println("UDP消息接收:"+msg);
return msg;
}
}
(2)开发者基于TCP和UDP方式定义消息传输服务接口
@ToClass(clazz = TCPMsgImpl.class)
interface IMsgService{
@ToMethod(method = "send")
@ParamType(param = {String.class, int.class})
public void send(String text, int seq);
@ToClass(clazz =UDPMsgImpl.class)
@ToMethod(method = "recv")
@ParamType(param = {String.class})
public String recv(String text);
}
(3)服务接口调用
public static void main(String[] args) throws Exception{
System.out.println("---------发生信息--------");
MyLocator.getInstance().lookup(IMsgService.class).send("hello", 1);
System.out.println("---------接收信息--------");
String r = MyLocator.getInstance().lookup(IMsgService.class).recv("hello world");
System.out.println("---------客户端--------");
System.out.println("客户端接收到的信息:" + r);
}
输出:
---------发生信息--------
创建连接
TCP消息发送:hello
TCP消息序号:1
关闭连接
---------接收信息--------
创建连接
UDP消息接收:hello world
关闭连接
---------客户端--------
客户端接收到的信息:hello world
到此,这是所有关于Java反射的汇总记录,后续如有重要点再补充。
这是水木竹水的博客