Java动态代理解析

Java动态代理的用法如下:

public class Main {

    public static void main(String[] args) throws IOException {
        // 1. 创建Proxy对象,并强制转换为接口类型
        Test proxy = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{Test.class}, new InvocationHandler() {  // 2. 创建InvocationHandler对象,并在invoke中做方法实现
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                method.invoke(new Test() {
                    @Override
                    public void print() {
                        System.out.println("这里是匿名内部类");
                    }
                }, args);
                return null;
            }
        });
        
        // 3. 使用
        proxy.print();
    }
}

interface Test {
    public abstract void print();
}

而上面InvocationHandler的invoke方法中对接口定义的方法的实现是通过接口的匿名内部类完成的,当然还可以使用其他的方式,例如:

Test proxy = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{Test.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("这里是代理方法");
                if (method.getName().equals("print")) {
                    System.out.println("你调用了print方法,来自于接口");
                } else {
                    System.out.println("你调用了来自Object的方法,"+method.getName());
                }
                return null;
            }
        });
        proxy.print();

上述展示的在InvocationHandler的invoke方法中对各个方法调用的实现是通过if语句判断调用的方法,然后进行操作的。

关于Java中的动态代理,其中几个关键角色说一下:

  • Proxy:官方提供的类,主要使用其newProxyInstance静态方法获取对接口进行实现的、真正动态代理类对象
  • InvocationHandler:方法调用委派对象,接口中所定义的API方法,在生成的动态代理类中,全都是分派到该handler的invoke方法,即接口方法的真正实现逻辑是需要开发者在handler的invoke中进行

说明:在InvocationHandler的invoke方法中,第一个参数proxy就是实际的代理对象,而第二个参数为前者含有的各个方法,最后一个是方法参数。他们之间的关系如下:

class Proxy$0 {
 Method m1 = Class.forName("XXX.Proxy$0").getMethod("method1", Class.forName("[java.lang.Object"));
  public Object method1(Object[] args) {
      return handler.invoke(this, m1, args);
  }
}

即传入的InvocationHandler的第一个Object参数为代理类对象this,第二个参数为被调用的方法Method对象,第三个参数是方法参数。

因此,InvocationHandler的invoke方法中,不能够method.invoke(proxy, args),这会导致无限死循环proxy.h.invoke() -> proxy.method.invoke() -> proxy.h.invoke() ...

因此,根据上述的使用方式,我们有如下两个疑问:

  • 生成的代理类如何持有InvocationHandler对象的
  • 生成的代理类如何实现方法的

Proxy通过一个静态方法newProxyInstance隐去了所有的细节,我们就来看看该方法的实现:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }

该方法,大体上很简单,获取构造函数,然后调用newProxyInstance方法返回对象,而newProxyInstance方法内部也只是简单的回调cons.newInstance:

    private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
                                           Constructor<?> cons,
                                           InvocationHandler h) {
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (caller != null) {
                checkNewProxyPermission(caller, cons.getDeclaringClass());
            }

            return cons.newInstance(new Object[]{h});
        } ....
    }

唉,我们发现,调用Constructor的newInstance时,传递的参数正式InvocationHandler对象,这就说明了,生成的代理类有一个构造器,该构造器接收一个InvocationHandler对象为参数。因此,就来到了第一个关注的问题:代理类是如何以及何时生成这样一个构造器的?问题的答案,一定来自getProxyConstructor方法,在分析该方法前我们先不关心这里的参数caller为何,仅当其为null。

    private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // optimization for single interface
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            if (caller != null) {
                checkProxyAccess(caller, loader, intf);
            }
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()  // build()方法返回的才是Constructor对象
            );
        } else {
            // interfaces cloned
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
                checkProxyAccess(caller, loader, intfsArray);
            }
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()  // build()方法返回的才是Constructor对象
            );
        }
    }

实际上的构造器正是通过ProxyBuilder的build()方法返回的,至于这里的getProxyConstructor方法其他部分到底是做什么的,我们先不管,直接来看ProxyBuilder.build()方法

