jdk动态代理源码底层(jdk生成字节码及5种字节码生产方式比较)
在前两篇文章中
java 的三种代理模式
jdk动态代理与cglib优势劣势以及jdk动态代理为什么要interface
(警惕动态代理导致的Metaspace内存泄漏问题,警惕动态代理导致的Metaspace内存泄漏问题)
讨论了jdk的动态代理
本文从源码级别了解一下,在源代码的基础上,加上
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
在注解的本质(yet)中,我们也曾经这么干,将代理类弄出来
Proxy0.class
public final class $Proxy0 extends Proxy implements IUserDao { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void save() throws {【我们的方法】 try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
h:
public class ProxyFactory implements InvocationHandler { //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object returnValue = method.invoke(target, args); return returnValue; } //给目标对象生成代理对象 public Object getProxyInstance(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this); Class<?> cl = getProxyClass0(loader, intfs); return proxyClassCache.get(loader, interfaces); final Constructor<?> cons = cl.getConstructor(constructorParams); return cons.newInstance(new Object[]{h}); private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
https://www.cnblogs.com/liuyun1995/p/8144706.html
通过前面几篇的分析,我们知道代理类是通过Proxy类的ProxyClassFactory工厂生成的,这个工厂类会去调用ProxyGenerator类的generateProxyClass()方法来生成代理类的字节码。ProxyGenerator这个类存放在sun.misc包下,我们可以通过OpenJDK源码来找到这个类,该类的generateProxyClass()静态方法的核心内容就是去调用generateClassFile()实例方法来生成Class文件。我们直接来看generateClassFile()这个方法内部做了些什么。
private byte[] generateClassFile() { //第一步, 将所有的方法组装成ProxyMethod对象 //首先为代理类生成toString, hashCode, equals等代理方法 addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); //遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象 for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } //对于具有相同签名的代理方法, 检验方法的返回值是否兼容 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } //第二步, 组装要生成的class文件的所有的字段信息和方法信息 try { //添加构造器方法 methods.add(generateConstructor()); //遍历缓存中的代理方法 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { //添加代理类的静态字段, 例如:private static Method m1; fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); //添加代理类的代理方法 methods.add(pm.generateMethod()); } } //添加代理类的静态字段初始化方法 methods.add(generateStaticInitializer()); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } //验证方法和字段集合不能大于65535 if (methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } if (fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } //第三步, 写入最终的class文件 //验证常量池中存在代理类的全限定名 cp.getClass(dotToSlash(className)); //验证常量池中存在代理类父类的全限定名, 父类名为:"java/lang/reflect/Proxy" cp.getClass(superclassName); //验证常量池存在代理类接口的全限定名 for (int i = 0; i < interfaces.length; i++) { cp.getClass(dotToSlash(interfaces[i].getName())); } //接下来要开始写入文件了,设置常量池只读 cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); try { //1.写入魔数 dout.writeInt(0xCAFEBABE); //2.写入次版本号 dout.writeShort(CLASSFILE_MINOR_VERSION); //3.写入主版本号 dout.writeShort(CLASSFILE_MAJOR_VERSION); //4.写入常量池 cp.write(dout); //5.写入访问修饰符 dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); //6.写入类索引 dout.writeShort(cp.getClass(dotToSlash(className))); //7.写入父类索引, 生成的代理类都继承自Proxy dout.writeShort(cp.getClass(superclassName)); //8.写入接口计数值 dout.writeShort(interfaces.length); //9.写入接口集合 for (int i = 0; i < interfaces.length; i++) { dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName()))); } //10.写入字段计数值 dout.writeShort(fields.size()); //11.写入字段集合 for (FieldInfo f : fields) { f.write(dout); } //12.写入方法计数值 dout.writeShort(methods.size()); //13.写入方法集合 for (MethodInfo m : methods) { m.write(dout); } //14.写入属性计数值, 代理类class文件没有属性所以为0 dout.writeShort(0); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } //转换成二进制数组输出 return bout.toByteArray(); }
本质是用jdk的方式(不是javassist,不是cglib)构造了一个类(class文件字节码,非java文件)
private byte[] generateClassFile() {
//第一步, 将所有的方法组装成ProxyMethod对象 3 //首先为代理类生成toString, hashCode, equals等代理方法 4 addProxyMethod(hashCodeMethod, Object.class); 5 addProxyMethod(equalsMethod, Object.class); 6 addProxyMethod(toStringMethod, Object.class); 7 //遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象 8 for (int i = 0; i < interfaces.length; i++) { 9 Method[] methods = interfaces[i].getMethods(); 10 for (int j = 0; j < methods.length; j++) { 11 addProxyMethod(methods[j], interfaces[i]); 12 } 13 } 14 //对于具有相同签名的代理方法, 检验方法的返回值是否兼容 15 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 16 checkReturnTypes(sigmethods); 17 } 18 19 //第二步, 组装要生成的class文件的所有的字段信息和方法信息
//第二步, 组装要生成的class文件的所有的字段信息和方法信息 20 try { 21 //添加构造器方法 22 methods.add(generateConstructor()); 23 //遍历缓存中的代理方法 24 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 25 for (ProxyMethod pm : sigmethods) { 26 //添加代理类的静态字段, 例如:private static Method m1; 27 fields.add(new FieldInfo(pm.methodFieldName, 28 "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); 29 //添加代理类的代理方法 30 methods.add(pm.generateMethod()); 31 } 32 } 33 //添加代理类的静态字段初始化方法
//第三步, 写入最终的class文件
48 //验证常量池中存在代理类的全限定名
49 cp.getClass(dotToSlash(className));
50 //验证常量池中存在代理类父类的全限定名, 父类名为:"java/lang/reflect/Proxy"
51 cp.getClass(superclassName);
52 //验证常量池存在代理类接口的全限定名
//接下来要开始写入文件了,设置常量池只读
private ProxyGenerator.ConstantPool cp = new ProxyGenerator.ConstantPool(null);
57 cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream(); 60 DataOutputStream dout = new DataOutputStream(bout); 61 try {。。。
return bout.toByteArray();
方法区 永久代 元空间 常量池文中所说,
常量池
1.6 - 永久代(方法区)
1.7、1.8 - 堆
看来有误,这个地方被塞入代理类的常量池应属于方法区(1.7永久带,1.8元空间)
其他参考:
https://www.jianshu.com/p/3137b33efc3f
动态代理方案,优缺点,比较(摘抄+自己的理解)
//测试结果 //创建代理的速度 Create JDK Proxy: 13 ms Create CGLIB Proxy: 217 ms //较慢 Create JAVAASSIST Proxy: 99 ms Create JAVAASSIST Bytecode Proxy: 168 ms //较慢 Create ASM Proxy: 3 ms //最快 ================ Run JDK Proxy: 2224 ms, 634,022 t/s //很慢,jdk生成的字节码考虑了太多的条件,所以字节码非常多,大多都是用不到的 Run CGLIB Proxy: 1123 ms, 1,255,623 t/s //较慢,也是因为字节码稍微多了点 Run JAVAASSIST Proxy: 3212 ms, 438,999 t/s //非常慢,完全不建议使用javassist的代理类来实现动态代理 Run JAVAASSIST Bytecode Proxy: 206 ms, 6,844,977 t/s //和asm差不多的执行速度,因为他们都是只生成简单纯粹的执行字节码,非常少(所以直接用javassist生成代理类的方式最值得推荐[从速度上讲]) Run ASM Bytecode Proxy: 209 ms, 6,746,724 t/s //asm什么都是最快的,毕竟是只和最底层打交道