• 你一定能看懂的JDK动态代理



    卡通买书

    前言:阅读这篇文章前,一定要知道什么是代理模式,具体可以参考这篇文章《设计模式(一):代理模式》

    《设计模式(一):代理模式》一文中说了,程序员思思买书有两种选择:一种是选择去书厂(目标对象)买;另一种则是去书店(代理对象)买。第二种方式可以称为静态代理,因为这个代理对象是我们自己编写的。而JDK动态代理则是一种系统自动为我们生成代理对象的方式,下面先介绍一下这种方式如何实现。

    一、JDK动态代理


    首先还是有一家书厂,并且宣称自己有思思想要的书卖。

    public interface Book {
        public void buyBook();
    }
    public class BookFactory implements Book{
        @Override
        public void buyBook() {
            System.out.println("思思买的书来自书厂");
        }
    }

    现在思思将使用JDK动态代理的方式去买到和书厂一模一样的书,那应该怎么做呢?

    首先思思要找到一个 “神秘人” ,这个人专做各种倒买倒卖的生意。

    public class MysteryMen implements InvocationHandler{
        private Object target; //思思需要告诉神秘人她想买书厂的书
        public MysteryMen(Object target){ 
            this.target = target;
        }
        //下面是神秘人做的事情,他能帮思思买到书,暂时不用管是怎么做到的。
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("神秘人开始买书");
            method.invoke(target, args);
            System.out.println("神秘人完成买书");
            return null;
        }
    }

    找到神秘人买书后,神秘人告诉思思还必须填写下面这个承诺表,才能将书给她。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

    思思可是程序员啊,这自然难不倒她,下面是思思买书的具体步骤。

    public class SiSi {
        public static void main(String[] args) {
            Book book = new BookFactory();
            MysteryMen  mysteryMen = new MysteryMen(book);
            Book dynamicBookStore = (Book)Proxy.newProxyInstance(BookFactory.class.getClassLoader(), 
                    BookFactory.class.getInterfaces(), mysteryMen);
            dynamicBookStore.buyBook();
        }
    }

    执行 main 方法,结果如下:


    买书

    二、代码分析


    结合上面的例子,下面对整个动态代理的过程做一个简单的梳理。

    综合上一篇文章《设计模式(一):代理模式》可以看到,动态代理与静态代理有一个最大的不同点,就是静态代理需要自己编写代理类,即《设计模式(一):代理模式》中的 BookStore 类,再在 main 方法中执行 bookStore.buyBook()方法来达到目的。而使用动态代理时,这个代理对象是由系统在运行时为我们动态生成的。那系统到底是怎么为我们生成动态代理对象的呢?

    首先看示例中,Proxy 类是JDK为我们提供的,它的 newProxyInstance 方法源码是这样写的:

    *@param   loader the class loader to define the proxy class
    *@param   interfaces the list of interfaces for the proxy class
    *         to implement
    *@param   h the invocation handler to dispatch method invocations to
    *@return  a proxy instance with the specified invocation handler of a
    *          proxy class that is defined by the specified class loader
    *          and that implements the specified interfaces
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
    • 第一个参数是一个类加载器,这个类加载器必须是加载目标对象字节码的类加载器。
    • 第二个参数是指定把动态生成的代理对象挂载到哪个接口下,此处是让动态代理对象与目标对象实现了同一个接口 Book。
    • 第三个参数,也是最为重要的一个参数,需要一个InvocationHandler接口的实现类。
    • 这个方法返回的是一个动态代理对象。

    如果上面的参数不是很理解。具体到文中的例子,应该写成这样:Proxy.newProxyInstance(BookFactory.class.getClassLoader(),
    BookFactory.class.getInterfaces(), mysteryMen)

    InvocationHandler接口的源码如下:

    public interface InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }

    结合上面的例子来看,我们在 invoke 方法里写了这样一段代码method.invoke(target, args),这段代码涉及到 Java 反射的内容,相当于通过反射调用了 BookFactory 中的 buyBook 方法。

    那系统是怎么知道要去调用BookFactory方法的呢?

    使用Debug调试可以看到:


    动态代理01

    • method就是 buyBook 方法。
    • 而args是方法参数,此处为 null。
    • $Proxy0就是系统为我们动态生成的代理对象。通过在 main 方法里加上System.out.println(dynamicBookStore.toString)可验证。

    method.invoke(target, args)中还有一处还没有说到,那就是第一个参数target。这个参数为系统指明了调用哪个实现类的 buyBook 方法。在示例中我们通过构造函数的传参的方式,在MysteryMen类中传入了一个 BookFactory 类的引用,也是为了反射调用目标方法做准备的。

    至此我们重新梳理一下整个动态代理的过程。

    三、动态代理过程解析


    结合文章开始的例子,要写一个动态代理,首先得有个接口(Book)和该接口的实现类(BookFactory),这里的实现类其实就是被代理对象,或者称为目标对象 target ;然后写一个 InvocationHandler 实现类,这个实现类的 invoke 方法里包含着我们的处理逻辑,例如对目标对象方法的调用,或者添加上我们自己想要实现的处理逻辑;最后通过 Proxy 类的静态方法 newProxyInstance 来生成动态代理对象。

    再次使用Debug调试,将断点打在dynamicBookstore.buyBook()处,再单步进入此方法可以看到,程序立刻跳入了 MysteryMen 的 invoke 方法:
    debug

    这张图说明了一点,那就是执行dynamicBookstore.buyBook()方法其实是执行了 InvocationHandler 接口实现类的 invoke 方法,invoke 方法里才是真正的处理逻辑地方。

    在 invoke 方法里我们可以做任何我们想要的处理逻辑,例如在反射调用方法前后做一些操作,甚至不执行对目标方法的反射调用。

    三、动态代理的优势


    目前来说,我们使用动态代理完成的功能都能换用静态代理来完成。那么如此的话,动态代理到底有什么优势呢?

    在静态代理中,一个目标对象对应这一个代理对象,如果目标对象过多,将会加重程序的代码量。而对于动态代理来说,如果我们想要对多个类进行代理,只需通过 newProxyInstance 方法让程序为我们动态生成代理对象即可,减少了代码工作量。并且在目标对象和动态代理对象之间还增加了一层 InvocationHandler 对目标方法进行拦截,一定程度上实现了解耦。

    还有最重要的一点,就是静态代理在编译期间就需要指定对哪一个类进行代理,并写好该类的代理类。而动态代理则可以在运行期间确定需要对哪一个类进行代理,增加了系统的灵活性,其中运用了 Java 反射技术。

  • 相关阅读:
    Hbase 安装
    Hive操作
    Hive安装
    HDFS操作
    hadoop安装
    番茄时间管理法(Pomodoro Technique)
    测试架构师修炼之道:“秘书九段”的故事
    windows远程连接报:身份错误,函数不支持的解决办法
    Linux crontab配置
    Hadoop 历史服务配置启动查看
  • 原文地址:https://www.cnblogs.com/KKSJS/p/9622822.html
Copyright © 2020-2023  润新知