java的动态代理

作者: 小小浪把_Dont_know拍 | 来源:发表于2018-03-16 19:40 被阅读25次

    代理模式

    在讲动态代理之前,需要了解什么是代理模式,经典的代理模式的uml图相信大家都见过:


    代理模式

    RealSubject是需要被代理的对象。我们要在RealSubject之外增加一些处理,就增加一个Proxy类,实现Subject接口,在Proxy类里持有RealSubject的实例。
    Client的请求全部打到Proxy实例上,由Proxy实例来控制是否将请求转发给RealSubject,或者做额外的处理。
    代理模式的优点:对于外界来讲,完全无感知,耦合性低。
    看一下实例代码:

    public class IntegerProxy implements Comparable<Integer> {
        private Integer i;
    
        public IntegerProxy(Integer i) {
            this.i = i;
        }
    
        @Override
        public int compareTo(Integer o) {
            System.out.println(i + "compareTo(" + o + ")");
            return i.compareTo(o);
        }
    }
    

    这里对Integer类做了一层代理。这个类的作用在于,在compareTo请求之上增加了信息的打印输出。

    在实际场景中,如果我们需要对多个方法都要做类似的处理,在每个地方都增加同样的代码,就显得有点不够优雅了。这时候,可以使用java的动态代理。
    常用的动态代理有两种实现方式:JDK和CGLIB

    JDK动态代理

    看名字就可以知道,这种动态代理是JDK本身就支持的,需要借助java.lang.reflect下的接口来实现。

    创建代理对象

    要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。

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

    这个方法有三个参数:

    1. 类加载器。作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。
    2. Class对象数组,每个元素都是需要实现的接口。
    3. 调用处理器。

    调用处理器

    调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只有一个方法:

    /**
     * @param   proxy the proxy instance that the method was invoked on
     */
    Object invoke(Object proxy, Method method, Object[] args)
    

    动态代理类,需要实现此接口,在接口方法的实现里做代理逻辑的处理。

    创建动态代理

    创建一个动态代理对象的工作如下:

    1. 获取 RealSubject上的所有接口列表;
    2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
    3. 根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;
      4 将对应的字节码转换为对应的class 对象;
    4. 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;
    5. Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象。
      接下来我们看一段代码实现:
    class TraceHandler implements InvocationHandler {
        private Object target;
        public TraceHandler(Object t) {
            target = t;
        }
        public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
        {
            // print implicit argument
            System.out.print(target);
            // print method name
            System.out.print("." + m.getName() + "(");
            // print explicit arguments
            if (args != null)
            {
                for (int i = 0; i < args.length; i++) {
                    System.out.print(args[i]);
                    if (i < args.length - 1) System.out.print(", ");
                }
            }
            System.out.println(")");
            // invoke actual method
            return m.invoke(target, args);
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            Integer value = 10;
            // 1.获取对应的ClassLoader
            ClassLoader classLoader = value.getClass().getClassLoader();
            // 2.获取Integer 所实现的所有接口
            Class[] interfaces = value.getClass().getInterfaces();
            // 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用
            InvocationHandler handler = new TraceHandler(value);
            /*
              4.根据上面提供的信息,创建代理对象 在这个过程中,
                     a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
                     b.然后根据相应的字节码转换成对应的class,
                     c.然后调用newInstance()创建实例
             */
            Comparable proxy = (Comparable) Proxy.newProxyInstance(classLoader, interfaces, handler);
            proxy.compareTo(11);
        }
    }
    /* output:
    compareTo
    10.compareTo(11)
     */
    

    那如何TraceHandler有什么用呢?
    利用TraceHandler可以打印出二分查找的查找顺序:

    Object[] elements = new Object[1000];
    // fill elements with proxies for the integers 1 . . . 1000
    for (int i = 0; i < elements.length; i++) {
        Integer value = i + 1;
        elements[i] = Proxy.newProxyInstance(..); // proxy for value;
    }
    // construct a random integer
    Integer key = new Random().nextInt(elements.length) + 1;
    // search for the key
    int result = Arrays.binarySearch(elements, key);
    // print match if found
    if (result >= 0)
        System.out.println(elements[result]);
    /* output:
    500.compareTo(288)
    250.compareTo(288)
    375.compareTo(288)
    312.compareTo(288)
    281.compareTo(288)
    296.compareTo(288)
    288.compareTo(288)
    288.toString()
     */
    

    为何toString也会被代理

    细心的读者会发现,上面的程序执行最后有一行

    288.toString()
    

    为什么toString方法也会被代理呢,我们代理的接口里,并没有声明toString的方法呀。

    这里有两点我们需要知道:

    • 所有的代理类都扩展于Proxy类;
    • 所有的代理类都覆盖了Object类中的方法toString、equals和hashCode;
      针对上面的第二点,所有对代理类的toString、equals和hashCode的方法调用,都会请求到invoke代理实现上去。所以就出现toString方法也被代理的情况了。

    参考资料:
    《Java核心技术 卷I》
    Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
    lombok的使用和原理
    Lombok原理分析与功能实现

    相关文章

      网友评论

        本文标题:java的动态代理

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