// ProxyBuilder.java

        Constructor<?> build() {
            Class<?> proxyClass = defineProxyClass(module, interfaces);
            final Constructor<?> cons;
            try {
                cons = proxyClass.getConstructor(constructorParams);
            } ...
            return cons;
        }

该方法也很简单明了,就是创建代理类的Class对象,然后获取其构造器返回就行了。看到这里是不是激动了起来,代理类的Class对象?好家伙,我们明明没有写过一行代理类的源代码,Java是怎么给我们凭空创建出来一个Class对象的?那么这个代理类的Class文件长啥样呢?咋生成的呢?

关于上面的代码,还有一个要说的点是constructorParams是一个在定义时就被初始化了的属性

private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

嗯,不出所料,正印证了前面在调用构造器的newInstance方法时需要传入InvocationHandler对象。

这就引入了问题的关键:Java是如何凭空定义一个代理类的?又是如何生成其Class文件的?搞清楚其中内容,自然也就知道了接口中的方法以及构造器是如何写的了。因此,关键来到了defineProxyClass方法:

// ProxyBuilder.java

        private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
            ....
            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = PROXY_GENERATOR_V49
                    ? ProxyGenerator_v49.generateProxyClass(proxyName, interfaces, accessFlags)
                    : ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags);
            try {
                Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile,
                                              null, "__dynamic_proxy__");
                reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
                return pc;
            } 
        }

该方法先会处理一些module以及package的信息,但这不是我们所关心的,直接来到generateProxyClass方法调用处,该方法就是生成代理类的地方,我们这里就看第二个ProxyGenerator吧。

// ProxyGenerator.java
    static byte[] generateProxyClass(ClassLoader loader,
                                     final String name,
                                     List<Class<?>> interfaces,
                                     int accessFlags) {
        ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags);
        final byte[] classFile = gen.generateClassFile();

        ....
        return classFile;
    }

已经离答案越来越近了,生成Class文件的地方就是generateClassFile方法:

// ProxyGenerator.java

private byte[] generateClassFile() {
        visit(V14, accessFlags, dotToSlash(className), null,
                JLR_PROXY, typeNames(interfaces));

        // 添加上hashCode、equals、toString三个从Object继承来的方法
        addProxyMethod(hashCodeMethod);
        addProxyMethod(equalsMethod);
        addProxyMethod(toStringMethod);

        
        for (Class<?> intf : interfaces) {
            for (Method m : intf.getMethods()) {
                if (!Modifier.isStatic(m.getModifiers())) {
                    // 添加上所有想要代理的接口的方法
                    addProxyMethod(m, intf);
                }
            }
        }

        ...
            
        // 凭空生成构造器
        generateConstructor();

        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {
                // add static field for the Method object
                visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName,
                        LJLR_METHOD, null, null);

                // 为所有上面添加的方法生成方法实现code
                pm.generateMethod(this, className);
            }
        }

        // 生成初始化块儿
        generateStaticInitializer();
    
        return toByteArray();
    }

可以看到,generateClassFile思路非常清晰:

  • 添加所有涉及到的方法,并之后generate出方法实现;
  • 为构造器generate出实现;
  • generate静态初始化块儿。

这三个内容唯独对方法的处理需要先add,我们就先看看add的到底是啥:

    private final static ProxyMethod hashCodeMethod;
    private final static ProxyMethod equalsMethod;
    private final static ProxyMethod toStringMethod;

    static {
        try {
            hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), "m0");
            equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), "m1");
            toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), "m2");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

可以看到,原来在ProxyGenerator中每个方法使用一个ProxyMethod指代,ProxyMethod中首先是包含了对应的真实的Method对象,然后紧跟的一个字符串代表着该方法在代理类对象中对应的引用的名字,例如在代理类中m0属性如下定义:

Method m0 = Class.forName("java.lang.Object").getMethod("hashCode");

找到了地方,我们先来看看是如何为代理类生成构造器的代码的。

生成构造器

该功能由generateConstructor()方法实现:

