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什么都是最快的,毕竟是只和最底层打交道
原文地址:https://www.cnblogs.com/silyvin/p/12032768.html