0.参考资料


浅谈JDK动态代理


1.概述

  • 为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。—设计模式》GoF
  • 核心:
  • 分类

    • 静态代理
    • 动态代理

      • JDK代理 (接口代理)
      • Cglib代理 (子类代理)

        • 在运行期扩展Java类/实现Java接口. 在内存中动态创建对象, 而不需要目标类实现接口

          1.1动机

      • 代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求。

      • 在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要额外扩展—安全控制/日志打印等,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦。
      • 如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式。

        1.2结构

      • 6.代理模式(Proxy)-结构型 - 图1

      • L~WGER1FG~3F`E5W4(V9XIH.png

        2.要点总结

    1. “增加一层间接层”是软件系统中对许多复杂问题的一-种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题, 作为间接层的proxy对象便是解决这一问题的常用手段。
    2. 具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制, 如copy-on-write技术, 也可能对组件模块提供抽象代理层,在架构层次对对象做proxy。
    3. Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。

      3.案例

      项目

      • 某项目中存在一个Calculator类,代表一个计算器,它可以进行加减乘除操作: ```cpp public class Calculator {

      // 加 public int add(int a, int b) { int result = a + b; return result; }

      // 减 public int subtract(int a, int b) { int result = a - b; return result; }

      // 乘法、除法… }

      1. <a name="AMnVx"></a>
      2. #### 需求
      3. - 在每个方法执行前后打印日志
      4. <a name="rnMeK"></a>
      5. #### 方案
      6. - 硬编码
      7. <a name="VF9Mh"></a>
      8. #### 代码
      9. - 直接修改Calculator类
      10. ```cpp
      11. public class Calculator {
      12. // 加
      13. public int add(int a, int b) {
      14. System.out.println("add方法开始...");
      15. int result = a + b;
      16. System.out.println("add方法结束...");
      17. return result;
      18. }
      19. // 减
      20. public int subtract(int a, int b) {
      21. System.out.println("subtract方法开始...");
      22. int result = a - b;
      23. System.out.println("subtract方法结束...");
      24. return result;
      25. }
      26. // 乘法、除法...
      27. }

      分析

    • 直接修改源程序,不符合开闭原则,即好的程序设计应该对扩展开放,对修改关闭
    • 如果Calculator类内部有几十个、上百个方法,修改量太大
    • 存在重复代码(都是在核心代码前后打印日志)
    • 日志打印硬编码在代理类中,不利于后期维护
      • 比如你花了一上午终于写完了,组长告诉你这个功能不做了,于是你又要打开Calculator花十分钟删除日志打印的代码(或回滚分支)

        4.使用模式


4.1静态代理

  1. - 编写一个代理类,实现目标对象相同的接口/父类,并在内部维护一个目标对象的引用。通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。

方案

  1. - Calculator抽取为接口
  2. - 创建目标类CalculatorImpl实现Calculator
  3. - 创建代理类CalculatorProxy实现Calculator

类图

  1. - ![A)T]IL@9II(W_BG{9`AXRCN.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628576592911-71355d30-b746-44fb-9f9e-1b50e142b988.png#clientId=u4d152ae3-a83c-4&from=paste&height=308&id=u96aadb0d&margin=%5Bobject%20Object%5D&name=A%29T%5DIL%409II%28W_BG%7B9%60AXRCN.png&originHeight=616&originWidth=1085&originalType=binary&ratio=1&size=46085&status=done&style=none&taskId=u60bd8ac1-c043-493a-8516-5aa80b16d7d&width=543)

代码

  1. - 抽取接口
  1. /**
  2. * Calculator接口
  3. */
  4. public interface Calculator {
  5. int add(int a, int b);
  6. int subtract(int a, int b);
  7. }
  1. - 目标类(实现接口)
  1. /**
  2. * 目标类,实现Calculator接口(如果一开始就面向接口编程,其实是不存在这一步的,CalculatorImpl原本就实现Calculator接口)
  3. */
  4. public class CalculatorImpl implements Calculator {
  5. // 加
  6. public int add(int a, int b) {
  7. int result = a + b;
  8. return result;
  9. }
  10. // 减
  11. public int subtract(int a, int b) {
  12. int result = a - b;
  13. return result;
  14. }
  15. // 乘法、除法...
  16. }
  1. - 新增代理类(实现接口)
  1. /**
  2. * 静态代理类,实现Calculator接口
  3. */
  4. public class CalculatorProxy implements Calculator {
  5. // 代理对象内部维护一个目标对象引用
  6. private Calculator target;
  7. // 通过构造方法,传入目标对象
  8. public CalculatorProxy(Calculator target) {
  9. this.target = target;
  10. }
  11. // 调用目标对象的add,并在前后打印日志
  12. @Override
  13. public int add(int a, int b) {
  14. System.out.println("add方法开始...");
  15. int result = target.add(a, b);
  16. System.out.println("add方法结束...");
  17. return result;
  18. }
  19. // 调用目标对象的subtract,并在前后打印日志
  20. @Override
  21. public int subtract(int a, int b) {
  22. System.out.println("subtract方法开始...");
  23. int result = target.subtract(a, b);
  24. System.out.println("subtract方法结束...");
  25. return result;
  26. }
  27. // 乘法、除法...
  28. }
  1. - 测试
  1. public class Test {
  2. public static void main(String[] args) {
  3. // 把目标对象通过构造器塞入代理对象
  4. Calculator calculator = new CalculatorProxy(new CalculatorImpl());
  5. // 代理对象调用目标对象方法完成计算,并在前后打印日志
  6. calculator.add(1, 2);
  7. calculator.subtract(2, 1);
  8. }
  9. }
  1. add方法开始...
  2. add方法结束...
  3. subtract方法开始...
  4. subtract方法结束...

分析

  • 静态代理的优点:可以在不修改目标对象的前提下,对目标对象进行功能的扩展和拦截。但是它也仅仅解决了上一种方案4大缺点中的第1、4两点:硬编码的缺点. 还剩下 代理类中重复编码的问题.

    1. - 直接修改源程序,不符合开闭原则,即好的程序设计应该对扩展开放,对修改关闭(✅,如果一开始就面向接口编程,这一步其实是不需要的)
    2. - 如果Calculator类内部有几十个、上百个方法,修改量太大(❎,目标类有多少个方法,代理类就要重写多少个方法)
    3. - 存在重复代码(都是在核心代码前后打印日志)(❎,代理类中的日志代码是重复的)
    4. - 日志打印硬编码在代理类中,不利于后期维护:比如你花了一上午终于写完了,组长告诉你这个功能不做了(✅,别用代理类就好了)

    静态代理的问题

  • 上面的代码中,为了给目标类做日志增强,我们编写了代理类,而且准备了一个构造器接收目标对象。代理代理对象构造器的参数类型是Calculator,这意味着它只能接受Calculator的实现类对象,亦即我们写的代理类CalculatorProxy只能给Calculator做代理,它们绑定死了!

    1. - 如果现在我们系统需要全面改造,要给其他类也添加日志打印功能,就得为其他几百个接口都**各自**写一份代理类...
    2. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618023158202-a38d1824-93d0-4c31-a285-61fd9b9b8937.png#from=url&id=IyoHN&margin=%5Bobject%20Object%5D&originHeight=107&originWidth=704&originalType=binary&ratio=1&status=done&style=none)
  • 自己手动写一个类并实现接口实在太麻烦了。仔细一想,我们其实想要的并不是代理类,而是代理对象!有没有一个方法,我传入接口+增强的代码(比如打印日志),它就给我自动返回代理对象呢?这样就能省去编写代理类这个无用的“中介”了

4.2动态代理

需求分析

  1. - 为什么打印日志的代码无法复用呢?因为打印日志的代码被直接硬编码在代理类中,和代理类是耦合的!
  2. - 分析前两版本, 从修改原代码到引入静态代理,**其实就是趋向解耦的过程:**
  3. - 原本我们把代码直接写在目标类中,**日志代码和目标类耦合**了,所以一旦需求被撤销,你又要去修改目标类
  4. - 静态代理则把日志代码**抽取**出来,放在代理类中,解决了增强代码和目标类的耦合,但又造成了**增强代码和代理类的耦合**,所以**代理类无法复用**,有多少个目标类就要写多少个代理类

现阶段待解决的问题

  1. - 动态代理如果要优于静态代理,至少需要解决两个问题:
  2. - **①自动生成代理对象(实例)**
  3. - ②将增强代码与代理类(代理对象)解耦,从而达到代码复用
  4. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618655444343-b7cf5372-f579-400b-9087-2cd2757bcdb9.png#from=url&height=308&id=HJM1r&margin=%5Bobject%20Object%5D&originHeight=616&originWidth=1498&originalType=binary&ratio=1&status=done&style=none&width=749)

问题思路

1.如何不写代理类
  1. - [类加载时到生成对象过程](https://www.yuque.com/ryze_java/javase/hebaus#AFRUd). 可以看出,要创建一个实例,最关键的就是**得到对应的Class对象.**
  2. - 回到我们之前的问题:如何不写代理类,直接得到代理对象。按照[类加载时到生成对象过程](https://www.yuque.com/ryze_java/javase/hebaus#AFRUd),代理类和实例对象之间其实还隔着一个Class对象。**如果能得到Class对象,就能生成实例。**所以,现在的问题又变成:**如何不写代理类,直接得到Class对象。**
  3. - 其中: 我们得知Class对象只能由JVM创建。虽然不能new,但Java还是提供了其他方式让我们得到Class对象,底层会告诉JVM帮我们创建:
  4. 1. Class.forName(xxx):Class<Person> clazz = Class.forName("com.bravo.Person");
  5. 1. xxx.classClass<Person> clazz = Person.class;
  6. 1. xxx.getClass():Class<Person> clazz = person.getClass();
  7. - 问题: **这三种方式都需要先有类,但我们不想编写代理类**!

2.从接口寻求突破口
  1. - 代理类或者代理对象重要吗?它其实只是个空壳,最重要的其实是 增强代码 + 目标对象。我们对代理对象的要求很低,只需要与目标对象拥有相同的方法即可。如此一来,别人调用proxy.add()得到的效果和调用target.add()是一样的,甚至因为两者都实现了相同接口,用接口类型接收后,calculator.add()根本分不出是代理还是原对象。
  2. - 所以本质上,代理对象只要有方法申明即可,甚至不需要方法体,或者只要一个空的方法体即可,反正我们会把目标对象返回去。
  3. - 有两个途径:
  4. - 目标类本身. CGLib动态代理
  5. - 目标类实现的接口. JDK动态代理
  6. - 这两个思路造就了两种不同的代理机制,本文重点介绍实现接口的JDK动态代理。

3.验证从接口获取方法信息
  1. - 测试类
  1. // 在静态代理类的同环境下测试
  2. public class ProxyTest {
  3. public static void main(String[] args) {
  4. /**
  5. * Calculator接口的Class对象
  6. * 得到Class对象的三种方式:
  7. * 1.Class.forName(xxx)
  8. * 2.xxx.class
  9. * 3.xxx.getClass()
  10. * 注意,这并不是我们new了一个Class对象,而是让虚拟机加载并创建Class对象
  11. */
  12. /**
  13. * Calculator接口的Class对象
  14. */
  15. Class<Calculator> calculatorClazz = Calculator.class;
  16. //Calculator接口的构造器信息
  17. Constructor<?>[] calculatorClazzConstructors = calculatorClazz.getConstructors();
  18. //Calculator接口的方法信息
  19. Method[] calculatorClazzMethods = calculatorClazz.getMethods();
  20. //打印
  21. System.out.println("------接口Class的构造器信息------");
  22. printClassInfo(calculatorClazzConstructors);
  23. System.out.println("\n");
  24. System.out.println("------接口Class的方法信息------");
  25. printClassInfo(calculatorClazzMethods);
  26. System.out.println("\n");
  27. /**
  28. * Calculator实现类的Class对象
  29. */
  30. Class<CalculatorImpl> calculatorImplClazz = CalculatorImpl.class;
  31. //Calculator实现类的构造器信息
  32. Constructor<?>[] calculatorImplClazzConstructors = calculatorImplClazz.getConstructors();
  33. //Calculator实现类的方法信息
  34. Method[] calculatorImplClazzMethods = calculatorImplClazz.getMethods();
  35. //打印
  36. System.out.println("------实现类Class的构造器信息------");
  37. printClassInfo(calculatorImplClazzConstructors);
  38. System.out.println("\n");
  39. System.out.println("------实现类Class的方法信息------");
  40. printClassInfo(calculatorImplClazzMethods);
  41. }
  42. /**
  43. * @description: 参数类型Executable是Mehode和Constructor共同的父类
  44. * @param: targets
  45. * @return: void
  46. * @date: 2021/8/10 16:57
  47. */
  48. public static void printClassInfo(Executable[] targets) {
  49. for (Executable target : targets) {
  50. StringBuilder stringBuilder = new StringBuilder();
  51. String targetName = target.getName();
  52. stringBuilder.append(targetName);
  53. stringBuilder.append("(");
  54. Class<?>[] parameterTypes = target.getParameterTypes();
  55. for (Class<?> parameterType : parameterTypes) {
  56. String parameterTypeName = parameterType.getName();
  57. stringBuilder.append(parameterTypeName).append(",");
  58. }
  59. if (parameterTypes.length != 0 && parameterTypes != null){
  60. stringBuilder.deleteCharAt(stringBuilder.length()-1);
  61. }
  62. stringBuilder.append(")");
  63. System.out.println(stringBuilder);
  64. }
  65. /*
  66. for (Executable target : targets) {
  67. // 构造器/方法名称
  68. String name = target.getName();
  69. StringBuilder sBuilder = new StringBuilder(name);
  70. // 拼接左括号
  71. sBuilder.append('(');
  72. Class<?>[] clazzParams = target.getParameterTypes();
  73. // 拼接参数
  74. for (Class<?> clazzParam : clazzParams) {
  75. sBuilder.append(clazzParam.getName()).append(',');
  76. }
  77. //删除最后一个参数的逗号
  78. if (clazzParams.length != 0) {
  79. sBuilder.deleteCharAt(sBuilder.length() - 1);
  80. }
  81. //拼接右括号
  82. sBuilder.append(')');
  83. //打印 构造器/方法
  84. System.out.println(sBuilder.toString());
  85. }*/
  86. }
  87. }
  1. - 结果
  1. ------接口Class的构造器信息------
  2. ------接口Class的方法信息------
  3. add(int,int)
  4. subtract(int,int)
  5. ------实现类Class的构造器信息------
  6. com.ryze.newProxy1.CalculatorImpl()
  7. ------实现类Class的方法信息------
  8. add(int,int)
  9. subtract(int,int)
  10. wait()
  11. wait(long,int)
  12. wait(long)
  13. equals(java.lang.Object)
  14. toString()
  15. hashCode()
  16. getClass()
  17. notify()
  18. notifyAll()
  1. - 得到以下结论:
  2. - **接口Class对象没有构造方法**,所以Calculator接口不能直接new对象
  3. - 实现类Class对象有构造方法,所以CalculatorImpl实现类可以new对象
  4. - 接口Class对象有两个方法add()、subtract()
  5. - 实现类Class对象除了add()、subtract(),还有从Object继承的方法(可用其它方法返回子类特有的所有方法)
  6. - 也就是说,接口Class的对象和实现类的Class对象除了构造器,其他信息基本相似
  7. - 至此,我们至少知道从接口获取方法信息是可能的!接下来的努力方向就是:**怎么根据一个接口得到代理对象。**

引出JDK动态代理

  1. - 通过刚才的实验,我们不仅知道了接口确实包含我们所需要的方法信息,还知道了接口为什么不能直接new对象:接口缺少构造器信息。那么,是否存在一种机制,能给接口安装上构造器呢?或者,不改变接口本身,直接拷贝接口的信息到另一个Class,然后给那个Class装上构造器呢?
  2. - 很显然,不论是从开闭原则还是常规设计考虑,直接修改接口Class的做法相对来说不是很合理。JDK选择了后者:拷贝接口Class的信息,产生一个新的Class对象。
  3. - **也就是说,JDK动态代理的本质是:用ClassClass,即用接口Class造出一个代理类Class。**
  4. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618657069339-c705be10-c445-441a-ae68-c12b5661e48a.png#from=url&id=Y4rgv&margin=%5Bobject%20Object%5D&originHeight=197&originWidth=720&originalType=binary&ratio=1&status=done&style=none)
  5. - 具体API: java.lang.reflect.Proxy
  6. - public static Class<?> _getProxyClass_(ClassLoader loader,Class<?>... interfaces) : 返回代理类的Class对象, 并向其提供类加载器和接口信息
  7. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618552992905-25fadecc-2dcc-45ca-9c27-d30490aa66e0.png#from=url&id=DbvVY&margin=%5Bobject%20Object%5D&originHeight=501&originWidth=1262&originalType=binary&ratio=1&status=done&style=none)
  8. - 也就说,只要传入接口的Class对象,getProxyClass()方法即可返回代理Class对象,而不用实际编写代理类。即跳过了代理类的编写!
  9. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618553104750-eaf78843-86db-49cd-9996-db530026e259.png#from=url&id=PSnRK&margin=%5Bobject%20Object%5D&originHeight=191&originWidth=400&originalType=binary&ratio=1&status=done&style=none)

代码
  1. - 测试 Proxy.getProxyClass(...) : Class<?>
  1. public class ProxyTest {
  2. public static void main(String[] args) {
  3. /*
  4. * 参数1:Calculator的类加载器(当初把Calculator加载进内存的类加载器)
  5. * 参数2:代理对象需要和目标对象实现相同接口Calculator
  6. * */
  7. Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
  8. //以Calculator实现类的Class对象作对比,看看代理Class是什么类型
  9. System.out.println(CalculatorImpl.class.getName());
  10. System.out.println(calculatorProxyClazz.getName());
  11. //打印代理Class对象的构造器
  12. Constructor<?>[] constructors = calculatorProxyClazz.getConstructors();
  13. System.out.println("----构造器----");
  14. printClassInfo(constructors);
  15. System.out.println("\n");
  16. //打印代理Class对象的方法
  17. Method[] methods = calculatorProxyClazz.getMethods();
  18. System.out.println("----方法----");
  19. printClassInfo(methods);
  20. System.out.println("\n");
  21. }
  22. public static void printClassInfo(Executable[] targets) {
  23. for (Executable target : targets) {
  24. // 构造器/方法名称
  25. String name = target.getName();
  26. StringBuilder sBuilder = new StringBuilder(name);
  27. // 拼接左括号
  28. sBuilder.append('(');
  29. Class<?>[] clazzParams = target.getParameterTypes();
  30. // 拼接参数
  31. for (Class<?> clazzParam : clazzParams) {
  32. sBuilder.append(clazzParam.getName()).append(',');
  33. }
  34. //删除最后一个参数的逗号
  35. if (clazzParams.length != 0) {
  36. sBuilder.deleteCharAt(sBuilder.length() - 1);
  37. }
  38. //拼接右括号
  39. sBuilder.append(')');
  40. //打印 构造器/方法
  41. System.out.println(sBuilder.toString());
  42. }
  43. }
  44. }
  1. - 结果
  1. com.ryze.newProxy2.CalculatorImpl
  2. com.sun.proxy.$Proxy0
  3. ----构造器----
  4. com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
  5. ----方法----
  6. add(int,int)
  7. equals(java.lang.Object)
  8. toString()
  9. hashCode()
  10. subtract(int,int)
  11. isProxyClass(java.lang.Class)
  12. getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
  13. getInvocationHandler(java.lang.Object)
  14. newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
  15. wait()
  16. wait(long,int)
  17. wait(long)
  18. getClass()
  19. notify()
  20. notifyAll()
  1. - 结论
  2. - Proxy.getProxyClass()返回的Class对象是有构造器的!
  3. - 返回的Class对象是 Class<Proxy>
  4. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618553504160-8b1893f0-c451-4e4a-98ec-00c2226eb765.png#from=url&height=266&id=JGHHl&margin=%5Bobject%20Object%5D&originHeight=354&originWidth=529&originalType=binary&ratio=1&status=done&style=none&width=397)
  5. - [使用动态代理需要解决问题](#moPcL). 现在我们已经得到了代理Class,只需通过反射即可得到代理对象。

动态代理的底层逻辑

  1. - 我们已经顺利通过接口得到代理Class对象,有了代理Class对象意味着代理对象唾手可得。
  2. - [观察代理类的构造器信息](https://www.yuque.com/ryze_java/design_pattern/nzse8l?inner=buTU9), 只有一个构造器,唯一参数InvocationHandler.
  3. - com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)

代码
  1. - 简单的动态代理. 成功获取代理对象并执行目标方法
  1. /**version: 1.0
  2. * @description: 简单的动态代理(还未有具体的目标类)
  3. */
  4. public class ProxyTest {
  5. public static void main(String[] args) throws Exception {
  6. /*
  7. * 参数1:ClassLoader :传入系统类加载器
  8. * 参数2:Class<?>... :需要生成代理Class的接口Class类型
  9. * */
  10. Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
  11. // 得到唯一的有参构造 $Proxy0(InvocationHandler h),和反射的Method有点像,可以理解为得到对应的构造器执行器
  12. Constructor<?> constructor = calculatorProxyClazz.getConstructor(InvocationHandler.class);
  13. // 用构造器执行器执行构造方法,得到代理对象。构造器需要InvocationHandler入参
  14. Calculator calculatorProxyImpl = (Calculator) constructor.newInstance(new InvocationHandler() {
  15. @Override
  16. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  17. return 10086;
  18. }
  19. });
  20. // 执行代理类的同名方法
  21. System.out.println(calculatorProxyImpl.add(1, 2)); // 10086
  22. }
  23. }

InvocationHandler解析

  1. - 从实验结果看,会发现每次调用代理对象的方法,最终都会调用InvocationHandlerinvoke()方法
  2. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618658505581-168d659b-21c0-422a-97b2-cb08c8891a99.png#from=url&height=183&id=zsvWg&margin=%5Bobject%20Object%5D&originHeight=366&originWidth=1349&originalType=binary&ratio=1&status=done&style=none&width=675)
  3. - 理解:
  4. - 根据代理Class的构造器创建对象时,需要传入InvocationHandlerProxy的内部确实有个成员变量: java.lang.reflect.Proxy类源码:

:::info protected InvocationHandler h; // JDK中描述: 代理实例的调用处理器
private Proxy() { // 私有默认构造
}
protected Proxy(InvocationHandler h) {
Objects._requireNonNull
(h);
this.h = h;
} :::

  1. - 而且代理对象的每个方法内部都会调用h.invoke(). 也就是说,动态代理为了实现代理对象和增强代码的解耦,把增强代码也抽取出去了,让InvocationHandler作为它与目标对象的桥梁。
  2. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618661371916-03ff6bc1-2631-4509-bb8a-73b1979c4c69.png#from=url&id=lpuBv&margin=%5Bobject%20Object%5D&originHeight=208&originWidth=720&originalType=binary&ratio=1&status=done&style=none)

JDK动态代理最终生成的其实是Class,最终的代理对象是proxy对象,而且实现了Calculator接口。

invoke(Object proxy, Method method, Object[] args) : Object 方法解析

参数
  1. - Object proxy:代理对象本身,而不是目标对象(不要调用,会无限递归,一般不会使用)
  2. - Method method:目标类的目标方法. 方法执行器,用来执行方法(有点不好解释,Method只是一个执行器,传入目标对象就执行目标对象的方法)
  3. - Obeject[] args:方法参数

问题与解释
  1. - 问: 形参中没有具体的(被代理)目标对象.
  2. - 答: ???

动态代理的代码实现

1简单的invoke() 内部new目标对象
  1. - 代码
  1. /**version:1
  2. * @description: 一个最基本的动态代理(已经有一个基本的目标实体类)
  3. */
  4. public class ProxyTest {
  5. public static void main(String[] args) throws Exception {
  6. /*
  7. * 参数1:类加载器,随便给一个
  8. * 参数2:需要生成代理Class的接口,比如Calculator
  9. * */
  10. Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
  11. // 得到唯一的有参构造 $Proxy(InvocationHandler h),和反射的Method有点像,可以理解为得到对应的构造器执行器
  12. Constructor<?> constructor = calculatorProxyClazz.getConstructor(InvocationHandler.class);
  13. // 用构造器执行器执行构造方法,得到代理对象。构造器需要InvocationHandler入参
  14. Calculator calculatorProxyImpl = (Calculator) constructor.newInstance(new InvocationHandler() {
  15. @Override
  16. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  17. Calculator calculator = new CalculatorImpl();
  18. System.out.println(method.getName() + "() 目标类的方法开始执行...");
  19. Object result = method.invoke(calculator, args[0], args[1]);
  20. System.out.println(result);
  21. System.out.println(method.getName() + "() 目标类的方法执行结束...");
  22. return result;
  23. }
  24. });
  25. // 执行代理类的同名方法
  26. calculatorProxyImpl.add(1, 2);
  27. }
  28. }
  1. - 测试结果
  1. add() 目标类的方法开始执行...
  2. 3
  3. add() 目标类的方法执行结束...

2.抽取方法,简化操作
  1. - 代码
  1. /**version:2
  2. * 外部传入目标对象, 简化invoke(...)内部责任
  3. */
  4. public class ProxyTest {
  5. public static void main(String[] args) throws Throwable {
  6. // 后期需要一个具体的目标类, 和目标类的接口. 故如此一对象二用
  7. CalculatorImpl target = new CalculatorImpl();
  8. // 传入目标对象
  9. Calculator calculatorProxy = (Calculator) getProxy(target);
  10. // 执行代理方法
  11. calculatorProxy.add(1, 2);
  12. }
  13. /**
  14. * 传入目标对象,获取代理对象
  15. *
  16. * @param target 目标类的接口实例
  17. * @return
  18. * @throws Exception
  19. */
  20. private static Object getProxy(final Object target) throws Exception {
  21. /**
  22. 获取代理类的Class对象.
  23. 其中:
  24. 参数二需要接口数组, 因 target 是具体实现实例(毕竟不能new 接口), 故此代码.
  25. 若二参填: target.getClass(),
  26. 则报: java.lang.IllegalArgumentException: com.ryze.newProxy3.CalculatorImpl is not an interface
  27. * */
  28. Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
  29. // 获取唯一的构造器
  30. Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
  31. return constructor.newInstance(new InvocationHandler() {
  32. @Override
  33. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  34. System.out.println(method.getName() + "方法开始执行...");
  35. Object result = method.invoke(target, args);
  36. System.out.println(result);
  37. System.out.println(method.getName() + "方法执行结束...");
  38. return result;
  39. }
  40. });
  41. }
  42. }
  1. - 测试结果
  1. add方法开始执行...
  2. 3
  3. add方法执行结束...

3.解耦代理对象与增强代码
  1. - 上面的代码还有问题:虽然传入任意对象我们都可以返回增强后的代理对象,但增强代码是写死的。如果我需要的增强不是打印日志而是其他操作呢?难道重新写一个getProxy()方法吗?所以,我们应该抽取InvocationHander,将增强代码和代理对象解耦(其实重写getProxy()和抽取InvocationHander本质相同,但后者细粒度小一些)。
  2. - 代码
  1. // version :3
  2. public class ProxyTest {
  3. public static void main(String[] args) throws Throwable {
  4. // 1.得到目标对象
  5. CalculatorImpl target = new CalculatorImpl();
  6. // 2.传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)
  7. InvocationHandler logInvocationHandler = getLogInvocationHandler(target);
  8. // 3.传入目标对象+增强代码,得到代理对象
  9. Calculator calculatorProxy = (Calculator) getProxy(target, logInvocationHandler);
  10. calculatorProxy.add(1, 2);
  11. }
  12. /**
  13. * 传入目标对象+增强代码,获取代理对象
  14. *
  15. * @param target
  16. * @param handler
  17. * @return
  18. * @throws Exception
  19. */
  20. private static Object getProxy(final Object target, final InvocationHandler handler) throws Exception {
  21. // 参数1:随便找个类加载器给它 参数2:需要代理的接口
  22. Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
  23. Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
  24. return constructor.newInstance(handler);
  25. }
  26. /**
  27. * 日志增强代码
  28. *
  29. * @param target
  30. * @return
  31. */
  32. private static InvocationHandler getLogInvocationHandler(final CalculatorImpl target) {
  33. return new InvocationHandler() {
  34. @Override
  35. public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
  36. System.out.println(method.getName() + "方法开始执行...");
  37. Object result = method.invoke(target, args);
  38. System.out.println(result);
  39. System.out.println(method.getName() + "方法执行结束...");
  40. return result;
  41. }
  42. };
  43. }
  44. }
  1. - 测试结果: 正常,

