- 动态代理
- 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 {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
} }
public static void main(String[] args) { Tank tank = new Tank();
//reflection 通过二进制字节码分析类的属性和方法
Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class}, //tank.class.getInterfaces()new LogHander(tank)
);
m.move(); } }
class LogHander implements InvocationHandler {
Tank tank;public LogHander(Tank tank) {this.tank = tank;}//getClass.getMethods[]@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("method " + method.getName() + " start..");Object o = method.invoke(tank, args);System.out.println("method " + method.getName() + " end!");return o;}
}
interface Movable { void move(); }
<a name="NPKMn"></a>## v9代理各种工厂--->茅台、电器、电脑- 将TimeProxy中的成员由Tank具体类型改为Movable接口类型- 最终改为Object类型- 将业务逻辑与横切代码分离AOP- 即将横切代码(即添加的代码)写成单独的方法```javapackage com.mashibing.dp.proxy.v09;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,还可以代理任何其他可以代理的类型* (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?* 分离代理行为与被代理对象* 使用jdk的动态代理** v09: 横切代码与业务逻辑代码分离 AOP*/public class Tank implements Movable {/*** 模拟坦克移动了一段儿时间*/@Overridepublic void move() {System.out.println("Tank moving claclacla...");try {Thread.sleep(new Random().nextInt(10000));} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {Tank tank = new Tank();Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),new Class[]{Movable.class}, //tank.class.getInterfaces()new TimeProxy(tank));m.move();}}class TimeProxy implements InvocationHandler {Movable m;public TimeProxy(Movable m) {this.m = m;}public void before() {System.out.println("method start..");}public void after() {System.out.println("method stop..");}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object o = method.invoke(m, args);after();return o;}}interface Movable {void move();}
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 {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
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(),
new Class[]{Movable.class}, //tank.class.getInterfaces()new TimeProxy(tank)
);
m.move();
} }
class TimeProxy implements InvocationHandler { Movable m;
public TimeProxy(Movable m) {this.m = m;}public void before() {System.out.println("method start..");}public void after() {System.out.println("method stop..");}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//Arrays.stream(proxy.getClass().getMethods()).map(Method::getName).forEach(System.out::println);before();Object o = method.invoke(m, args);after();return o;}
}
interface Movable { void move(); }
- 是用反射来进行代理调用的(生成的原理?应该与APT注解处理器类似----->asm动态操作字节码)- **jdk动态代理的执行过程如下图所示(生成过程和调用过程):**1. 生成源码动态链接,走编译……一系列过程1. 直接操作字节码1. asm直接操作二进制文件,直接改二进制字节码(二进制字节码操作类库)1. 用了asm之后,java才可以被称为动态语言<a name="wdZHX"></a>### 生成的代理类经反编译后的代码```java//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package com.mashibing.dp.proxy.v10;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;final class $Proxy0 extends Proxy implements Movable {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws {super(var1);}public final boolean equals(Object var1) throws {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final void move() throws {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.mashibing.dp.proxy.v10.Movable").getMethod("move");m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}}
v11cglib方式生产代理
- cglib底层用的就是asm(spring用过cglib)
- (asm比cglib复杂20倍以上)
- (可以代理final—->asm)
cglib的maven依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib --><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.12</version></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) {
} }Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Tank.class);// 设定回调,被代理类的方法被调用时会调用代理类的哪一个// 拦截器,相当于invocationHandlerenhancer.setCallback(new TimeMethodInterceptor());Tank tank = (Tank)enhancer.create();tank.move();
class TimeMethodInterceptor implements MethodInterceptor {
// 第一个参数为生成的代理类@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println(o.getClass().getSuperclass().getName());System.out.println("before");Object result = null;result = methodProxy.invokeSuper(o, objects);System.out.println("after");return result;}
}
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中的名词解释
动态语言
- javascript、python是动态语言
- 在运行时直接改变类中的属性和方法,动态添加、动态删除;java中反射只能读出来,改不了
- 要想用asm就必须知道字节码文件.class的结构是什么
- jvm生态:java、scala、kotlin、groovy、……
asm
- 项目上实际上很少用得上asm,出了bug看不出,危险性大(以后可能会问到)
- asm框架中综合了三种设计模式
- 研究、探讨二进制码,什么代表什么……
🤏随想
- 给对象成员属性进行初始化的时候,比较专业的做法是通过构造方法传入一个对象(本例中的对象即被代理人)
- jdk的动态代理也可以嵌套多层代理
- jdk动态代理和cglib动态代理底层都是asm
- asm直接操作的是二进制码,甚至可以删掉final,用asm在代码中,代码逻辑不知不觉就变了,但asm接口调用使用起来比cglib复杂20倍以上
- 还有instrument方式生成动态代理—->调琴弦(java自带的,钩子函数,拦截器)(用的不多)
- 每一个class加载到内存过程中会拦截到class,然后进行定制
- 功能强大,可以完全控制字节码
- 比asm更强大(asm是通过api实现的,jdk版本提升了但asm没升级可能就用不了了)
- 但instrument使用繁琐,必须了解字节码中每一个0、1的含义
