首先说明一下就是, jdk动态代理,为什么是动态,是因为其在运行时帮你生成了代理类,这里其实本质上还是生成一个静态代理类, 然后通过InvocationHandler的invoke(反射的方式)来调用真正的对象, 所以我的理解动态代理就是运行时生成+反射调用.(其实不局限于运行时生成,也可以是类加载生成或者类编译时生成).
这次我会用两种方式来实现动态代理.
第一种实现方式的思路就是, 按照我们平常的编写代码逻辑,也就是先编写java文件, 然后编译成class文件,然后加载到jvm中去,所以其核心逻辑就是拼接java文件的内容
各位应该记得jdk的动态代理的api吧, 如下
IService instance = (IService) Proxy.newProxyInstance(new MyClassLoader(Thread.currentThread().getContextClassLoader()), //这里我随便定义了classloader, 因为可以自定义生成的类路径, 所以可以通过自定义类加载器去加载class,并在加载后可以删除文件
new Class[]{IService.class}, //需要实现的接口
(proxy, method, args1) -> {
System.out.println("before");
return method.invoke(service, args1); //真正的执行
});
那么先看一下我们要生成的java模板吧
//首先模板要有声明包
package com.cdy.demo.repeatedWheels.myproxy;
// import 都给他省略了,因为下面都是使用全限定名
//动态代理类的名称这里简化为$Proxy+递增的数字, 然后其继承传进来的接口
public class $Proxy1 implements com.cdy.demo.repeatedWheels.myproxy.IService{
// 这里是真正的委托执行器
com.cdy.demo.repeatedWheels.myproxy.InvocationHandler h;
public $Proxy1(com.cdy.demo.repeatedWheels.myproxy.InvocationHandler h) {
this.h = h;
}
public java.lang.String doService(java.lang.String arg0){
try{
// 其实为了提供性能, 可以写一个static,提前把所有的method都准备好,这样不用再执行时再去查找,提高新=性能
java.lang.reflect.Method m = com.cdy.demo.repeatedWheels.myproxy.IService.class.getMethod("doService",new java.lang.Class[]{java.lang.String.class});
// 调用委托类
return (java.lang.String)this.h.invoke(this,m,new java.lang.Object[]{arg0});
}catch(java.lang.Throwable e){throw new java.lang.RuntimeException(e);}
}
}
具体的模板拼接代码如下, 也没啥好看的, 就是动态生成所有的对应方法,生成的结果就是上面的代码模板
String ln = "\r\n"; //换行 定义成static就可以
AtomicInteger integer = new AtomicInteger(0); //递增数 也定义从全局的就可以了
StringBuilder src = new StringBuilder();
String name = MyClassLoader.class.getPackage().getName();
src.append("package ").append(name).append(";").append(ln);
src.append("public class ").append(className).append(" implements ").append(interfaces.getName()).append("{").append(ln);
src.append(InvocationHandler.class.getName()).append(" h;").append(ln);
src.append("public ").append(className).append("(").append(InvocationHandler.class.getName()).append(" h) {").append(ln);
src.append("this.h = h;").append(ln);
src.append("}").append(ln);
for (Method m : interfaces.getMethods()) {
StringBuilder params = new StringBuilder();
StringBuilder params2 = new StringBuilder();
StringBuilder classes = new StringBuilder();
for (Parameter parameter : m.getParameters()) {
params.append(parameter.getType().getName()).append(" ").append(parameter.getName()).append(",");
classes.append(parameter.getType().getName()).append(".class").append(",");
params2.append(parameter.getName()).append(",");
}
params.deleteCharAt(params.lastIndexOf(","));
params2.deleteCharAt(params2.lastIndexOf(","));
classes.deleteCharAt(classes.lastIndexOf(","));
String returnType = m.getReturnType().getName();
src.append("public ").append(returnType).append(" ")
.append(m.getName()).append("(").append(params.toString()).append("){").append(ln);
src.append("try{").append(ln);
src.append("java.lang.reflect.Method m = ").append(interfaces.getName()).append(".class.getMethod(\"")
.append(m.getName()).append("\",new java.lang.Class[]{").append(classes).append("});").append(ln);
if (!returnType.contains("void")) {
src.append("return ").append("(").append(returnType).append(")");
}
src.append("this.h.invoke(this,m,").append("new java.lang.Object[]{").append(params2).append("}").append(");").append(ln);
src.append("}catch(java.lang.Throwable e){throw new java.lang.RuntimeException(e);}").append(ln);
src.append("}").append(ln);
}
src.append("}");
String s = src.toString();
System.out.println(s);
return s;
接下来就是重头戏了, 也就是jdk动态代理api的实现模板了
//1 生成类文件
int num = integer.incrementAndGet();
String className ="$Proxy" + num ;
String proxySrc = generateSrc(interfaces[0], className);
//2 加载类文件
String filePath = Proxy.class.getResource("").getPath();
File f = new File(filePath + className + ".java");
f.deleteOnExit();
File classFile = new File(filePath + className + ".class");
classFile.deleteOnExit();
FileWriter fw = new FileWriter(f);
fw.write(proxySrc);
fw.flush();
fw.close();
//3. 通过java api提供的编译器进行编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
//4. 类加载器加载类
Class proxyClass = classLoader.findClass(className);
Constructor c = proxyClass.getConstructor(InvocationHandler.class);
f.delete();
//5. 返回动态代理类的实例
return c.newInstance(h);
最后的执行结果就是输出一个before,来表明走了我们的代理新增逻辑了.(这里和装饰器模式的概念有点冲, 因为新增功能是装饰器模式的概念, 而代理模式仅表明是原实例的代理转发功能,不过也不影响我们的理解动态的思路)
各位应该发现了,我们上面的步骤其实多了一步,就是生产java文件, 因为jvm认识的是class,我们直接给他class就好了,为什么还多生成一个java文件, 这又不是我们平常写代码,写完代码统一用maven编译,这可是已经在运行中了啊, 所以我们应该直接生成class文件才对.
所以这里就用到字节码生成器 javassist了, 这个包的底层实现用的ASM(但是直接写字节码太复杂了).
然后就是具体的实现, 具体的实现和上面基本大同小异, 主要还是我不能直接写出全部的字节码文件(又难又累,倒是可以先生成字节码,然后在改成通用的,但是也麻烦啊), 所以主要还是将之前的java文件内容交给javassist去翻译为class文件,然后直接加载到jvm中去,可以提升很大的性能.
具体的实现如下
try {
int num = integer.incrementAndGet();
String className = "$Proxy" + num ;
// 创建类
CtClass ctClass = mPool.makeClass(className);
//添加接口
ctClass.addInterface(mPool.get(interfaces[0].getName()));
//添加成员变量
ctClass.addField(CtField.make("com.cdy.demo.repeatedWheels.myproxy.InvocationHandler h;", ctClass));
String constructor = "public " + className + "(" + InvocationHandler.class.getName() + " h) {" + ln +
"this.h = h;" + ln +
"}" + ln;
//添加构造函数
ctClass.addConstructor(CtNewConstructor.make(constructor,ctClass));
for (Method m : interfaces[0].getMethods()) {
StringBuilder method = new StringBuilder();
StringBuilder params = new StringBuilder();
StringBuilder params2 = new StringBuilder();
StringBuilder classes = new StringBuilder();
for (Parameter parameter : m.getParameters()) {
params.append(parameter.getType().getName()).append(" ").append(parameter.getName()).append(",");
classes.append(parameter.getType().getName()).append(".class").append(",");
params2.append(parameter.getName()).append(",");
}
params.deleteCharAt(params.lastIndexOf(","));
params2.deleteCharAt(params2.lastIndexOf(","));
classes.deleteCharAt(classes.lastIndexOf(","));
String returnType = m.getReturnType().getName();
method.append("public ").append(returnType).append(" ")
.append(m.getName()).append("(").append(params.toString()).append("){").append(ln);
method.append("try{").append(ln);
method.append("java.lang.reflect.Method m = ").append(interfaces[0].getName()).append(".class.getMethod(\"")
.append(m.getName()).append("\",new java.lang.Class[]{").append(classes).append("});").append(ln);
if (!returnType.contains("void")) {
method.append("return ").append("(").append(returnType).append(")");
}
method.append("this.h.invoke(this,m,").append("new java.lang.Object[]{").append(params2).append("}").append(");").append(ln);
method.append("}catch(java.lang.Throwable e){throw new java.lang.RuntimeException(e);}").append(ln);
method.append("}").append(ln);
// 添加方法
ctClass.addMethod(CtMethod.make(method.toString(), ctClass));
}
//生成类
Class clazz = ctClass.toClass(classLoader, JavasisstProxy.class.getProtectionDomain());
Constructor c = clazz.getConstructor(InvocationHandler.class);
// 实例化
return c.newInstance(h);
} catch (Exception e) {
throw new RuntimeException(e);
}
总的来说呢, javassist的方案的性能会比方案一高很多, 而且这个框架被很多其他的开源所用到比如dubbo的动态代理实现和spi的扩展实现都是有javassist的身影.
最后说一下就是其实动态代理除了jdk的动态代理还有cglib的动态代理, jdk动态代理主要是根据接口去生成代理类进行委托, 而cglib是通过直接继承对应的实现类来实现. 早期的时候cglib的性能会高出jdk动态代理很多, cglib使用的FastMethod可以一定程度提升性能, 不过后来经过jdk各个版本的优化后,反射性能不会差太多了,所以不用太纠结性能. 具体的cglib和反射的性能差异可以Java反射的效率测试-优先使用FastMethod看这篇文章的demo测试,不过这个demo测试有个不好的地方在于, 在调用方法那里,所有的参数都指向同一个实例, 在这个情况下cglib 的速度会快很多, 于是我改成都是new 出来的数组, 得出的速度如下