黑马程序员技术交流社区

标题: 【济南校区】JDK动态代理解析 [打印本页]

作者: 大山哥哥    时间: 2018-6-1 20:06
标题: 【济南校区】JDK动态代理解析
本帖最后由 大山哥哥 于 2018-6-1 20:06 编辑

        我们在学习框架的时候经常会听到XXX处使用到了动态代理的技术,而我们也在课上说过动态代理的基本使用方式,但是还是很多同学对动态代理的使用和基本原理没有很好的认识,今天在这我们就讨论一下JDK中的动态代理的基本实现原理。        代理,其实就是代替处理。也就是写一个类代替之前的被代理类处理被代理类的所有逻辑,从而实现在不修改原类代码的基础上修改逻辑的目的。而动态代理指的就是这个代理类不是手写的固定的Class,而是用代码运行时动态生成的一个类。
        首先,我们先看一下动态代理怎么用的。我在这写一个小例子:
        先定义一个很简单的接口,声明两个方法:
[AppleScript] 纯文本查看 复制代码
package com.itheima.test;

public interface TestInterface {
        void method1();
        String method2();
}

        写一个实现类实现这个接口:
[AppleScript] 纯文本查看 复制代码
package com.itheima.test;

public class TestClass implements TestInterface {

        @Override
        public void method1() {
                System.out.println("方法1被调用了");
        }

        @Override
        public String method2() {
                return "method2";
        }

}

        测试一下:
[AppleScript] 纯文本查看 复制代码
package com.itheima.test;

public class Test {
        public static void main(String[] args) {
                TestInterface object = new TestClass();
                object.method1();
                System.out.println(object.method2());
        }
}

        输出的结果是:
[AppleScript] 纯文本查看 复制代码
方法1被调用了
method2

        到此为止都还是比较简单的代码。接下来我们的需求出现了,在不修改TestClass这个类的源码的前提下,把method1打印结果的代码前后各打印一句话。如果使用JDK的动态代理,可以如下编写:
        
[AppleScript] 纯文本查看 复制代码
package com.itheima.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
        public static void main(String[] args) {
                // 动态代理
                TestInterface proxy = (TestInterface) Proxy.newProxyInstance(object.getClass().getClassLoader(),
                                object.getClass().getInterfaces(), new InvocationHandler() {

                                        @Override
                                        public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
                                                Object result = null;
                                                if ("method1".equals(method.getName())) {
                                                        System.out.println("在代码执行前打印");
                                                        result = method.invoke(object, params);
                                                        System.out.println("在代码执行前打印");
                                                }else{
                                                        result = method.invoke(object, params);
                                                }
                                                return result;
                                        }
                                });
                proxy.method1();
                System.out.println(proxy.method2());
        }
}
        执行的结果是:
[AppleScript] 纯文本查看 复制代码
在代码执行前打印
方法1被调用了
在代码执行前打印
method2


        使用大家都会,那这个神奇的Proxy.newProxyInstance()内部做了什么呢,动态代理的代码执行流程又是怎么样的呢?现在给大家介绍一下。
        首先,Proxy.newProxyInstance()这个方法有三个参数,第一个参数是被代理对象对应的类加载器,因为在内部需要用这个类加载器重新加载第二个参数指定的所有接口用于验证。这个和类加载器的委托机制有关,篇幅所限,不展开介绍了,大家可以自行查阅类加载器的相关资料。第二个参数是指定被代理对象的所有接口,这个接口其实会被代理类所实现,如下图,代理类会和被代理类,也就是目标类实现一样的接口以保证功能的一致性。
而第三个参数是最重要的,invocationHandler代表调用的处理器,它会负责代理类的方法执行。通俗来说,代理类的每个方法的调用,最终执行的都是invocationHandler中的invoke方法,而这个invoke方法有三个参数:第一个proxy参数就是代理类对象;第二个method就是代理类被调用的方法对应的Method对象了,是供反射调用使用的;第三个params就是代理类调用方法时传递的所有参数。这么说可能还是优点抽象,我们来模拟一下Proxy.newProxyInstance()方法中编写代理类的过程,来看一看究竟代理类怎么“写”出来的。
        还是接着上面的例子演示,当我们调用Proxy.newProxyInstance()的时候,由于传递了ClassLoader和interfaces数组,所以该方法会加载这些接口,然后生成一个类实现这些接口,同时,第三个参数invocationHandler会被存入到成员上供以后调用。如下:

[AppleScript] 纯文本查看 复制代码
/**
*  这是一个代理类
*/
class ProxyDemo implements TestInterface{
        // 把外部传入的invocationHandler存到成员上
        private InvocationHandler invocationHandler;

        public ProxyDemo(InvocationHandler invocationHandler) {
                this.invocationHandler = invocationHandler;
        }
....

        而实现接口就要实现接口中的所有方法,在这个代理类中,方法是没有具体的逻辑实现的,只是调用了invocationHandler里的invoke方法,如下:
[AppleScript] 纯文本查看 复制代码

class ProxyDemo implements TestInterface{
......

        @Override
        public void method1() {
                try {
                        invocationHandler.invoke(this, TestInterface.class.getMethod("method1"),null);
                } catch (Throwable e) {
                        e.printStackTrace();
                }
        }

        @Override
        public String method2() {
                try {
                        return (String) invocationHandler.invoke(this, TestInterface.class.getMethod("method2"),null);
                } catch (Throwable e) {
                        e.printStackTrace();
                }
                return null;
        }
        
}

        看到这大家就明白了,当我们调用Proxy.newProxyInstance()这个方法的时候其实它内部就“写”了这么一个代理类,此代理类实现了我们传参时候指定的所有接口,并且方法内部的实现直接调用了我们传参时通过invocationHandler指定的逻辑。所以我们调用代理类的方法时,实际执行的是invocationHandler的invoke方法。可以通过对方法名称的判断方便的对某些方法进行逻辑上的修改、添加,达到动态代理某个类的对象的目的。但是JDK的动态代理只能代理有接口的类,修改的也是接口中的方法,所以还是有一定的局限性的。

作者: Yin灬Yan    时间: 2018-6-2 11:41
我来占层楼啊   




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2