CGLIB(Code Generation Library)是实现动态代理的一种方案。动态代理的内容一般都包含三个部分:① 代理类的生成;② 代理类的实例化;③ 代理类的使用。 本文作为CGLIB文章的前篇,将通过与使用者直接相关入手,先介绍代理类使用相关底层逻辑。即,当我们调用代理对象的对应方法时如何实现代理的。关于前两个部分将会在后篇进行介绍。
开篇第一章,先回顾下CGLIB动态代理的简单使用,代码如下:
① 被代理类
public class Student {
public String name;
public Student() {
this.name = "spl";
}
public void outStudentName() {
System.out.println(this.name);
}
}
② 方法拦截器
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("代理前置....");
System.out.println("第一个参数:obj = " + obj.getClass().getSimpleName());
System.out.println("第二个参数:method = " + method.getName());
// System.out.println("第三个参数:args = " + args);
System.out.println("第三个参数:proxy = " + proxy.getSignature());
Object invokeResult = proxy.invokeSuper(obj, args);
System.out.println("代理后置....");
return invokeResult;
}
}
③ 使用cglib代理
public class Client {
public static void main(String[] args) {
System.setProperty ( DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/spl/own/mavenTest" );
MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Student.class);
enhancer.setCallback(myMethodInterceptor);
Student stu = (Student) enhancer.create();
stu.outStudentName();
}
}
执行结果:
最上面的红字表示我已经开启了保存动态类信息功能,便于后续分析代理功能的实现逻辑。下面输出了多行信息表明动态代理已生效。
配置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY属性,cglib会将动态生成的类保存在指定目录下。
可以看到,动态生成代理类文件路径在指定目录下的代理类同路径下,即${DebuggingClassWriter.DEBUG_LOCATION_PROPERTY}/{被代理类包路径}.
net.sf.cglib包下的动态代理类先不介绍,先看Strudent$$XXX动态代理相关类。
FastClass子类及其对象都是在调用对应代理方法时才会被创建、实例化。这部分后续文中会提到。
public class Student$$EnhancerByCGLIB$$2838b21a
extends Student
implements Factory {
...
}
代理类直接继承了被代理类,而且实现了Factory接口。所有被Enhancer类返回的增强代理类均会实现Factory接口。Factory接口提供了一系列创建新增强代理对象的newInstance()方法,且支持替换之前指定的Callbacks列表(什么是Callback?简单理解为方法回调对象,如上文示例中MyMethodInterceptor实现的MethodInterceptor接口就继承了Callbacks接口)。
【TODO】cglib能够代理抽象类、接口吗?不能。
增强代理类的属性如下:
private static final Method CGLIB$outStudentName$0$Method; // 被代理方法句柄
private static final MethodProxy CGLIB$outStudentName$0$Proxy; // 代理方法句柄
被代理方法对象以及代理方法对象会作为第二、第四个参数传给回调函数的intercep(…)方法。JVM加载代理类时会初始化所有的被代理方法对象(Method)和代理方法对象(MethodProxy)。
被代理方法Method对象的获取是通过反射的方式获取,而代理方法的获取是创建MethodProxy对象,MethodProxy创建时会持有被代理类Class对象、代理类Class对象、被代理方法名、代理方法名、以及方法描述(由参数类型即返回值组成)。
在具体调用方法拦截器的intercept方法入参中Method就是这里的被代理方法、MethodProxy就是代理方法对象。MethodProxy在整个CGLIB代理过程中作用至关重要,具体有关MethodProxy细节在后续第三章内容。
public Student$$EnhancerByCGLIB$$86327ae0() {
CGLIB$BIND_CALLBACKS(this);
}
代理类仅提供了一个无参构造方法,该方法内部仅做了一件事情-给代理类对象绑定回调方法。绑定回调方法的过程:
构造方法还有一件事情,就是调用父类的构造方法。如果被代理对象时有参构造,代理对象也是有参构造,并会显示调用super(…)方法
静态初始化+构造方法基本上是将代理类所必须的属性全部设置完。其中静态初始化的属性均为所有对象确定且一致的,而构造函数依赖于运行时可变静态属性。
代理类的公开方法在这里会被分为三个部分,分别是父类引用方法、接口引用方法、内部公开方法。我先说下为什么这么分:既然是代理类,那么该类的类名实际上肯定是运行时动态确定的,因此在实际程序代码中不太可能使用代理类型变量,因此代理类对象肯定是通过多态进行调用。
根据代理类的类间关系,代理类对象要么被其父类(即被代理类)引用,要么被其实现的接口(Factory)引用。除此之外,代理类还有几个内部静态公开方法,这个是给内部使用的,上层应用程序中使用不到。
前面说到增强代理类代理的方法有(n+4)个,其中n为被代理类的方法,4为Object类中的方法。这些方法在代理类中会被重写,代理对象调用时的逻辑可总结图如下:
流程还是十分简单清楚的,有回调对象就直接调用其intercept()方法,具体后续交给每一个MethodInteceptor对象处理。反之,如果没有回调对象,那就只能调用父类方法了(即,无代理动作)。
这里说一下调用MethodInteceptor#intercept()方法的入参,为后续做好铺垫。几乎所有重写方法的内部逻辑差异不大,这里以Object#equals方法为例:
Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
var10000就是MethodInteceptor回调对象,intercept方法的第一个参数就是当前代理类对象,第二个参数被代理方法(Method),第三个参数是方法入参,第四个参数是代理方法(MethodProxy)。如果方法没有任何入参,第三个参数会传入CGLIB$emptyArgs空Object数组。
前面已经提到所有创建的增强代理类都会实现Factory接口,Factory接口提供了两个能力:
这里使用setXXX方法不好,其实内部逻辑实际上是replaceXXX。
增强代理类除了以上公开方法,还有三个内部使用的静态方法:
前面已经将代理类和代理对象的内容分析的十分清楚了,代理对象会重写父类(也即,代理类)的方法,而在实际调用中会将代理类对象、被代理方法Method对象、方法参数、代理方法MethodProxy对象传给MethodInterceptor对象。这其中MethodProxy我们还是比较陌生的,本章节将会一步步拆解其内部逻辑。
public class MethodProxy {
private Signature sig1;
private Signature sig2;
private CreateInfo createInfo;
private final Object initLock = new Object();
private volatile FastClassInfo fastClassInfo;
}
MethodProxy共有5个属性:
MethodProxy的构造函数为私有,仅提供一个公开静态方法用于内部创建方法代理实例。
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
MethodProxy 实例化方法中就是创建代理类及被代理类对应方法的签名和CreateInfo实例。
当前除了创建对象的初始化逻辑之外,在调用MethodProxy对象的任何公开方法时,都会调用其init()方法去初始化FastClassInfo对象,便于之后的方法索引获取。
private void init() {
if (fastClassInfo == null){
synchronized (initLock) {
if (fastClassInfo == null) {
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}
可以看到这里通过双重检查锁的方式来创建FastClassInfo对象。FastClassInfo对象的创建过程会创建并存储被代理类、代理类的FastClass对象(f1、f2),也会存储当前代理方法在被代理类、代理类FastClass对象的索引(i1、i2)便于后续快速查找方法。
FastClass对象的创建逻辑在helper方法中,此处暂时先不展开介绍,在后续文章介绍FastClass自然就理解了。值得提醒的是,代理类、非代理类的FastClass类只有在此处才会被创建,意即只有在调用MethodProxy对象公开方法才会创建对应的FastClass类。这就回答了一个问题,为什么FastClassInfo对象不是在create方法中直接创建好,而是延迟使用DCL创建呢?因为并非是所有代理类都会被调用,如果大量的FastClass创建会减慢编译速度,增加java程序的编译时间。
看过我之前单例模式文章,在讲解DCL机制的时候提到过,以上这种使用方式可能存在问题-在高并发下,有可能会使用不成熟的FastClassInfo对象,产生空指针。
解决办法:在第一重判断条件上,增加与逻辑(或直接替换):createInfo != null;
MethodProxy共提供了5个公开方法(其中1个是静态方法):
invoke是调用被代理对象的被代理方法,而invokeSuper则是调用增强代理对象的代理方法。
疑问:invoke似乎用处很小,一般都是使用invokeSuper。
下面以invokeSuper为例,梳理下具体调用过程:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init(); // 例行初始化FastClassInfo(创建被代理类、代理类FastClass)
FastClassInfo fci = fastClassInfo;
/**
* 调用代理类的FastClass对象的invoke方法
* 第一个参数为代理方法的索引ID,第二个参数为代理对象,第三个参数方法参数
* 具体执行:就是根据索引ID找到方法,然后根据obj、args调用方法
**/
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
在第二个章节开头,其中Student$$EnhancerByCGLIB$2838b21a$FastClassByCGLIB$594c36f就是为代理类生成的FastClass子类文件。FastClass就是为了加速检索类方法,这里不再赘述。其invoke方法逻辑为:
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
86327ae0 var10000 = (86327ae0)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
return new Boolean(var10000.equals(var3[0]));
case 1:
return var10000.toString();
case 2:
return new Integer(var10000.hashCode());
case 3:
return var10000.clone();
...
case 17:
return var10000.CGLIB$clone$4();
case 18:
var10000.CGLIB$outStudentName$0();
return null;
case 19:
return 86327ae0.CGLIB$findMethodProxy((Signature)var3[0]);
case 20:
86327ae0.CGLIB$STATICHOOK1();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
当被代理方法很多时,这个方法代码会很长,但是理解起来其实十分简单,就是根据第一个参数index值,来执行不同的方法。代理类所有的方法都对应了一个索引,而在MethodProxy#invokeSuper方法的索引值一定是代理方法(以CGLIB开头)对应的index。代理方法在代理类中的权限为默认权限,且其内部逻辑仅是调用父类的对应方法,没有调用回调对象的拦截方法。下面给出equals()方法的代理方法示例:
final boolean CGLIB$equals$1(Object var1) {
return super.equals(var1);
}
回到最初的问题,当我们调用代理对象的对应方法时如何实现代理的?
我们先回顾下JDK动态代理。JDK动态代理也是会生成并实例化一个代理类,该代理类继承的父类为Proxy类,且实现了接口下的所有方法。同样的,因为是运行时生成的代理类,代理对象一定是通过接口引用的多态对象存在。当代理对象调用任何接口方法时,其内部逻辑都会调用InvocationHandler对象(仅一个)的invoke方法进行调用。因此,使用者可以自定义InvocationHandler对象,在invoke方法中实现对指定对象的代理。
但CGLIB却有所不同。CGLIB也会生成并实例化一个代理类,该代理类直接继承被代理类,且会重写所有被代理类的方法。代理类的的重写方法内部逻辑是如果有回调对象,就将方法的执行逻辑托给回调对象,该回调对象就是使用者指定的MethodInterceptor对象,使用者可以在intercept方法中实现方法逻辑的增减修改。
两者有所不同,实际上JDK动态代理类完全就是一个新类,从代理关系上来看,代理对象代理了InvocationHandler对象,InvocationHandler对象代理了需要被代理的对象(当然,这一步依赖于用户的具体实现逻辑)。而CGLIB增强类实际上是被代理类的子类,利用重写方法的机制实现方法逻辑的增强,并提供回调对象供使用者自定义增强逻辑。因此,CGLIB生成的类应该算是动态类型增强,好过理解为代理。