1. 动态代理
  2. Spring AOP

v8分离代理行为与代理对象

  • 动态代理
  • 看不见的,运行时自动生成了额外的代理代码
  • java.lang.reflect.Proxy
  • 反射:通过二进制字节码分析类的属性和方法
  • Proxy.newInstance有三个参数:
    • loader:该代理对象由哪个类加载器加载到内存中去
    • interface:该代理对象应该实现哪些接口
    • invocationHandler:调用时的处理器,被代理对象的方法被调用时应该如何处理
  • jdk动态代理必须有接口
  • 代理对象和被代理对象的类加载器不能是平行的,但是代理对象的类加载器可以是被代理对象的类加载器的子加载器?双亲委派机制 ```java package com.mashibing.dp.proxy.v08;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Random;

/**

  • 问题:我想记录坦克的移动时间
  • 最简单的办法:修改代码,记录时间
  • 问题2:如果无法改变方法源码呢?
  • 用继承?
  • v05:使用代理
  • v06:代理有各种类型
  • 问题:如何实现代理的各种组合?继承?Decorator?
  • v07:代理的对象改成Movable类型-越来越像decorator了
  • v08:如果有stop方法需要代理…
  • 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 Object
  • (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
  • 分离代理行为与被代理对象
  • 使用jdk的动态代理 */ public class Tank implements Movable {

    /**

    • 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println(“Tank moving claclacla…”); try {

      1. Thread.sleep(new Random().nextInt(10000));

      } catch (InterruptedException e) {

      1. e.printStackTrace();

      } }

      public static void main(String[] args) { Tank tank = new Tank();

      //reflection 通过二进制字节码分析类的属性和方法

      Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),

      1. new Class[]{Movable.class}, //tank.class.getInterfaces()
      2. new LogHander(tank)

      );

      m.move(); } }

class LogHander implements InvocationHandler {

  1. Tank tank;
  2. public LogHander(Tank tank) {
  3. this.tank = tank;
  4. }
  5. //getClass.getMethods[]
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println("method " + method.getName() + " start..");
  9. Object o = method.invoke(tank, args);
  10. System.out.println("method " + method.getName() + " end!");
  11. return o;
  12. }

}

interface Movable { void move(); }

  1. <a name="NPKMn"></a>
  2. ## v9代理各种工厂--->茅台、电器、电脑
  3. - 将TimeProxy中的成员由Tank具体类型改为Movable接口类型
  4. - 最终改为Object类型
  5. - 将业务逻辑与横切代码分离AOP
  6. - 即将横切代码(即添加的代码)写成单独的方法
  7. ```java
  8. package com.mashibing.dp.proxy.v09;
  9. import java.lang.reflect.InvocationHandler;
  10. import java.lang.reflect.Method;
  11. import java.lang.reflect.Proxy;
  12. import java.util.Random;
  13. /**
  14. * 问题:我想记录坦克的移动时间
  15. * 最简单的办法:修改代码,记录时间
  16. * 问题2:如果无法改变方法源码呢?
  17. * 用继承?
  18. * v05:使用代理
  19. * v06:代理有各种类型
  20. * 问题:如何实现代理的各种组合?继承?Decorator?
  21. * v07:代理的对象改成Movable类型-越来越像decorator了
  22. * v08:如果有stop方法需要代理...
  23. * 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型
  24. * (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
  25. * 分离代理行为与被代理对象
  26. * 使用jdk的动态代理
  27. *
  28. * v09: 横切代码与业务逻辑代码分离 AOP
  29. */
  30. public class Tank implements Movable {
  31. /**
  32. * 模拟坦克移动了一段儿时间
  33. */
  34. @Override
  35. public void move() {
  36. System.out.println("Tank moving claclacla...");
  37. try {
  38. Thread.sleep(new Random().nextInt(10000));
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. public static void main(String[] args) {
  44. Tank tank = new Tank();
  45. Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
  46. new Class[]{Movable.class}, //tank.class.getInterfaces()
  47. new TimeProxy(tank)
  48. );
  49. m.move();
  50. }
  51. }
  52. class TimeProxy implements InvocationHandler {
  53. Movable m;
  54. public TimeProxy(Movable m) {
  55. this.m = m;
  56. }
  57. public void before() {
  58. System.out.println("method start..");
  59. }
  60. public void after() {
  61. System.out.println("method stop..");
  62. }
  63. @Override
  64. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  65. before();
  66. Object o = method.invoke(m, args);
  67. after();
  68. return o;
  69. }
  70. }
  71. interface Movable {
  72. void move();
  73. }

