jvm原理(36)透过字节码生成审视Java动态代理运作机制

Posted 魔鬼_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm原理(36)透过字节码生成审视Java动态代理运作机制相关的知识,希望对你有一定的参考价值。

我们在使用spring这类框架的时候,基于动态代理的使用,比如AOP,会使得开发更加灵活,那么在字节码的层面动态代理是什么样子的呢,生成出来的代理类结构是什么,本次我们首先写一个动态代理的例子,然后得到生成的动态代理类。
定义接口:

public interface SubJect 
    void request();

定义实现类:

public class RealSubJect implements SubJect 
    @Override
    public void request() 
        System.out.println("method calling");
    

定义动态代理类:

public class DynamicSubject implements InvocationHandler 

    private Object sub;

    public DynamicSubject(Object obj)
        this.sub = obj;
    

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        System.out.println("befor method call");
        method.invoke(sub,args);
        System.out.println("after method call");
        return null;
    

定义客户端:

public class Client 
    public static void main(String[] args) 
        RealSubJect realSubJect = new RealSubJect();
        InvocationHandler invocationHandler = new DynamicSubject(realSubJect);
        SubJect subJect = (SubJect) Proxy.newProxyInstance(realSubJect.getClass().getClassLoader(),realSubJect.getClass().getInterfaces(),invocationHandler);
        subJect.request();
        System.out.println(subJect.getClass());//打印动态代理类的class
        System.out.println(subJect.getClass().getSuperclass()); //打印父类
    

运行客户端得到结果:

befor method call
method calling
after method call
class com.sun.proxy.$Proxy0
class java.lang.reflect.Proxy

那么这个com.sun.proxy.$Proxy0是怎么出来的呢,这个需要进入到Proxy.newProxyInstance()里边看一下他的逻辑:

Proxy.newProxyInstance()
  -->getProxyClass0()
     -->proxyClassCache.get()[通过ProxyClassFactory获取]
        -->WeakCache.Factory.get()
          -->valueFactory.apply(key, parameter)
             -->Proxy.apply()
               -->byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
                  -->byte[] proxyClassFile = ProxyGenerator.generateClassFile();

ProxyGenerator是 sun.misc包里边的,我们得到的代码是ide反编译的结果,我们贴出来,var4是生成出来的字节数组,然后
saveGeneratedFiles是一个开关,如果为true就会把class文件输出到磁盘

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) 
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();//返回最终的字节数组
    if (saveGeneratedFiles) 
        AccessController.doPrivileged(new PrivilegedAction<Void>() 
            public Void run() 
                try 
                    int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if (var1 > 0) 
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                     else 
                        var2 = Paths.get(var0 + ".class");
                    

                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                 catch (IOException var4x) 
                    throw new InternalError("I/O exception saving generated file: " + var4x);
                
            
        );
    

    return var4;

saveGeneratedFiles的定义:

 private static final boolean saveGeneratedFiles =
 ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

ok 我们设置一下sun.misc.ProxyGenerator.saveGeneratedFiles就可以得到class文件。下边这行代码放在Client main方法的第一行,提前设置。

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

运行Client,然后在我们的工程里边就会出现一个目录
com.sun.proxy.$Proxy0
这个就是动态代理类:

package com.sun.proxy;

import com.twodragonlake.jvm.bytecode.SubJect;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements SubJect 
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    /**
    构造方法
    这个构造方法在Proxy类的newProxyInstance方法里边,有一个地方是获取代理类的构造器:
     Class<?> cl = getProxyClass0(loader, intfs); //代理类
     final Constructor<?> cons = cl.getConstructor(constructorParams);//获取代理类的构造器
     return cons.newInstance(new Object[]h);//实例化代理类;
     h是InvocationHandler,InvocationHandler是我们定义的DynamicSubject的接口,这个时候就会调用当前的这个构造方法
     动态代理类的父类Proxy持有InvocationHandler的引用,完成这个引用的赋值。
    */
    public $Proxy0(InvocationHandler var1) throws  
        super(var1);
    

    public final boolean equals(Object var1) throws  
        try 
          /**
            调用Proxy的InvocationHandler的invoke方法,其实就是调用DynamicSubject的invoke方法,
            因为DynamicSubject实现了InvocationHandler,m1是Object类的equals方法,new Object[]var1是equals的方法参数
            如果DynamicSubject重新了equals方法就会转发到DynamicSubject的equals方法,否则就是调用Object的equals方法
          */
            return ((Boolean)super.h.invoke(this, m1, new Object[]var1)).booleanValue();
         catch (RuntimeException | Error var3) 
            throw var3;
         catch (Throwable var4) 
            throw new UndeclaredThrowableException(var4);
        
    

    /**
    和equals方法道理了一样
    */
    public final String toString() throws  
        try 
            return (String)super.h.invoke(this, m2, (Object[])null);
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    

    public final void request() throws  
        try 
            super.h.invoke(this, m3, (Object[])null);
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    

    /**
    和equals方法道理了一样
    */
    public final int hashCode() throws  
        try 
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    

    static 
        try 
          //类加载的时候,将Object类的是个方法拿出来
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.twodragonlake.jvm.bytecode.SubJect").getMethod("request");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
         catch (NoSuchMethodException var2) 
            throw new NoSuchMethodError(var2.getMessage());
         catch (ClassNotFoundException var3) 
            throw new NoClassDefFoundError(var3.getMessage());
        
    

除了equals、toString、hashCode的其他的Object的方法,都不会得到代理类的转发,原来是什么样子的,代理后也还是什么样子的,不会发生变化。
好,到目前为止我们分析了动态代理类的源码,那回到client的代码:

  SubJect subJect = (SubJect) Proxy.newProxyInstance(realSubJect.getClass().getClassLoader(),realSubJect.getClass().getInterfaces(),invocationHandler);

subJect返回的是一个动态代理类,这个类的名字是 P r o x y 0 , Proxy0, Proxy0Proxy0的父类是Proxy,父类持有InvocationHandler的引用,也就是持有DynamicSubject的引用,同时 P r o x y 0 实 现 了 S u b J e c t 接 口 , Proxy0实现了SubJect接口, Proxy0SubJectProxy0里边生成了DynamicSubject里边所声明的方法,并且转发到了DynamicSubject里边。当我们调用subJect.request();
的时候就是调用了 P r o x y 0. r e q u e s t ( ) ; Proxy0.request(); Proxy0.request();Proxy0将方法的调用通过父类Proxy持有的InvocationHandler的引用,即【super.h.invoke(this, m0, (Object[])null))】进行了转发,转发到DynamicSubject的invoke方法。所以说DynamicSubject的invoke方法的第一个参数是proxy,接下里就会打印:

befor method call
method calling
after method call

动态代理的优势:
代理对象可以在没有真实对象不存在的情况下提前生成代理对象,代理对象可以代理多种真实对象,而且jdk的动态代理是面向接口的。
cglib:
面向继承的代理,子类可以重写父类的实现,同时代理类可以调用父类的方法(jdk动态代理没有这个优势,因为jdk动态代理是面向接口的)

以上是关于jvm原理(36)透过字节码生成审视Java动态代理运作机制的主要内容,如果未能解决你的问题,请参考以下文章

Clojure 运行原理之字节码生成篇

透过字节码分析Java动态代理机制。

JVM字节码执行引擎和动态绑定原理

jvm原理(33)通过字节码分析Java方法的静态分派与动态分派机制(invokevirtual 指令)

jvm原理(33)通过字节码分析Java方法的静态分派与动态分派机制(invokevirtual 指令)

JAVA 反射原理