代理模式

代理模式简介

代理模式是一种结构性设计模式。用于无法直接控制和访问目标对象时,通过给目标对象提供一个代理对象,通过代理对象来间接操作目标对象。

代理模式的应用场景很多,比如Spring AOP、RPC远程调用、服务器反向代理等,通常用来不改变目标类的情况下,对目标对象进行保护或拓展。

代理模式的分类

代理模式按照使用场景进行分类,可分为以下几类:

  1. 远程代理。
  2. 虚拟代理。
  3. 保护代理。
  4. Cache代理。
  5. 同步化代理。
  6. 智能引用代理。

由于各种编程语言的语言特性不尽相同,在不同的语言中代理模式的实现也有所不同。由于在Java可以在运行时在JVM中创建新的字节码,所以可以根据代理类字节码创建的时机,将代理模式分为静态代理动态代理,静态代理是指在编译期就生成代理类,动态代理是指在运行期生成代理类。其中,动态代理又由生成字节码方式的不同分为JDK动态代理CGLib动态代理

静态代理

Java静态代理与动态代理 - 图1

静态代理实现

Subject是对代理行为的抽象,是代理类和目标类的公共接口。

  1. public interface Subject {
  2. void request();
  3. }

RealSubject是目标类,这里实现真正的doSomeThing的行为

  1. public class RealSubject implements Subject {
  2. @Override
  3. public void request() {
  4. System.out.println("Do something");
  5. }
  6. }

Proxy是代理类,对目标类的行为进行封装和增强。

  1. public class Proxy implements Subject {
  2. private Subject target; // 被代理的对象
  3. Proxy(Subject target) {
  4. this.target = target;
  5. }
  6. @Override
  7. public void request() {
  8. before(); // 对目标类增强的行为
  9. target.request(); // 调用目标对象的方法
  10. after(); // 对目标类增强的行为
  11. }
  12. private void before() {
  13. System.out.println("Before ...");
  14. }
  15. private void after() {
  16. System.out.println("After ...");
  17. }
  18. }

在客户端类中使用代理对象来替代目标对象

  1. public class Client {
  2. public static void main(String[] args) {
  3. RealSubject realSubject = new RealSubject(); // 目标对象
  4. Proxy proxy = new Proxy(realSubject); // 代理对象
  5. proxy.request(); // 通过代理对象去操作目标对象
  6. }
  7. }

运行结果

  1. Before ...
  2. Do something
  3. After ...

静态代理优缺点

优点

静态代理是代理模式实现方式的一种,所以它拥有代理模式的优点,在不改变目标类的情况下对目标类进行保护和增强。

缺点

  1. 当需要代理的行为要改变时,目标类和代理类都有进行修改,不宜维护。
  2. 一个代理类只能为一个接口服务,程序开发过程中会出现很多代理类。
  3. 当需要对大量行为进行同样的扩展的时候(比如在所有的目标对象方法中都要添加同样的after和before),需要在代理类中为每个行为写很多相同的代码。

JDK动态代理

在JDK中,提供了Proxy.newProxyInstance()方法来生成代理类,使用方法如下。

JDK动态的使用

代理接口

  1. public interface Subject {
  2. void request();
  3. }

RealSubject是目标类,这里实现真正的doSomeThing的行为

  1. public class RealSubject implements Subject {
  2. @Override
  3. public void request() {
  4. System.out.println("Do something");
  5. }
  6. }

SubjectInvocationHandler
getProxy()函数中通过Proxy.newProxyInstance()方法返回通过反射生成的代理类的对象,Proxy.newProxyInstance()方法的三个参数含义如下。

  • ClassLoader loader:代理类的类加载器
  • Class<?>[] interfaces:代理类需要实现的接口
  • InvocationHandler handler:代理对象方法调用的实际处理者,所有接口中的方法以及hashCode()、equals()、toString()调用时都会调用handler的invoke()方法。

    1. public class SubjectInvocationHandler implements InvocationHandler {
    2. // 目标对象
    3. private Subject target;
    4. public SubjectInvocationHandler(Subject target) {
    5. this.target = target;
    6. }
    7. public Subject getProxy() {
    8. // 通过反射生成代理类的代理对象。
    9. return (Subject) Proxy.newProxyInstance(
    10. // 指定生成的代理类的类加载器
    11. Thread.currentThread().getContextClassLoader(),
    12. // 这是获取target的接口,也可以直接传入接口,比如这里传入new Class[]{Subject.class}
    13. this.target.getClass().getInterfaces(),
    14. //要绑定的InvocationHandler
    15. this);
    16. }
    17. @Override
    18. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    19. before();
    20. Object result = method.invoke(target, args); // 调用目标对象的方法
    21. after();
    22. return result; // 返回目标对象方法的返回值
    23. }
    24. private void before() {
    25. System.out.println("Before ...");
    26. }
    27. private void after() {
    28. System.out.println("After ...");
    29. }
    30. }
  1. public class Client {
  2. public static void main(String[] args) {
  3. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  4. RealSubject realSubject = new RealSubject(); // 目标对象
  5. SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);
  6. Subject proxy = handler.getProxy(); // 真正的代理对象
  7. proxy.request(); // 通过代理对象去操作目标对象
  8. }
  9. }

