zoukankan      html  css  js  c++  java
  • 你一定能看懂的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 反射技术。

  • 相关阅读:
    怎么查看京东店铺的品牌ID
    PPT编辑的时候很卡,放映的时候不卡,咋回事?
    codevs 1702素数判定2
    codevs 2530大质数
    codevs 1488GangGang的烦恼
    codevs 2851 菜菜买气球
    hdu 5653 Bomber Man wants to bomb an Array
    poj 3661 Running
    poj 1651 Multiplication Puzzle
    hdu 2476 String Painter
  • 原文地址:https://www.cnblogs.com/KKSJS/p/9622822.html
Copyright © 2011-2022 走看看