前言
动态代理:程序运行过程中动态地创建代理类和对象的技术。通过动态代理,我们可以在不修改源代码的情况下,在方法执行前后加入一些附加操作,如:日志记录、性能统计、事务管理等。
Java中,动态代理两种实现:
- 基于接口动态代理(JDK动态代理):使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来创建代理对象。
- 基于继承动态代理(CGLIB动态代理)。
基于接口的动态代理(JDK动态代理)
基于Java反射机制。
// 接口
public interface Calculator {
int add(int a,int b);
}
// 实现接口
public class CalculatorImpl implements Calculator{
@Override
public int add(int a, int b) {
return a+b;
}
}
// 实现InvocationHandler接口
public class LoggingInvocationHandler implements InvocationHandler {
private Object target;
public LoggingInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
System.out.println("method:"+method.getName());
return result;
}
}
测试调用:
// 实现类
val calculator = CalculatorImpl()
// 代理类:传入classLoader,interfaces,具体代理类
val proxy = Proxy.newProxyInstance(
calculator.javaClass.classLoader,
calculator.javaClass.interfaces,
LoggingInvocationHandler(calculator)
) as Calculator
// 通过代理类调用实现类的方法
val result = proxy.add(2, 3)
println("result:$result")
CGLIB
基于字节码生成库。
- 引入CGLIB库
implementation 'cglib:cglib:3.3.0'
- 创建目标类:创建一个普通的Java类作为被代理的目标类。
- 创建拦截器(Interceptor)类:实现对目标类方法拦截和增强逻辑。
- 使用Enhancer生成代理对象
net.sf.cglib.proxy.Enhancer:增强类,通过字节码动态创建委托类的子类实现。不能对final类进行代理操作。Hibernate不能持久化final class的原因。
// 创建被代理类
public class SayHello {
public void say(){
System.out.println("hello");
}
}
/**
*代理类
*/
public class ProxyCglib implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("可以在调用实际方法前做一些事情");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("可以在调用实际方法后做一些事情");
return result;
}
}
// 测试
public class Mytest {
public static void main(String[] args) {
ProxyCglib proxy = new ProxyCglib();
//通过生成子类的方式创建代理类
SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
proxyImp.say();
}
}
JDK和CGLib动态代理对比
JDK动态代理实现了被代理对象所实现的接口,CGLib是继承了被代理对象。JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,CGlib代理实现更复杂,生成代理类的效率比JDK代理低。
JDK调用代理方法,通过反射机制调用;CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。
原理区别: java动态代理利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心实现InvocationHandler接口,使用invoke()方法进行面向切面的处理。
CGLib动态代理利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理。
- 如果目标对象实现了接口,默认情况下会采用JDK动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLib实现AOP
- 如果目标对象没有实现接口,必须采用CGLib库,spring会自动在JDK动态代理和CGLib之间切换。
| 类型 | 机制 | 回调方式 | 适用场景 | 效率 |
|---|---|---|---|---|
| JDK动态代理 | 委托机制,代理类和目标实现了同样的接口,InvocationHandler持有目标类,代理类委托,InvocationHandler去调用目标类的原始方法 | 反射 | 目标类是接口类 | 反射调用稍慢 |
| CGLib动态代理 | 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 | 通过FastClass方法索引调用 | 非接口类、非final类,非final方法 | 第一次调用因为哟啊生成多个Class对象,比JDK方式慢。多次调用方法索引比反射快,如果方法过多,switch case过多效率需要测试 |
静态代理和动态代理区别
- 静态代理只通过手动完成代理操作,如:被代理类增加新方法,代理类需要同步新增,违背开闭原则
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。