JDK动态代理的原理

JDK动态代理会生成一个java.lang.reflect.Proxy类的子类,然后由指定的类加载器加载到方法区中,这个子类名为”com.sun.proxy.$ProxyN“,并实现了目标接口的方法,并在接口方法的函数体中通过反射去调用Object invoke(Object proxy, Method method, Object[] args) 方法,这样,代理接口中所有方法以及Object中的hashCode()、equals()、toString()调用时都会进入到 invoke(Object proxy, Method method, Object[] args)方法中。

具体原理解析见JDK动态代理源码分析

JDK动态代理的使用限制

由于JDK动态代理的实现是运行时在JVM中生成一个名为com.sun.proxy.$ProxyN的Class,这个Class继承了java.lang.reflect.Proxy类,而Java中只能单继承,所以JDK动态代理必须将代理的行为定义在一个或多个接口中,如果目标类没有实现接口则无法使用JDK动态代理。

其次,由于JDK动态代理的实现是在com.sun.proxy.$ProxyN实现的接口中通过反射调用InvocationHandler的invoke方法,所以会存在性能的问题。

CGLib动态代理

CGLib动态代理的使用

目标类RealSubject

  1. public class RealSubject implements Subject {
  2. @Override
  3. public void request() {
  4. System.out.println("Do something");
  5. }
  6. }

方法拦截器类,继承MethodInterceptor并实现intercept方法,在intercept中进行拦截。
intercept的参数如下。

  • Object object:代理的目标对象
  • Method method:拦截的方法
  • Object[] objects:参数参数列表
  • MethodProxy methodProxy:方法代理

    1. public class Interceptor implements MethodInterceptor {
    2. @Override
    3. public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    4. before();
    5. // 这里使用方法代理的invokeSuper方法调用目标类的方法,目标类是代理类的子类。
    6. // 使用invoke是调用代理类的方法,会造成死循环。
    7. Object result = methodProxy.invokeSuper(object, objects);
    8. after();
    9. return result; // 返回代理后的结果
    10. }
    11. private void before() {
    12. System.out.println("Before ...");
    13. }
    14. private void after() {
    15. System.out.println("After ...");
    16. }
    17. }

    客户端类

    1. public class Client {
    2. public static void main(String[] args) {
    3. Enhancer enhancer = new Enhancer();
    4. enhancer.setSuperclass(RealSubject.class); // 设置超类,cglib是通过继承来实现的
    5. enhancer.setCallback(new Interceptor());
    6. RealSubject realSubject = (RealSubject)enhancer.create(); // 创建代理类并返回一个代理类对象
    7. realSubject.request(); // 通过代理类调用方法
    8. }
    9. }

    CGLib动态代理的原理

    CGLib动态代理和JDK动态代理都是在代理类方法中进行拦截,不过二者在动态生成代理类的方法不同,JDK通过创建一个Proxy类的子类并实现代理接口的方法来生成代理类,CGLib通过ASM生成目标类的子类(子类重写目标类的所有非final方法)来生成代理类。

CGLib动态代理的使用限制

  • 由于Cglib是通过生成目标类的子类来实现代理类,所以Cglib无法对声明为final的目标类进行代理。
  • CGLib在创建代理对象时花费的时间比JDK多很多,对于需要频繁创建对象的情况,使用CGLib代价会比较大。

    三种代理方式的比较

  • 代理类生成时间上来看,静态代理的代理类在编译期就生成了,JDK和CGLib在运行时生成,前者不需要消耗JVM运行时间,但后者具有动态性,更加灵活。

  • 代理类生成方式上来看,静态代理的代理类人为实现(可接口,可继承)生成代理类,JDK代理通过创建一个Proxy类的子类并实现代理接口的方法来生成代理类,CGLib代理通过ASM生成目标类的子类来生成代理类。
  • 使用限制上来看,静态代理和CGLib对目标类没有要求(可接口,可为类),JDK的目标类需要额外的接口。
  • 性能消耗上来看,静态代理的带运行时没有额外的性能消耗,JDK动态代理在方法调用时(反射)性能消耗较大,CGLib在生成对象时性能消耗较大。