4.优化代码语义
  1. - 上面的代码抽取了两个方法,仔细观察你会发现,getLogInvocationHandler(target)的target参数是必要的,但getProxt(target, invocationHandler)的target参数是没必要的:
  2. - 首先,getProxy()其实只需要知道要实现的接口是什么,就能返回该接口的代理对象不是吗?
  3. - 其次,invocationHandler已经包含目标对象
  4. - 测试代码
  5. - 代码
  1. // version: 4
  2. public class ProxyTest {
  3. public static void main(String[] args) throws Throwable {
  4. // 1.得到目标对象
  5. CalculatorImpl target = new CalculatorImpl();
  6. // 2.传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)
  7. InvocationHandler logInvocationHandler = getLogInvocationHandler(target);
  8. // 3.传入接口+增强对象(含目标对象),得到代理对象
  9. Calculator calculatorProxy = (Calculator) getProxy(
  10. logInvocationHandler, // 增强对象(包含 目标对象 + 增强代码)
  11. target.getClass().getClassLoader(), // 随便传入一个类加载器
  12. target.getClass().getInterfaces() // 需要代理的接口
  13. );
  14. calculatorProxy.add(1, 2);
  15. }
  16. /**
  17. * 传入接口+增强(已经包含了目标对象),获取代理对象
  18. *
  19. * @param handler
  20. * @param classLoader
  21. * @param interfaces
  22. * @return
  23. * @throws Exception
  24. */
  25. private static Object getProxy(final InvocationHandler handler, final ClassLoader classLoader, final Class<?>... interfaces) throws Exception {
  26. // 参数1:随便找个类加载器给它 参数2:需要代理的接口
  27. Class<?> proxyClazz = Proxy.getProxyClass(classLoader, interfaces);
  28. Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
  29. return constructor.newInstance(handler);
  30. }
  31. /**
  32. * 日志增强代码
  33. *
  34. * @param target
  35. * @return
  36. */
  37. private static InvocationHandler getLogInvocationHandler(final CalculatorImpl target) {
  38. return new InvocationHandler() {
  39. @Override
  40. public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
  41. System.out.println(method.getName() + "方法开始执行...");
  42. Object result = method.invoke(target, args);
  43. System.out.println(result);
  44. System.out.println(method.getName() + "方法执行结束...");
  45. return result;
  46. }
  47. };
  48. }
  49. }
  1. - 测试结果: 正常,

