引子
CGLib 是 Code Generation Library 的简称,是一个基于字节码技术(ASM)的代码生成库。与上一篇介绍的 JDK 动态代理的显著不同是,CGLib 可以生成指定类的子类,而无需该类实现接口。
从前面我们介绍的 ClassLoader 部分我们知道,JVM 运行时通过 ClassLoader 读取 class 文件来加载类对象( class 文件存储的便是提前编译好的字节码)。而 CGLib 就是在运行时动态生成字节码,然后通过 ClassLoader 加载为类对象。CGLib 本身并没有实现生成字节码逻辑,而是基于 java 的 ASM 包还实现的,而 ASM 比较复杂,所以我们就不在生成字节码上展开说明了,感兴趣的朋友可以自己搜索相关资料学习,本文的重点还是放在CGLib 是如何实现动态代理的方面着重展开。
例子
我们还是从一个简单的例子开始逐步的学习,我们实现一个在 UserService.add 方法调用的前后打印日志的功能。
//要被增强的类
public class UserService {
public void add(String name) {
System.out.println("保存用户:" + name);
}
}
//方法拦截
public class LogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
/**
* Object o : 生成的代理对象
* Method method : 父类对应的 Method.这里就是 UserService的 add
* Object[] objects : 方法参数
* MethodProxy methodProxy : 代理方法(区别于method参数)
**/
System.out.println("方法执行前打印");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("方法执行后打印");
return result;
}
}
//测试类
public class Main {
public static void main(String[] args) {
//这句话是为了将生成的class 文件保存下来,用于分析
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/chenxing/code/xchen/demo-maven/gen");
//创建增强
Enhancer enhancer = new Enhancer();
//指定父类,也就是我们要增强的类
enhancer.setSuperclass(UserService.class);
//指定增强的逻辑
enhancer.setCallback(new LogInterceptor());
//创建代理对象
UserService o = (UserService) enhancer.create();
//调用代理对象
o.add("小明");
}
}
输出:
方法执行前打印
保存用户:小明
方法执行后打印
我们看到,代理的最核心的逻辑在LogInterceptor里面,与 上一篇我们讲到的JDK 动态代理类似,也是在调用方法的前后写入自定义的逻辑实现功能增强,而不同的是,这里我们没有直接调用 method的 invoke 方法,而是通过 methodProxy 对象的 invokeSuper 来实现方法调用。那整个过程具体是怎么调用的呢?
第一步:生成代理对象
UserService o = (UserService) enhancer.create();
第二步:调用代理对象的 add 方法:这个代理对象内封装了我们实现的 LogInterceptor 对象,然后在调用代理对象的 add 方法内调用LogInterceptor的intercept方法。
o.add("小明"); //内部调用了LogInterceptor的intercept方法
第三步:调用原始方法:就是上面说到的通过 methodProxy.invokeSuper 来调用原始的方法了。这里比较复杂,现在只要知道是为了调用原始的方法即可,后面会介绍到。
分析
直接上生成的源码!!!算了,那玩意变量名有 1 米长,看着就头疼,我来把代码翻译的可读一些,去掉里面不需要关注的部分,简化一下给大家看,当然,大家也可以直接参考上面的内容自己生成实际的代码查看。
我们先来看一下生成的子类:(这里是我简化重写的,表示那个意思)
public class UserServiceChild extends UserService {
private MethodInterceptor methodInterceptor;
private static final Method addMethod;
private static final MethodProxy addMethodProxy;
//省略以上参数的初始化内容
final void add0(String var1) {
super.add(var1);
}
public final void add(String var1) {
methodInterceptor.intercept(this, addMethod, new Object[]{var1}, addMethodProxy);
}
}
然后我们看到这里调用了 LogInterceptor 的 intercept方法,而通过 LogInterceptor 中我们又看到其调用了MethodProxy.invokeSuper()方法。如下:
class FastClassInfo {
private FastClass fastClass;
//表示当前 MethodProxy对象表示的方法在FastClass中的索引值,往下看就明白了
private int methodIndex;
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
FastClassInfo fci = initFastClass();
//这里的 methodIndex 是 1
return fci.fastClass.invoke(fci.methodIndex, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
这里有一个FastClassInfo,这个是干嘛的?其实这个是 CGLib 为了解决反射慢的问题生成的一个加速类,通过把类的方法进行编码,比如 0 代表 add方法,1代表 add0 方法,来快速调用对应的方法,而不需要通过反射,如下:
public class UserServiceChildFastClass extends FastClass {
@Override
public Object invoke(int methodIndex, Object proxyObj, Object[] methodArgs) throws InvocationTargetException {
UserServiceChild proxy = (UserServiceChild) proxyObj;
try {
switch (methodIndex) {
case 0:
proxy.add((String)methodArgs[0]);
return null;
case 1:
proxy.add0((String)methodArgs[0]);
return null;
}
} catch (Throwable e) {
throw new InvocationTargetException(e);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}
这里我们因为调用时methodIndex=1(initFastClass()方法时根据当前的方法签名从 UserServiceChildFastClass 中获得,这里给出的UserServiceChildFastClass并非完整版,只是示意,有兴趣可以自己运行一下代码生成一下),所以调用的是UserServiceChild的 add0 方法,而 add0 方法又是调用的super.add() 就是 UserService.add 方法。
最后,MethodProxy 对象其实还有一个 invoke 方法,如果这么写会造成 StackOverflow.:
public class LogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
/**
* Object o : 生成的代理对象
* Method method : 父类对应的 Method.这里就是 UserService的 add
* Object[] objects : 方法参数
* MethodProxy methodProxy : 代理方法(区别于method参数)
**/
System.out.println("方法执行前打印");
Object result = methodProxy.invoke(o, objects);
System.out.println("方法执行后打印");
return result;
}
}
这是为啥呢?其实是因为 invoke 方法直接调用了代理对象的 add 方法。而从上面可知add又调用了 Intercept方法,Intercept方法又调用了invoke 方法,无限循环!最后就 StackOverflow 了。
结束~~~,写的不好,轻喷!