Java高级架构Java开发那些事程序员

java的动态代理

2018-03-16  本文已影响25人  小小浪把_Dont_know拍

代理模式

在讲动态代理之前,需要了解什么是代理模式,经典的代理模式的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的方法呀。

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

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

上一篇下一篇

猜你喜欢

热点阅读