5.final版本: 更好用的API:Proxy.newProxyInstance()
  1. - 目前为止,我们学习都是Proxy.getProxyClass():
  2. - 先获得proxyClazz
  3. - 再根据proxyClazz.getConstructor()获取构造器
  4. - 最后constructor.newInstance()生成代理对象
  5. - 为了简化代码,我们把这三步封装为getProxy()方法。然而,其实JDK已经提供了一步到位的方法Proxy.newProxyInstance(),你会发现它的参数和我们上面最终封装的getProxy()是一样的:

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

  1. - 直接使用JDK封装的newProxyInstance()
  2. - 代码测试
  3. - 代码
  1. //version: final
  2. public class ProxyTest {
  3. public static void main(String[] args) throws Throwable {
  4. // 1.得到目标对象
  5. CalculatorImpl target = new CalculatorImpl();
  6. // 2.传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)
  7. InvocationHandler logInvocationHandler = getLogInvocationHandler(target);
  8. // 3.传入目标对象+增强代码,得到代理对象(直接用JDK的方法!!!)
  9. Calculator calculatorProxy = (Calculator) Proxy.newProxyInstance(
  10. target.getClass().getClassLoader(), // 随便传入一个类加载器
  11. target.getClass().getInterfaces(), // 需要代理的接口
  12. logInvocationHandler // 增强对象(包含 目标对象 + 增强代码)
  13. );
  14. calculatorProxy.add(1, 2);
  15. }
  16. /**
  17. * 日志增强代码
  18. *
  19. * @param target
  20. * @return
  21. */
  22. private static InvocationHandler getLogInvocationHandler(final CalculatorImpl target) {
  23. return new InvocationHandler() {
  24. @Override
  25. public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
  26. System.out.println(method.getName() + "方法开始执行...");
  27. Object result = method.invoke(target, args);
  28. System.out.println(result);
  29. System.out.println(method.getName() + "方法执行结束...");
  30. return result;
  31. }
  32. };
  33. }
  34. }
  1. - 结果正常:略
  2. - 至此,我们又完成了动态代理的第二个目标:代码复用。不仅目标对象与增强代码解耦,代理对象也和增强代码解耦了

