054、反射之动态代理

Par @Martin dans le
Tags :

代理模式

代理模式是 GOF 的23种设计模式之一 (结构型模式), 通过引入一个新的对象, 来实现对真实对象的操作或者将新的对象作为真实对象的一个替身, 即代理对象. 它可以在客户端和目标对象之间起到中介的作用, 并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务.

根据代理类的生成时间不同可以将代理分为静态代理和动态代理两种.

静态代理

由程序员创建或工具生成代理类的源码, 再编译代理类. 所谓静态也就是在程序运行前就已经存在代理类的字节码文件, 代理类和委托类的关系在运行前就确定了.

缺点:

  • 代理对象的一个接口只服务于一种类型的对象, 如果要代理的方法很多, 势必要为每一种方法都进行代理, 静态代理在程序规模稍大时就无法胜任了
  • 如果接口增加一个方法, 除了所有实现类需要实现这个方法外, 所有代理类也需要实现此方法. 增加了代码维护的复杂度

动态代理

动态代理类的源码是在程序运行期间由 JVM 根据反射等机制动态的生成, 所以不存在代理类的字节码文件. 代理类和委托类的关系是在程序运行时确定.

优点:

动态代理与静态代理相比较, 最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理 (InvocationHandler.invoke). 这样, 在接口方法数量比较多的时候, 我们可以进行灵活处理, 而不需要像静态代理那样每一个方法进行中转.

缺点:

动态代理解决了代理代理的缺点, 但它始终无法摆脱仅支持 interface 代理的桎梏; 但是我们有更强大的代理 -- cglib, 以后再介绍这个.

动态代理实例

相关 API

1) java.lang.reflect.Proxy

这是 Java 动态代理机制生成的所有动态代理类的父类, 它提供了一组静态方法来为一组接口动态地生成代理类及其对象.

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)

// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)

// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)

// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)


2) java.lang.reflect.InvocationHandler

这是调用处理器接口, 它自定义了一个 invoke 方法, 用于集中处理在动态代理类对象上的方法调用, 通常在该方法中实现对委托类的代理访问. 每次生成动态代理类对象时都要指定一个对应的调用处理器对象.

// 该方法负责集中处理动态代理类上的所有方法调用. 第一个参数既是代理类实例, 第二个参数是被调用的方法对象
// 第三个方法是调用参数. 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
Object invoke(Object proxy, Method method, Object[] args)


3) java.lang.ClassLoader

这是类装载器类, 负责将类的字节码装载到 Java 虚拟机 (JVM) 中并为其定义类对象, 然后该类才能被使用. Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用, 它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中.

每次生成动态代理类对象时都需要指定一个类装载器对象.

实现步骤

  • 实现 InvocationHandler 接口创建自己的调用处理器
  • 给 Proxy 类提供 ClassLoader 和代理接口类型数组创建动态代理类
  • 以调用处理器类型为参数, 利用反射机制得到动态代理类的构造函数
  • 以调用处理器对象为参数, 利用动态代理类的构造函数创建动态代理类对象
// InvocationHandlerImpl 实现了 InvocationHandler 接口, 并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用, 用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..);

// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });

// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });

// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });


Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装, 简化了动态代理对象的获取过程.

// InvocationHandlerImpl 实现了 InvocationHandler 接口, 并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..);

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);


示例

创建自己的调用处理器

/**
 * 动态代理类对应的调用处理程序类
 */
public class SubjectInvocationHandler implements InvocationHandler {

 //代理类持有一个委托类的对象引用
 private Object delegate;

 public SubjectInvocationHandler(Object delegate) {
  this.delegate = delegate;
 }

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  long stime = System.currentTimeMillis();
  //利用反射机制将请求分派给委托类处理. Method的invoke返回Object对象作为方法执行结果.
  //因为示例程序没有返回值, 所以这里忽略了返回值处理
  method.invoke(delegate, args);
  long ftime = System.currentTimeMillis();
  System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");

  return null;
 }
}


生成动态代理对象的工厂, 工厂方法列出了如何生成动态代理类对象的步骤.

/**
 * 生成动态代理对象的工厂.
 */
public class DynProxyFactory {
 //客户类调用此工厂方法获得代理对象.
 //对客户类来说, 其并不知道返回的是代理类对象还是委托类对象.
 public static Subject getInstance(){
  Subject delegate = new RealSubject();
  InvocationHandler handler = new SubjectInvocationHandler(delegate);
  Subject proxy = null;
  proxy = (Subject)Proxy.newProxyInstance(
    delegate.getClass().getClassLoader(),
    delegate.getClass().getInterfaces(),
    handler);
  return proxy;
 }
}


动态代理客户类

public class Client {

 public static void main(String[] args) {

  Subject proxy = DynProxyFactory.getInstance();
  proxy.dealTask("DBQueryTask");
 }

}