v10生成代理类(saveGeneratedFiles属性)

  • 不设置该属性,直接在内存中生成并且直接拿来用;设置之后保存到硬盘上
  • mybatis的proxy好几层
  • 可以根据method给不同的方法指定不同的代理(即不同的横切代码)
  • 接口中方法与对象是一致的,传给invoke什么引用就调用哪个对象中的相应方法===>method.invoke(对象, 参数); 方法被调用的时候需要告诉方法由谁来调用它
  • 返回void时Object为null ```java package com.mashibing.dp.proxy.v10;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Random;

/**

  • 问题:我想记录坦克的移动时间
  • 最简单的办法:修改代码,记录时间
  • 问题2:如果无法改变方法源码呢?
  • 用继承?
  • v05:使用代理
  • v06:代理有各种类型
  • 问题:如何实现代理的各种组合?继承?Decorator?
  • v07:代理的对象改成Movable类型-越来越像decorator了
  • v08:如果有stop方法需要代理…
  • 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型
  • (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
  • 分离代理行为与被代理对象
  • 使用jdk的动态代理 *
  • v09: 横切代码与业务逻辑代码分离 AOP
  • v10: 通过反射观察生成的代理对象
  • jdk反射生成代理必须面向接口,这是由Proxy的内部实现决定的 */ public class Tank implements Movable {

    /**

    • 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println(“Tank moving claclacla…”); try {

      1. Thread.sleep(new Random().nextInt(10000));

      } catch (InterruptedException e) {

      1. e.printStackTrace();

      } }

      public static void main(String[] args) { Tank tank = new Tank();

      // jdk版本不同也会不一样 // 高版本 System.getProperties().put(“jdk.proxy.ProxyGenerator.saveGeneratedFiles”,”true”);

      // 低版本 jdk1.8 System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”,”true”);

      Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),

      1. new Class[]{Movable.class}, //tank.class.getInterfaces()
      2. new TimeProxy(tank)

      );

      m.move();

      } }

class TimeProxy implements InvocationHandler { Movable m;

  1. public TimeProxy(Movable m) {
  2. this.m = m;
  3. }
  4. public void before() {
  5. System.out.println("method start..");
  6. }
  7. public void after() {
  8. System.out.println("method stop..");
  9. }
  10. @Override
  11. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  12. //Arrays.stream(proxy.getClass().getMethods()).map(Method::getName).forEach(System.out::println);
  13. before();
  14. Object o = method.invoke(m, args);
  15. after();
  16. return o;
  17. }

}

interface Movable { void move(); }

  1. - 是用反射来进行代理调用的(生成的原理?应该与APT注解处理器类似----->asm动态操作字节码)
  2. - **jdk动态代理的执行过程如下图所示(生成过程和调用过程):**
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/22850845/1633410043533-8675dbe3-7f2f-4729-bfa2-3ff066b3047b.png#clientId=ub5460220-4be2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=391&id=kftZd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=391&originWidth=853&originalType=binary&ratio=1&rotation=0&showTitle=false&size=115201&status=done&style=none&taskId=u0409b94a-a614-46bd-b2ac-44907a20ab2&title=&width=853)
  4. 1. 生成源码动态链接,走编译……一系列过程
  5. 1. 直接操作字节码
  6. 1. asm直接操作二进制文件,直接改二进制字节码(二进制字节码操作类库)
  7. 1. 用了asm之后,java才可以被称为动态语言
  8. <a name="wdZHX"></a>
  9. ### 生成的代理类经反编译后的代码
  10. ```java
  11. //
  12. // Source code recreated from a .class file by IntelliJ IDEA
  13. // (powered by FernFlower decompiler)
  14. //
  15. package com.mashibing.dp.proxy.v10;
  16. import java.lang.reflect.InvocationHandler;
  17. import java.lang.reflect.Method;
  18. import java.lang.reflect.Proxy;
  19. import java.lang.reflect.UndeclaredThrowableException;
  20. final class $Proxy0 extends Proxy implements Movable {
  21. private static Method m1;
  22. private static Method m3;
  23. private static Method m2;
  24. private static Method m0;
  25. public $Proxy0(InvocationHandler var1) throws {
  26. super(var1);
  27. }
  28. public final boolean equals(Object var1) throws {
  29. try {
  30. return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
  31. } catch (RuntimeException | Error var3) {
  32. throw var3;
  33. } catch (Throwable var4) {
  34. throw new UndeclaredThrowableException(var4);
  35. }
  36. }
  37. public final void move() throws {
  38. try {
  39. super.h.invoke(this, m3, (Object[])null);
  40. } catch (RuntimeException | Error var2) {
  41. throw var2;
  42. } catch (Throwable var3) {
  43. throw new UndeclaredThrowableException(var3);
  44. }
  45. }
  46. public final String toString() throws {
  47. try {
  48. return (String)super.h.invoke(this, m2, (Object[])null);
  49. } catch (RuntimeException | Error var2) {
  50. throw var2;
  51. } catch (Throwable var3) {
  52. throw new UndeclaredThrowableException(var3);
  53. }
  54. }
  55. public final int hashCode() throws {
  56. try {
  57. return (Integer)super.h.invoke(this, m0, (Object[])null);
  58. } catch (RuntimeException | Error var2) {
  59. throw var2;
  60. } catch (Throwable var3) {
  61. throw new UndeclaredThrowableException(var3);
  62. }
  63. }
  64. static {
  65. try {
  66. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  67. m3 = Class.forName("com.mashibing.dp.proxy.v10.Movable").getMethod("move");
  68. m2 = Class.forName("java.lang.Object").getMethod("toString");
  69. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  70. } catch (NoSuchMethodException var2) {
  71. throw new NoSuchMethodError(var2.getMessage());
  72. } catch (ClassNotFoundException var3) {
  73. throw new NoClassDefFoundError(var3.getMessage());
  74. }
  75. }
  76. }

