CGLib浅析

什么是CGLib

CGLIB实现动态代理,并不要求被代理类必须实现接口,底层采用asm字节码生成框架生成代理类字节码(该代理类继承了被代理类)。

所以被代理类一定不能定义为final class并且对于final 方法不能被代理。

实现需要

//MethodInterceptor接口的intercept方法
/**
*obj 代理对象
*method 委托类方法,被代理对象的方法字节码对象
*arg 方法参数
*MethodProxy 代理方法MethodProxy对象,每个方法都会对应有这样一个对象
*/
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy)
Ehancer enhancer = new Enhancer() //Enhancer为字节码增强器,很方便对类进行扩展
enhancer.setSuperClass(被代理类.class);
enhancer.setCallback(实现MethodInterceptor接口的对象)
enhancer.create()

代码案例

导入依赖

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

UserDaoImpl 用户实现类(RealSubject)

public class UserDaoImpl {
public boolean insert(String name) {
System.out.println("insert name=" + name);
return true;
}

public final boolean insert1(String name) {
System.out.println("final insert name=" + name);
return true;
}
}

CglibProxy CGLIB代理类(Proxy)

public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("----------before----------");
System.out.println("Proxy=" + o.getClass());
System.out.println("method=" + method);
System.out.println("args=" + Arrays.toString(objects));
System.out.println("methodProxy=" + methodProxy);
//执行目标方法对象
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("----------after----------");
return result;
}
}

ProxyFactory 代理工厂

public class ProxyFactory {
private static Enhancer enhancer = new Enhancer();
private static CglibProxy cglibProxy = new CglibProxy();

public static Object getProxy(Class cls) {
enhancer.setSuperclass(cls);
enhancer.setCallback(cglibProxy);
return enhancer.create();
}

public static void main(String[] args) {
UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);
userDao.insert("zc");
}
}
5

CGLib流程

Ehancer enhancer = new Enhancer() //Enhancer为字节码增强器,很方便对类进行扩展
enhancer.setSuperClass(被代理类.class); //为生成的类设置父类
enhancer.setCallback(实现MethodInterceptor接口的对象);
enhancer.create(); //创建代理对象

创建代理对象会经过三步:

1.生成代理类的二进制字节码文件。

2.加载二进制字节码文件到JVM,生成class对象。

3.反射获得实例构造方法,创建代理对象。

接下来,看看反编译出现的Java文件

2

CGLib反编译方法

  • 使用以下语句
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\xxxx")
  • 使用HSDB进行反编译

  • 使用 arthas 配合 jad进行反编译

具体使用方法可以自行查找


我们以insert() 为入口开始:

UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);  //Ehancer,创建代理对象
userDao.insert("zc");

这时候会进入UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2 中的 insert()

public final boolean insert(String string) {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);
return object == null ? false : (Boolean)object;
}
return super.insert(string);
}

其实在上述方法中,是因为设置了 enhancer.setCallback(cglibProxy); ,只要不为空,则会执行

Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);

this : 当前代理对象

CGLIB$say$0$Method : 目标类中的方法

CGLIB$emptyArgs : 方法参数,这里为空

CGLIB$say$0$Proxy : 代理类生成的代理方法

这样会去调用 CglibProxy.intercept() 方法

/**
* Object:cglib生成的代理对象
* Method:被代理对象方法
* Object[]:方法入参
* MethodProxy:代理的方法
*/
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("----------before----------");

//执行目标方法对象
Object result = methodProxy.invokeSuper(o, objects);

System.out.println("----------after----------");
return result;
}
}

这时候进入 methodProxy.invokeSuper(o, objects) 方法

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}

第一反应,可能不知道是f2i2 都是什么,这里扯一下 init() 方法,其中对于FastClass 类,就是反编译出来的对应类:

