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什么都是最快的,毕竟是只和最底层打交道
    
  • 相关阅读:
    Java实现 蓝桥杯VIP 基础练习 完美的代价
    Java实现 蓝桥杯VIP基础练习 矩形面积交
    Java实现 蓝桥杯VIP 基础练习 完美的代价
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    核心思想:想清楚自己创业的目的(如果你没有自信提供一种更好的产品或服务,那就别做了,比如IM 电商 搜索)
    在Linux中如何利用backtrace信息解决问题
  • 原文地址:https://www.cnblogs.com/silyvin/p/12032768.html
Copyright © 2011-2022 走看看