v11cglib方式生产代理

  • cglib底层用的就是asm(spring用过cglib)
  • (asm比cglib复杂20倍以上)
  • (可以代理final—->asm)

    cglib的maven依赖

    1. <!-- https://mvnrepository.com/artifact/cglib/cglib -->
    2. <dependency>
    3. <groupId>cglib</groupId>
    4. <artifactId>cglib</artifactId>
    5. <version>3.2.12</version>
    6. </dependency>

    cglib的应用

    ```java package com.mashibing.dp.cglib;

import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method; import java.util.Random;

/**

  • CGLIB实现动态代理不需要接口 */ public class Main { public static void main(String[] args) {
    1. Enhancer enhancer = new Enhancer();
    2. enhancer.setSuperclass(Tank.class);
    3. // 设定回调,被代理类的方法被调用时会调用代理类的哪一个
    4. // 拦截器,相当于invocationHandler
    5. enhancer.setCallback(new TimeMethodInterceptor());
    6. Tank tank = (Tank)enhancer.create();
    7. tank.move();
    } }

class TimeMethodInterceptor implements MethodInterceptor {

  1. // 第一个参数为生成的代理类
  2. @Override
  3. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  4. System.out.println(o.getClass().getSuperclass().getName());
  5. System.out.println("before");
  6. Object result = null;
  7. result = methodProxy.invokeSuper(o, objects);
  8. System.out.println("after");
  9. return result;
  10. }

}

class Tank { public void move() { System.out.println(“Tank moving claclacla…”); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } } ```

代理类生成的方式

jdk动态代理

asm操作字节码

asm直接操作二进制文件,直接改二进制字节码(二进制字节码操作类库) 用了asm之后,java才可以被称为动态语言

  • spring、cglib、jdk动态代理都是用asm实现的

编译源码(动态链接)

生成源码后再进行预处理、连接、……一系列过程

instrument操作字节码

调琴弦(java自带的,钩子函数,拦截器)(用的不多) 每一个class加载到内存过程中会拦截到class,然后进行定制 功能强大,可以完全控制字节码 比asm更强大(asm是通过api实现的,jdk版本提升了但asm没升级可能就用不了了) 但instrument使用繁琐,必须了解字节码中每一个0、1的含义

cglib动态代理

全程code generation library 比jdk反射生成简单一点 通过继承实现代理,代理类是被代理类的子类 final修饰的类无法用cglib实现代理,但是asm可以,因为他直接操作的是二进制码,甚至可以删掉final 需要添加maven依赖chlib,或者引入包 Enhancer增强器:设置父类、设定回调 底层也是asm 代理类中只有eat方法,没有run方法,因为run方法被final修饰了,不可被重写,所以代理类中就没有run方法,这里要符合java规范!!! 不能增强final方法

Spring动态代理

  • 底层原理也是asm
  • aop:aspect oriented programme
  • 织入weave、切入点、连接点、切面、通知
  • 通过配置文件配置哪个方法应该先执行,哪个方法应该切入到哪个方法中去,由框架自动生成代理需要用的方法(通过jdk动态代理或者cglib动态代理)
  • aspectweaver依赖
  • Spring两大核心:IOC + AOP
  • bean工厂+灵活装配+动态行为拼接
  • AOP中的名词解释

动态语言

  1. javascript、python是动态语言
  2. 在运行时直接改变类中的属性和方法,动态添加、动态删除;java中反射只能读出来,改不了
  3. 要想用asm就必须知道字节码文件.class的结构是什么
  4. jvm生态:java、scala、kotlin、groovy、……

asm

  • 项目上实际上很少用得上asm,出了bug看不出,危险性大(以后可能会问到)
  • asm框架中综合了三种设计模式
  • 研究、探讨二进制码,什么代表什么……

🤏随想

  1. 给对象成员属性进行初始化的时候,比较专业的做法是通过构造方法传入一个对象(本例中的对象即被代理人)
  2. jdk的动态代理也可以嵌套多层代理
  3. jdk动态代理和cglib动态代理底层都是asm
  4. asm直接操作的是二进制码,甚至可以删掉final,用asm在代码中,代码逻辑不知不觉就变了,但asm接口调用使用起来比cglib复杂20倍以上
  5. 还有instrument方式生成动态代理—->调琴弦(java自带的,钩子函数,拦截器)(用的不多)
    1. 每一个class加载到内存过程中会拦截到class,然后进行定制
    2. 功能强大,可以完全控制字节码
    3. 比asm更强大(asm是通过api实现的,jdk版本提升了但asm没升级可能就用不了了)
    4. 但instrument使用繁琐,必须了解字节码中每一个0、1的含义