private void init()
{
if (fastClassInfo == null)
{
synchronized (initLock)
{
if (fastClassInfo == null)
{
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1); // 被代理类FastClass
fci.f2 = helper(ci, ci.c2); // 代理类FastClass
fci.i1 = fci.f1.getIndex(sig1); // 被代理类的方法签名(index)
fci.i2 = fci.f2.getIndex(sig2); // 代理类的方法签名(index)
fastClassInfo = fci;
createInfo = null;
}
}
}
}

private static class FastClassInfo
{
FastClass f1; // 被代理类FastClass
FastClass f2; // 代理类FastClass
int i1; // 被代理类的方法签名(index)
int i2; // 代理类的方法签名(index)
}
fci.f2 = helper(ci, ci.c2);    // 代理类FastClass
fci.i2 = fci.f2.getIndex(sig2); // 代理类的方法签名(index)

这时候看到UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5

@Override
public int getIndex(Signature signature) {
String string = ((Object)signature).toString();
switch (string.hashCode()) {
//XXXXX 省略

case -747055045: {
if (!string.equals("CGLIB$insert$0(Ljava/lang/String;)Z")) break;
return 16;
}

//XXXXX 省略
return -1;
}

所以 i2 在其中为 16 , 这时候运行下面方法

fci.f2.invoke(fci.i2, obj, args)

即,UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5invoke() 方法

@Override
public Object invoke(int n, Object object, Object[] objectArray) throws InvocationTargetException {
UserDaoImpl$$EnhancerByCGLIB$.f32f6ae2 f32f6ae22 = (UserDaoImpl$$EnhancerByCGLIB$.f32f6ae2)object;
try {
switch (n) {
//XXXXX 省略
case 16: {
return new Boolean(f32f6ae22.CGLIB$insert$0((String)objectArray[0]));
}
//XXXXX 省略
}
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}

可以看到,他进行调用的是 UserDaoImpl$$EnhancerByCGLIB$f32f6ae2 中的 CGLIB$insert$0() 方法

final boolean CGLIB$insert$0(String string) {
return super.insert(string);
}

这里,才是真正调用到了父类(目标类)中对应的方法。至此,整个的调用流程完毕。

流程总结

  1. 首先生成代理对象。创建增强类enhancer,设置代理类的父类,设置回调拦截方法,返回创建的代理对象;

  2. 调用代理类中的方法。这里调用的代理类中的方法实际上是重写的父类的拦截。重写的方法中会去调用intercept方法;

  3. 调用intercept,方法中会对调用代理方法中的invokeSuper方法。而在 invokeSuper 中维护了一个 FastClassInfo类,其包含四个属性字段,分别为FastClass f1(目标类)FastClass f2 (代理类)int i1(目标类要执行方法的下标)int i2(代理类要执行方法的下标); invokeSuper中会调用的为代理类中的对应方法(代理类继承于父类的时候,对于其父类的方法,自己会生成两个方法,一个是重写的方法,一个是代理生成的方法,这里调用的即是代理生成的方法);

  4. 调用代理类中的代理方法。代理方法中通过super.xxxx(string)来实际真正的调用要执行的方法;

思考

JDK动态代理CGLib动态代理 有什么本质区别?

首先我们可以回想一下JDK动态代理CGLib动态代理,两者代理类中的区别:

//CGLib
private static Enhancer enhancer = new Enhancer();
private static CglibProxy cglibProxy = new CglibProxy();

public static Object getProxy(Class cls) {
enhancer.setSuperclass(cls);
enhancer.setCallback(cglibProxy);
return enhancer.create();
}

public static void main(String[] args) throws InterruptedException {
UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);
userDao.insert("zc");
}
//JDK
public static Object getProxy(Object proxyObj) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
proxyObj.getClass().getInterfaces(), new MyInvocationHandler(proxyObj));
}

public static void main(String[] args) {
AgeDao ageDao = (AgeDao) getProxy(new NameAndAgeDaoImpl());
ageDao.addAge(20);
}

