文章迁移自语雀。
其实jdk的代理模式已经说了很多次了,现在是北京时间2020年2月16日23:15:06, 躺在床上打字,手冰冷的,写完这个总结就睡觉的.
现在手感觉没啥知觉了, 好冷, 现在是2020年2月16日23:50:51., 写了45分钟. 关电脑准备睡觉,明天看jdk的动态代理实现,学习大师的作品,对比自己的实现差异.
综述
jdk中的代理,就是三种
1.继承
2.聚合
1,2属于静态代理
3.动态代理
下面我们依次说这三种方式.
1.继承
就是使用一个新的类,去继承原来的类,对原来的类对方法的增强.
使用的实例代码如下:
public interface UserService {
public void login();
}
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("好想念程程,不知道她睡觉了没有");
}
}
public class TimeExtends extends UserServiceImpl {
@Override
public void login() {
System.out.println("继承的代理");
super.login();
}
}
缺点很明显, 需要继承, 而且随着功能的增加, 要不断的继承, 体系混乱,越来越复杂. 最后谁就不懂了.
2.聚合
因为是持有原来的对象,所以又交聚合,这个其实就是一种设计模式了,用的很巧妙.
public class LogExtends implements UserService {
private UserService userService;
public LogExtends(UserService userService) {
this.userService = userService;
}
@Override
public void login() {
System.out.println("这是静态代理的方法");
userService.login();
}
}
相对于第一种有了很明显的进步, 耦合性大大的降低,但是如果需要增强的功能太多,也是很复杂的,而且要增强的功能往往不明确,不知道去实现谁的接口.
3.动态代理
这次我们手写一个自己的动态代理.
我们模仿第二种方式,动态的生成一个.java文件, 再编译成.calss文件,最后加载到jvm中,生成对象,调用新的对象的方法,就得到的增强的目的,而且对用户是透明的.
public class ProxyUtil {
private final static String Line="\n";
private final static String Tab="\t";
/**
* 生成LogExtends 一样的接口.
* @param target
* @return
*/
public static Object getProxyObject(Object target){
StringBuilder sb = new StringBuilder("package com.onyx.proxy;").append(Line);
sb.append(Line);
sb.append("import").append(" ");
Class<?> targetClass = target.getClass();
Class<?> anInterface = targetClass.getInterfaces()[0];
String interfaceSimpleName = anInterface.getSimpleName();
sb.append(anInterface.getName()).append(";").append(Line).append(Line);
sb.append("public class $ProxyClass implements ");
sb.append(interfaceSimpleName).append("{").append(Line);
sb.append(Line);
sb.append(Tab).append("private").append(" ").append(interfaceSimpleName).append(Tab).append("proxyObject").append(";").append(Line).append(Line);
//构造方法
sb.append(Tab).append("public").append(Tab).append("$ProxyClass(").append(interfaceSimpleName).append(Tab).append("param")
.append("){").append(Line).append(Tab).append(Tab).append("this.proxyObject=param;").append(Line)
.append(Tab).append("}").append(Line).append(Line);
String methodName = anInterface.getMethods()[0].getName();
sb.append(Tab).append("public").append(Tab).append("void").append(Tab).append(methodName)
.append("(){").append(Line);
sb.append(Tab).append(Tab).append("System.out.println(\"这是增强的log方法\");").append(Line);
sb.append(Tab);
sb.append(Tab).append("proxyObject.").append(methodName).append("();").append(Line);
sb.append(Tab).append("}").append(Line).append("}");
System.out.println(sb.toString());
return null;
}
}
这个生成的结构就是和LogExtends.java是一样的,只是我们自己拼接出来的,生成的结果是:
这是生成的.java结构, 我打印出来了,
至于怎么加载到jvm中,各位自己想办法. 有了string字符串, 保存成.java文件,在找到文件加载就可以了.
这个不是本文的重点,就不说了,手好冰啊.
其实这是我们自己实现的一个简单版本的动态代理,这个有待于优化
1.调用方法的参数(这里直接写了空参数) 进行类型的获取, 填充,
2.方法的返回值(直接写了void), 获取返回类型, 判断是都void ,返回
3.类的多接口实现(我这里就直接取了第一个接口).
最后是我们的测试类:
public class ChengCheng {
public static void main(String[] args) {
//new TimeExtends().login();
//new LogExtends(new UserServiceImpl()).login();
UserService object = (UserService)ProxyUtil.getProxyObject(new UserServiceImpl());
}
}
上面的代码其实还有个问题,代理的逻辑,我们没有抽离出来, 这个要封装很多层才好传递进去,最好是用接口的形式传递参数.
下面我们来看jdk中是怎么代理的.
//jdk的代理
UserService service = (UserService) Proxy.newProxyInstance(
ChengCheng.class.getClassLoader(), new Class[]{UserService.class},
new MyInvokeHandle(new UserServiceImpl()));
service.login();
其中719行代码, 就是获取到生成的代理类的class类对象, 所以这一行代码是关键, 下面的代码就是利用这个class对象进行创建一个对象,然后返回回去.
第408行的注释, 说如果没有就使用ProxyClassFactory创建一个,否则直接从缓存里面拿, 我们不看缓存的拿去, 就是一个Map, 没事好看的. 看看这个ProxyClassFactory类.他是静态的内部类.
第639行, 是生成了代理类文件的二进制流, 所以我们看看这里.
第110行生成了这个类的class文件. 这个就非常的关键了.其实就是不断的拼接生成了.class文件, 牛逼哄哄的不得了. 哈哈哈
最后生成了返回, 第642行把一个二进制的class文件变成了 一个class对象返回去了,
看到这里, 就看不下去了,还想看就去下载openjdk的源码, 去研究下,怎么把byte[] 变成class对象.
1 人点赞