zoukankan      html  css  js  c++  java
  • 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什么都是最快的,毕竟是只和最底层打交道
    
  • 相关阅读:
    第十一章 练习。内附100道练习题URL
    第八章 模块;第九章 文件
    mysql union和join 的使用
    第七章 循环
    第六章 课后习题
    第六章 字符串操作
    第五章 课后习题
    第五章 容器之字典
    实战智能推荐系统笔记
    协同过滤推荐算法的原理及实现
  • 原文地址:https://www.cnblogs.com/silyvin/p/12032768.html
Copyright © 2011-2022 走看看