我理解,这2种动态代理,最本质的区别就是:JDK动态代理是基于委托思想,而CGLib动态代理是基于继承的思想。

基于委托思想,JDK生成动态代理类的时候,需要传入被代理类(被委托类)的对象,可以看作是对象级别的重用机制

基于继承思想,动态代理类继承了被代理类,理论上父类的所有开放方法对于子类都是可见的,可以看作是级别的重用机制;

而怎么理解上面的话呢,这就要回归到动态代理的本质:

动态代理 = 拦截器机制 + 回溯到被代理类的能力

  • 对于JDK动态代理:

JDK动态代理 = 拦截器机制(InvocationHandler) + 回溯到被代理类的能力(反射调用被代理类对象相关方法)

JDK动态代理中,生成的代理类的全限定类名是com.sun.proxy.$ProxyN(N是正整数,比如$Proxy0),它继承了com.sun.proxy类,该类中存在一个InvocationHandler类型的h成员变量,它就是拦截器。但这里会存在一个问题,由于我们希望代理类和被代理类在行为上是一致的(具有相同的类型),所以JDK动态代理需要引入接口的概念,代理对象和被代理对象需要具有相同的接口定义。

所以,在我们使用JDK动态代理的过程中,我们需要自定义拦截器,实现InvocationHandler 接口,然后将被代理对象(被委托对象)注入到拦截器中。当调用接口方法时,会首先调用拦截器的invoke方法,拦截器invoke方法内部,会经过反射去调用被代理对象的相应方法。

public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行目标方法对象
Object result = method.invoke(target, args);
return result;
}
}
  • 对于CGLIB动态代理:

CGLIB动态代理 = 拦截器机制(MethodInterceptor) + 回溯到被代理类的能力 (FastClass辅助类、MethodProxy类)

CGLIB动态代理中,生成的代理类的全限定类名是很自由的。因为它是基于继承机制,代理类继承了被代理类。 在生成的代理类中,会存在一个MethodInterceptor类型的CGLIB$CALLBACK_0成员变量,它就是拦截器。由于是继承,代理类天然就可以调用到父类(被代理类)的方法,因此这里不再需要注入被代理类的对象实例了。但这里仍然存在一个很核心的问题:代理类看起来,既要能够调用到拦截器,又要可以回溯到父类(被代理类)的原始方法,这看起来很矛盾。怎么解决呢?

其实很简单,CGLIB生成的代理,对于被代理类的原有方法(比如上面的insert方法),会调用到拦截器。而与此同时,CGLIB还增加了隐藏的能够回溯到原始方法的传送门方法(比如CGLIB$insert$0),这样就可以两全其美了。

可是问题又来了,拦截器是我们自己来实现并添加业务自定义逻辑的,当我们想要在拦截器里调用到原始的被代理对象的insert方法,该如何去实现呢?

一种可行的方式是使用反射,调用代理对象的隐藏传送门CGLIB$insert$0方法。不得不说,这的确是可行的,但成本也非常大,你需要提前反编译动态代理的源码,找到对应的方法名。而动态代理之所以被称为动态代理,核心在于它是在jvm运行期动态生成的,所以这并不符合我们的初衷。而且要让拦截器对所有的方法进行适配,这显然也不现实。

我们说,没有什么问题不能通过加一层解决,CGLIB又一次证明了它的正确性。为了解决这个问题,CGLIB框架引入了MethodProxy的概念。针对每一个被代理对象的方法(比如insert),都有一个MethodProxy,它对外提供了invokeSuperinvoke方法,分别可以路由到CGLIB$insert$0insert方法。

所以,我们在拦截器做拦截操作时,直接调用对应MethodProxyinvokeSuper就可以路由到代理对象的隐藏传送门方法啦。

public class CglibProxy implements MethodInterceptor {   
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//执行目标方法对象
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
  • 两者区别
  1. JDK动态代理基于接口实现,必须先定义接口