5.总结

5.1Proxy

  1. - JDK根据接口生成的其实是ProxyClass对象(即 Class<Proxy> ),然后根据proxyClass(.getConstructor(...).newInstance(...) : Object)得到proxy代理对象. proxy代理对象实现了接口,同时也是Proxy类型的。
  2. - proxy对象的原理是:内部维护一个InvocationHandler,而InvocationHandler是对增强代码的抽象。通过抽取InvocationHandler,将代理对象和增强代码解耦。
  3. - 其实就是调用链路拉长了,原本代理对象直接调用目标对象,现在是代理对象调InvocationHandlerInvocationHandler再调目标对象。Proxy代理对象内部有InvocationHandler对象,而InvocationHandler对象内部有我们塞进去的目标对象,所以最终通过代理对象可以调用到目标对象,并且得到了增强。

点击查看【processon】


.经典使用


1.Spring中AOP

  1. - SpringAOP编程中:
  2. - 如果加入容器的目标对象有实现接口,用JDK代理
  3. - 如果目标对象没有实现接口,用Cglib代理

2.MyBatis中

  1. - 通过Map文件的namespace绑定对应的DAO层接口, 通过sql标签的id属性绑定具体方法. 会动态生成具体DAO接口的运行时实现子类完成业务
  2. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1632886315317-5934071f-15dd-45c4-8647-d31308bebf1e.png#clientId=u204cb7be-327d-4&from=paste&height=354&id=ufc25a137&margin=%5Bobject%20Object%5D&name=image.png&originHeight=707&originWidth=1618&originalType=binary&ratio=1&size=163408&status=done&style=none&taskId=ud76aa959-288c-4410-b713-eec7003f2df&width=809)