意图

为其他对象提供一种代理,以控制这个对象的访问。

适用性

  1. 远程代理(Remote Proxy)
    为一个对象在不同的地址空间提供局部代表。
  2. 虚代理(Virtual Proxy)
    根据需要创建开销很大的对象。
  3. 保护代理(Protection Proxy)
    控制对原始对象的访问。适用于对象应该有不同访问权限的时候。
  4. 智能指引
    取代简单的指针,在访问对象时执行了一些附加操作。例如
    • 对指向实际对象的引用计数,当这个对象没有引用时候,可以自动释放它。
    • 第一次引用一个持久对象时候,将它装入内存。
    • 在访问一个实例对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

结构

代理模式.svg

参与者

  1. Subject
    普通对象RealSubject和代理对象Proxy的抽象,这样就可以在任何使用普通对象的地方替换为代理对象。
  2. RealSubject
    普通对象定义。
  3. Proxy
    • 保存一个普通对象的引用,使得可以访问实体。
    • 实现Subject接口,即可用代理来替代普通对象。
    • 控制对实体的存取,并可能负责创建和删除它。

其他功能依赖于代理的类型:

  • Remote Proxy
    负责对请求及其参数进行编码,向不同地址空间的实体发送已编码的请求。
  • Virtual Proxy
    缓存实体的附加信息,以便延迟访问
  • Protection Proxy
    检查调用者是否有实现一个请求所需的访问权限。

实现

静态代理

  1. 要求
    被代理对象和代理对象实现同一接口。
  2. 优势
    不修改目标对象的前提下,扩展功能。
  3. 不足
    1. 会产生过多的代理类。
    2. 不易于维护。一旦接口增加方法,目标对象和所有代理对象需要同步修改。

动态代理(JDK代理 / 接口代理)

  • 利用了JDK的API,在运行时动态在内存中创建代理对象。
  • 与静态代理的区别是:
    • 静态代理在编译时就已经实现,编译完成形成.class文件;
    • 动态代理编译完成时没有class文件,而是运行时生成字节码加载到jvm中。
  • 特点:
    动态代理不需要实现接口,但要求目标对象必须实现接口(非抽象方法)。

核心类

  1. public interface InvocationHandler {
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  3. }

实现类

  1. /**
  2. * 动态代理类模拟
  3. *
  4. * @author Jinhua
  5. */
  6. public class MyInvocationHandler implements InvocationHandler {
  7. /**
  8. * 被代理对象的引用
  9. */
  10. private Object target;
  11. public MyInvocationHandler() {
  12. super();
  13. }
  14. /**
  15. * 拿到被代理对象
  16. *
  17. * @param target 被代理对象
  18. */
  19. public MyInvocationHandler(Object target) {
  20. super();
  21. this.target = target;
  22. }
  23. /**
  24. * 产生代理对象的方法
  25. *
  26. * @param proxy 代理对象
  27. * @param method 原有要增强的方法
  28. * @param args 方法的参数
  29. * @return 返回动态代理类对象
  30. * @throws Throwable 反射可能抛出的异常
  31. */
  32. @Override
  33. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  34. // 添加了代理方法
  35. if ("addPro".equals(method.getName())) {
  36. System.out.println("开启事务……");
  37. Object result = method.invoke(target, args);
  38. System.out.println("提交事务……");
  39. return result;
  40. } else { // 未添加代理方法
  41. return method.invoke(target, args);
  42. }
  43. }
  44. public static void main(String[] args) {
  45. // 普通对象产生
  46. ProductService ps = new ProductServiceImpl();
  47. InvocationHandler ih2 = new MyInvocationHandler(ps);
  48. // 产生代理对象
  49. ProductService proxy = (ProductService) Proxy.newProxyInstance(
  50. ps.getClass().getClassLoader(),
  51. ps.getClass().getInterfaces(),
  52. ih2
  53. );
  54. proxy.addPro();
  55. System.out.println("---------");
  56. proxy.delPro();
  57. }
  58. }

CgLib继承代理

第三方代码生成类库,运行时在内存中动态生成子类对象,从而实现对目标对象的功能扩展。

底层通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构及class文件格式和指令集都很熟悉。

特点

  • 无需实现接口,达到代理类无入侵。想代理的对象没有接口实现类,即可使用CgLib实现。

实现

  1. /**
  2. * CgLib继承代理
  3. *
  4. * @author Jinhua
  5. * @date 2021/3/13下午9:58
  6. */
  7. public class ProxyFactory implements MethodInterceptor {
  8. /**
  9. * 目标对象
  10. */
  11. private final Object target;
  12. public ProxyFactory(Object target) {
  13. this.target = target;
  14. }
  15. public Object getProxyInstance() {
  16. // 工具类
  17. Enhancer en = new Enhancer();
  18. // 设置父类
  19. en.setSuperclass(target.getClass());
  20. // 设置回调函数
  21. en.setCallback(this);
  22. // 产生代理对象
  23. return en.create();
  24. }
  25. @Override
  26. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  27. // 添加了代理方法
  28. if ("delPro".equals(method.getName())) {
  29. System.out.println("开启事务……");
  30. Object result = method.invoke(target, objects);
  31. System.out.println("提交事务……");
  32. return result;
  33. } else { // 未添加代理方法
  34. return method.invoke(target, objects);
  35. }
  36. }
  37. public static void main(String[] args) {
  38. // 普通对象产生
  39. ProductService ps = new ProductServiceImpl();
  40. // 代理对象的产生
  41. ProductService proxy = (ProductService) new ProxyFactory(ps).getProxyInstance();
  42. System.out.println(proxy.getClass());
  43. proxy.addPro();
  44. System.out.println("---------");
  45. proxy.delPro();
  46. }
  47. }

对比分析

  1. 静态代理
    • 实现较简单。但只能为一个目标类的对象服务,如果目标类过多,会产生很多代理类。
    • 静态代理编译时产生class字节码文件,直接使用效率高。
  2. JDK动态代理
    • 需要目标对象实现业务接口。代理类只需实现InvocationHandler接口。
    • 通过反射来代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
  3. CgLib代理
    • 无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题。但CgLib需要继承目标对象,需要重写方法,所以目标类不能为终类