- 动态代理
- 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[]
@Override
public 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
- 即将横切代码(即添加的代码)写成单独的方法
```java
package 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 {
/**
* 模拟坦克移动了一段儿时间
*/
@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();
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..");
}
@Override
public 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..");
}
@Override
public 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);
// 设定回调,被代理类的方法被调用时会调用代理类的哪一个
// 拦截器,相当于invocationHandler
enhancer.setCallback(new TimeMethodInterceptor());
Tank tank = (Tank)enhancer.create();
tank.move();
class TimeMethodInterceptor implements MethodInterceptor {
// 第一个参数为生成的代理类
@Override
public 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的含义