    CGLib动态代理基于被代理类实现,可以直接定义或者实现接口的类

  2. JDK动态代理需要实现InvocationHanlder接口,加上反射机制实现代理类

    CGLib动态代理需要实现MethodInterceptor接口,对于代理类不可使用final修饰

  3. JDK动态代理是委托机制,委托hanlder,生成新的委托类,调用实现类方法;

    CGLib动态代理则使用继承机制,被代理类和代理类是继承关系,直接调用其中方法;

在其中FastClass类发挥了什么作用呢?为什么要有FastClass类

我在上面称FastClass类为辅助类:

  • 首先在invoke()invokeSuper()中都存在FastClass
public Object invoke(Object obj, Object[] args) throws Throwable {	
//初始化
return fci.f1.invoke(fci.i1, obj, args);
//XXXXX 省略
}

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
//初始化
return fci.f2.invoke(fci.i2, obj, args);
//XXXXX 省略
}

private static class FastClassInfo{
FastClass f1; // UserDaoImpl$$FastClassByCGLIB$$481edb7a
FastClass f2; // UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5
int i1;
int i2;
}

在上面的举例中可以看到,在生成的代理类、被代理类的FastClass类中,有 getindex()、invoke()等,获取索引,通过索引调用方法等。所以我称为 FastClass辅助类

在JDK动态代理中,调用目标对象的方法使用的是反射,而在CGLIB动态代理中使用的是FastClass机制

  1. FastClass使用:动态生成一个继承FastClass的类,并向类中写入委托对象,直接调用委托对象的方法。
  2. FastClass逻辑:在继承FastClass的动态类中,根据方法签名(方法名字+方法参数)得到方法索引,根据方法索引调用目标对象方法。
  3. FastClass优点:FastClass用于代替Java反射,避免了反射造成调用慢的问题。

明明下面两个方法中都有 super.xxxx(string) , 但是使用的是 invokeSuper() ,而不是 invoke()

看下这两个方法:

final boolean CGLIB$insert$0(String string) {   
return super.insert(string);
}
public final boolean insert(String string) {   
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);
return object == null ? false : (Boolean)object;
}
return super.insert(string);
}
  • 如果使用invokeSuper()
public Object invokeSuper(Object obj, Object[] args) throws Throwable {        
try {
init();
FastClassInfo fci = fastClassInfo;
//执行被代理类FastClass 的对应 i2 索引的方法
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}

就是按照上面讲的步骤,先进行 insert() 方法,经过intercept,最终可以运行到 CGLIB$insert$0() ,调用到了父类(目标类)中对应的方法。

  • 如果使用invoke()
public Object invoke(Object obj, Object[] args) throws Throwable {    
try {
init();
FastClassInfo fci = fastClassInfo;
//执行代理类FastClass 的对应 i1 索引的方法
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalArgumentException e) {
if (fastClassInfo.i1 < 0)
throw new IllegalArgumentException("Protected method: " + sig1);
throw e;
}
}

i1 的值通过下面方法获取为 1

@Overridepublic int getIndex(Signature signature) {   
String string = ((Object)signature).toString();
switch (string.hashCode()) {
//XXXXX 省略
case -982250262: {
if (!string.equals("insert(Ljava/lang/String;)Z"))
break;
return 1;
}
//XXXXX 省略
}
return -1;
}

接着,执行对应方法

@Overridepublic Object invoke(int n, Object object, Object[] objectArray) throws InvocationTargetException {   
UserDaoImpl userDaoImpl = (UserDaoImpl)object;
try {
switch (n) {
//XXXXX 省略
case 1: {
return new Boolean(userDaoImpl.insert((String)objectArray[0]));
}
//XXXXX 省略
}
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}

先进行 insert() 方法,经过intercept,通过 invoke() 方法,再次进入insert()方法,继而是一直死循环。