// ProxyGenerator.java    
private void generateConstructor() {
        MethodVisitor ctor = visitMethod(Modifier.PUBLIC, NAME_CTOR,
                MJLR_INVOCATIONHANDLER, null, null);
        ctor.visitParameter(null, 0);
        ctor.visitCode();
        ctor.visitVarInsn(ALOAD, 0);
        ctor.visitVarInsn(ALOAD, 1);
        ctor.visitMethodInsn(INVOKESPECIAL, JLR_PROXY, NAME_CTOR,
                MJLR_INVOCATIONHANDLER, false);
        ctor.visitInsn(RETURN);

        // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored
        ctor.visitMaxs(-1, -1);
        ctor.visitEnd();
    }

一头雾水,这些都是啥啊。不急,我们先来看看visitMethod方法传递的参数就知道了:

  • Modifier.PUBLIC:唉,这不是public修饰符吗?
  • NAME_CTOR:是个常量,内容为"<init>",莫名熟悉;
  • MJLR_INVOCATIONHANDLER:也是个常量,内容为"(Ljava/lang/reflect/InvocationHandler;)V"!!

看到上面的三个参数,大师,我悟了!!这三个拼起来,不就是class文件中构造器的descriper吗?原来,Java的动态代理真的想跳过源码编译的步骤,直接写出一个class文件来!!

看到这里大致明白了,这里的MethodVisitor应该是代表着要往class文件中写入一个Method,而创建MethodVisitor时根据传入的参数确定下了该Method的描述符以及访问权限,那么还有一个重头戏就是Method的code部分怎么写,这些就是上述的各种visitXXX了

实际上根据上述几个visitXXX的第一个参数:ALOAD、INVOKESPECIAL、RETURN就能猜出来了,这写显然就是JVM标准定义的栈指令描述符啊,因此,后续调用visitEnd()方法之前的各种visitXXX方法就是在硬写一个个指令完成方法code的书写。

等等,说着说着,怎么为代理类生成的构造器中使用invokespecial指令回调了其他类的方法,我们先来看看回调的是谁的方法:

JLR_PROXY -> private static final String JLR_PROXY = "java/lang/reflect/Proxy";
NAME_CTOR -> private static final String NAME_CTOR = "<init>;
MJLR_INVOCATIONHANDLER -> private static final String MJLR_INVOCATIONHANDLER = "(Ljava/lang/reflect/InvocationHandler;)V";

破案了,原来生成的代理类继承自Proxy类,而且构造器的实现就是简单的super(InvocationHandler),而我们看看Proxy构造器:

    protected InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

好小子,分析了半天,让你Proxy小子给截胡了。

生成普通方法

代理类中方法属性的命名问题

在ProxyGenerator类中直接为hashCode、toString、equals三个方法创建好了对应的ProxyMethod对象,并且提到了为其在代理类中的属性命名为了m0、m1、m2;而其他来自接口中的方法又是如何命名的呢?而上面显示,来自于接口的方法被addProxyMethod的方式添加了进来:

    // ProxyGenerator.java
    private int proxyMethodCount = 3;

    private void addProxyMethod(Method m, Class<?> fromClass) {
        ...
        List<ProxyMethod> sigmethods = proxyMethods.computeIfAbsent(sig,
                (f) -> new ArrayList<>(3));
        ...
        sigmethods.add(new ProxyMethod(m, sig, m.getParameterTypes(), returnType,
                exceptionTypes, fromClass,
                "m" + proxyMethodCount++));
    }

很简单,就是proxyMethodCount开始计数罢了。

生成接口方法的code

在generateClassFile方法中,生成接口方法code的时机发生在生成Constructor之后,代码如下:

// ProxyGenerator.java

private byte[] generateClassFile() {
      ....
        // 凭空生成构造器
        generateConstructor();

        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {
                // add static field for the Method object
                visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName,
                        LJLR_METHOD, null, null);

                // 为所有上面添加的方法生成方法实现code
                pm.generateMethod(this, className);
            }
        }

    
        return toByteArray();
    }

