「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
上一篇文章 学习了 JDK 动态代理,JDK 动态代理有一个问题是只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。CGLIB 基于 ASM 的字节码生成库,可以在运行时对字节码进行修改和动态生成。
CGLIB 动态代理
MethodInterceptor 接口和 Enhancer 类
在 CGLIB 动态代理机制中核心是 MethodInterceptor 接口和 Enhancer 类。
我们需要自定义类实现 MethodInterceptor接口 并重写 intercept 方法,intercept 用于增强被代理类的方法。当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。
public interface MethodInterceptor extends Callback {
Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable;
}
-
Object object: object是 CGLIB 动态生成的代理类对象
-
Method method: Method 为实体类所调用的被代理的方法引用
-
Objectp[] args: 这个就是方法的参数列表
-
MethodProxy methodProxy : 这个就是生成的代理类对方法的引用。
通过 Enhancer类可以动态获取代理类,代理类是目标类的子类。Enhancer 类和 JDK 动态代理中的 Proxy 类类似,调用 Proxy 类的 newProxyInstance() 方法可以创建代理对象,同样,调用 Enhancer 的 create() 方法也可以创建代理对象,只是两者原理不同, Proxy 类通过反射创建代理对象,而 Enhancer 类通过修改字节码来实现,动态地创建给定类的子类。
使用小例子
首先创建需要被代理的类 RealSubject,request 方法是要被增强的目标方法。
public class RealSubject {
@Override
public void request() {
System.out.println("real request");
}
}
CGLIB 动态代理不需要实现与目标类一样的接口,而是通过方法拦截的方式实现代理,实现方法拦截接口 MethodInterceptor。intercept 方法中我们在目标方法执行前和执行后都输出了信息。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("do sth before request");
methodProxy.invokeSuper(object, args);
System.out.println("do sth after request");
return null;
}
}
在 main 方法中,通过 Enhancer 类的 create() 方法获取到代理对象。
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MyMethodInterceptor());
RealSubject proxy = (RealSubject) enhancer.create();
System.out.println("代理对象" + proxy.getClass());
proxy.request();
}
}
输出结果
代理对象class cglib.RealSubject$$EnhancerByCGLIB$$1248fed6
do sth before request
real request
do sth after request
JDK 动态代理和 CGLIB 动态代理的区别
-
从使用上:
JDK动态代理只能代理实现了接口的类,CGLIB动态代理生成的代理类是目标类的子类,目标类和方法不能加final修饰。 -
从原理上:
JDK动态代理通过反射创建代理对象,CGLIB利用ASM框架,对目标类生成的class文件加载进来,通过修改其字节码生成子类。 -
从性能上:
JDK6前字节码技术生成代理类的性能比反射生成的效率要高,随着对 JDK 动态代理的优化,在 JDK8 的时候JDK动态代理的效率要高于CGLIB动态代理了。Spring会在这两者之间切换,在bean有实现接口时,使用JDK动态代理,没有实现接口使用CGLIB动态代理。