美文网首页
Java动态代理解析

Java动态代理解析

作者: 慕北人 | 来源:发表于2021-12-27 11:54 被阅读0次

    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文件反编译出来的内容,可以看到,和我们上文分析的一样。

    相关文章

      网友评论

          本文标题:Java动态代理解析

          本文链接:https://www.haomeiwen.com/subject/kkkyqrtx.html