不同于generateConstructor,generateMethod定义在ProxyMethod中:

       // ProxyMethod.java
       private void generateMethod(ClassWriter cw, String className) {
            ....
            MethodVisitor mv = cw.visitMethod(accessFlags,
                    method.getName(), desc, null,
                    typeNames(Arrays.asList(exceptionTypes)));

            ....
            // 开始编写code部分
            mv.visitCode();
            ...
                
            // 准备第一个参数this
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, JLR_PROXY, handlerFieldName,
                    LJLR_INVOCATION_HANDLER);
           // 准备第二个参数
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETSTATIC, dotToSlash(className), methodFieldName,
                    LJLR_METHOD);

            ....

            // invokeinterface调用方法
            mv.visitMethodInsn(INVOKEINTERFACE, JLR_INVOCATION_HANDLER,
                    "invoke",
                    "(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
                            "[Ljava/lang/Object;)Ljava/lang/Object;", true);

            ...
            mv.visitEnd();
        }

该方法的结构和generateConstructor一致,这里的MethodVisitor肯定也可以看做方法的descripter+accessFlags,重点解释如下几个栈指令调用:

mv.visitFieldInsn(GETFIELD, JLR_PROXY, handlerFieldName,LJLR_INVOCATION_HANDLER);
    JIR_PROXY -> private static final String JLR_PROXY = "java/lang/reflect/Proxy";
    handlerFieldName -> private static final String handlerFieldName = "h";
    LJLR_INVOCATION_HANDLER -> private static final String LJLR_INVOCATION_HANDLER = "Ljava/lang/reflect/InvocationHandler;"

mv.visitFieldInsn(GETSTATIC, dotToSlash(className), methodFieldName,LJLR_METHOD);
    methodFieldName -> "m0""m1"这种
    LJLR_METHOD -> private static final String LJLR_METHOD = "Ljava/lang/reflect/Method;"
        
// 接下来这个想必已经不用解释了
mv.visitMethodInsn(INVOKEINTERFACE, JLR_INVOCATION_HANDLER,
                    "invoke",
                    "(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
                            "[Ljava/lang/Object;)Ljava/lang/Object;", true);

很显然,在生成的代理类中,所有的方法实现都是如下形式:

h.invoke(this, m0, args);

即,全都委托给了代理类持有的InvocationHandler h的invoke方法来完成

动态代理总结

使用

动态代理的使用最重要的就是InvocationHandler对象,该对象的invoke方法负责对代理类中所有方法进行实现,至关重要。

知识点

  1. 生成的代理类是Proxy这个类的子类,而后者的构造器在代理类中被回调,用于接收InvocationHandler对象;
  2. 代理类的所有方法都会在类中定义一个Method属性,名字从m0开始沿用;
  3. 代理类中所有方法实现都是如下模板:
class Proxy$0 {
    protected InvocationHandler h;
    private Method m3 = Class.forName("代理类的全限定名").getMethod("方法名", Class.forName("[java.lang.Objec或者接口全限定名"));
    public Object method1(Object[] args) {
        return h.invoke(this, m3, args);
    }
}

实例

最后我们来看看现象,以下实例来自文章https://mp.weixin.qq.com/s/gnj8x4bSQoNRMcRUWS6p5Q

public finalclass $Proxy0 extends Proxy implements Person { ★
    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 {   ④
        return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
    }

    public final void rent() throws {   ③
        super.h.invoke(this, m3, (Object[]) null);
    }

    public final String toString() throws { ④
        return (String) super.h.invoke(this, m2, (Object[]) null);
    }

    public final int hashCode() throws {    ④
        return (Integer) super.h.invoke(this, m0, (Object[]) null);
    }

    static {    ①
        m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
        m3 = Class.forName("com.dujc.mybatis.proxy.Person").getMethod("rent");
        m2 = Class.forName("java.lang.Object").getMethod("toString");
        m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    }
}

这就是将动态代理生成的class文件反编译出来的内容,可以看到,和我们上文分析的一样。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容