系列文章索引:
- Spring事务Transactional和动态代理(一)-JDK代理实现
- Spring事务Transactional和动态代理(二)-cglib动态代理
- Spring事务Transactional和动态代理(三)-事务失效的场景
什么是cglib
Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。JDK必须强制基于interface接口类型:Spring事务Transactional和动态代理(上)-JDK代理实现
cglib的应用
cglib应用很广泛,根据cglib在Github上的描述(cglib),存在以下应用:
-
Byte Code Engineering Library
也就是JavaClass字节码文件,这个库可以很方便的分析,创建和操作字节码文件 -
XORM
是一个可扩展的ORM框架,使用cglib来生成持久化对象,为RDBMS提供了映射到接口的持久Entity,让开发人员专注于业务对象模型 -
Hibernate
Hibernate是一个又一个强大的、超高性能的Java对象/关系持久性框架。可以开发持久对象,包括关联、继承、多态性、组合和Java集合框架 -
The Java Class File Editor
Java类文件编辑器,允许用户在磁盘上或在运行时加载类时读取/修改Class文件,也它可以动态地创建新类 -
Nanning Aspects
是一个基于java的简介AOP框架 - Spring
- iBatis/Mybatis
- ASM
-
Proxool
基于java的连接池 - Guice
- ModelMapper
cglib的使用
使用cglib需要先引入jar包,在maven中添加依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
新建一个目标类,其中一个为final方法,一个为非final方法,用于对比cglib对于两种方法的织入结果:
public class Student {
public void study(){
System.out.println("study");
}
public final void eat(){
System.out.println("eat");
}
}
Interceptor 代理类如下:
public class CglibInterceptor implements MethodInterceptor {
//织入前的处理
private void beforeInvoke(Method method){
System.out.println("before " + method.getName());
}
//织入后的处理
private void afterInvoke(Method method){
System.out.println("after " + method.getName());
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
beforeInvoke(method);
//调用cglib的invokeSuper而不是invoke方法
Object object = methodProxy.invokeSuper(o,objects);
afterInvoke(method);
return object;
}
}
测试类的调用顺序为
- 创建增强建Enhancer实例
- 通过setSuperclass方法来设置目标类
- 通过setCallback设置Interceptor拦截
- 调用Enhancer的create方法生成代理类
代码如下:
public class CglibTesst {
public static void main(String[] args) {
//把生产的代理类保存到磁盘指定文件夹
System.getProperties().put(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Student.class);
enhancer.setCallback(new CglibInterceptor());
Student studentProxy = (Student) enhancer.create();
studentProxy.study();
studentProxy.eat();
}
}
其中的输出如下,可以看到只有非final方法study织入了before和after逻辑,而final方法eat是没有的:
before study
study
after study
eat
cglib生成的代理class文件分析
通过在测试类中加入了
System.getProperties().put(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
代码之后,本地就多出来了一些.class文件如下:
首先看一下Student$EnhancerByCGLIB$92f3e3f6,继承了Student并且实现了Factory接口(接口方法主要是newInstance,setCallback和getCallbacks),该类中的代码太多,以下代码是节选:
public class Student$EnhancerByCGLIB$92f3e3f6 extends Student implements Factory {
//静态初始化类
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.randy.dynamicproxy.cglib.Student$$EnhancerByCGLIB$$92f3e3f6");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
CGLIB$study$0$Method = ReflectUtils.findMethods(new String[]{"study", "()V"}, (var1 = Class.forName("com.randy.dynamicproxy.cglib.Student")).getDeclaredMethods())[0];
CGLIB$study$0$Proxy = MethodProxy.create(var1, var0, "()V", "study", "CGLIB$study$0");
}
static {
CGLIB$STATICHOOK1();
}
final void CGLIB$study$0() {
super.study();
}
public final void study() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
//检查当前Callback拦截对象
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
//根据是否存在判定是通过拦截类来调用还是直接调用父类Student的study方法
if (var10000 != null) {
var10000.intercept(this, CGLIB$study$0$Method, CGLIB$emptyArgs, CGLIB$study$0$Proxy);
} else {
super.study();
}
}
final boolean CGLIB$equals$1(Object var1) {
return super.equals(var1);
}
public final boolean equals(Object var1) {
...
}
final String CGLIB$toString$2() {
return super.toString();
}
public final String toString() {
...
}
final int CGLIB$hashCode$3() {
return super.hashCode();
}
public final int hashCode() {
...
}
final Object CGLIB$clone$4() throws CloneNotSupportedException {
return super.clone();
}
protected final Object clone() throws CloneNotSupportedException {
...
}
}
可以看到该生成类中除了实现Factory接口的方法以外,都复写了Student类以及超类Object中的非final方法(对于Student中的final方法eat和Object中的final方法wati,notify,notifyAll等方法都没有复写),这也就是为什么cglib无法对final方法进行代理,因为java不允许复写final方法
另外两个类 Student$EnhancerByCGLIB\(92f3e3f6\)FastClassByCGLIB\(1d02f934 和 Student\)FastClassByCGLIB$ec571eb6 都继承了cglib的抽象类FastClass,
主要是实现了FastClass的一下几个方法
public abstract int getIndex(String var1, Class[] var2);
public abstract int getIndex(Class[] var1);
public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;
public abstract Object newInstance(int var1, Object[] var2) throws InvocationTargetException;
public abstract int getIndex(Signature var1);
public abstract int getMaxIndex();
其中的
cglib的原理
cglib动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法(cglib无法对final方法进行代理)。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
CGLIB底层使用字节码处理框架ASM,来转换字节码并生成新的类。关于java字节码请查看:The Java class File Format
Enhancer类源码分析
public class Enhancer extends AbstractClassGenerator {
//设置目标类作为父类,也就是对应生成的Student$$EnhancerByCGLIB$$92f3e3f6类继承了Student
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.isInterface()) {
this.setInterfaces(new Class[]{superclass});
} else if (superclass != null && superclass.equals(Object.class)) {
this.superclass = null;
} else {
this.superclass = superclass;
}
}
//通过Enhancer来创建代理类
public Object create() {
this.classOnly = false;
this.argumentTypes = null;
return this.createHelper();
}
private Object createHelper() {
this.preValidate();
//根据当前设置的父类等信心构造一个唯一的key
Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
}
protected Object create(Object key) {
try {
ClassLoader loader = this.getClassLoader();
Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
//首先从缓存中查找key,如果就生成一个
AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
if (data == null) {
Class var5 = AbstractClassGenerator.class;
synchronized(AbstractClassGenerator.class) {
cache = CACHE;
data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
if (data == null) {
Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);
//核心是调用了AbstractClassGenerator的generate来生成字节码文件,并通过ReflectUtils.defineClass返回
data = new AbstractClassGenerator.ClassLoaderData(loader);
//加入缓存
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
Object obj = data.get(this, this.getUseCache());
//如果是类就通过firstInstance初始化,而firstInstance在AbstractClassGenerator类中是一个抽象方法,具体实现如下
//firstInstance和nextInstance都是通过cglib的ReflectUtils.newInstance来创建实例的
return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
} catch (RuntimeException var9) {
throw var9;
} catch (Error var10) {
throw var10;
} catch (Exception var11) {
throw new CodeGenerationException(var11);
}
}
MethodProxy
当所生成的代理类被调用的时候,MethodProxy会在所设置的CallBack中调用intercept方法。而在上面的CglibInterceptor类的intercept方法中就是使用的MethodProxy.invokeSuper方法,源码如下:
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();
}
}
init方法:
init()方法是一个经典的双重检查单例设计模式,初始判断对象是否已经初始化了,如果没有就加锁并再次判空。初始化的内容主要是FastClassInfo对象及其属性
private final Object initLock = new Object();
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);
//通过getIndex来查找到指定方法的索引
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}
FastClass机制
FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,在上述的其中的invokeSuper中init初始化的主要就是FastClassInfo(内部类,持有两个FastClass类型的变量)。
private static class FastClassInfo
{
//目标类的FastClass
FastClass f1;
//代理类的FastClass
FastClass f2;
//目标类方法的索引
int i1;
//代理类方法的索引
int i2;
}
在上一篇JDK代理实现 中提到JDK拦截对象是通过InvocationHandler反射的机制来调用被拦截方法的,反射的效率比较低。
而cglib是对一个类的方法建立索引,通过索引来直接调用相应的方法。
如生成的Student\(FastClassByCGLIB\)ec571eb6就是继承了FastClass,通过getIndex(Signature)通过方法签名来定位一个索引,
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1310345955:
if (var10000.equals("eat()V")) {
return 1;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return 2;
}
break;
case 1876544780:
if (var10000.equals("study()V")) {
return 0;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return 3;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return 4;
}
}
return -1;
}
在根据获取的的Index位置来调用invoke方法,invoke方法在FastClass类中是一个抽象方法,子类(也就是生成的Student\(FastClassByCGLIB\)ec571eb6继承FastClass)具体实现如下:
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
Student var10000 = (Student)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.study();
return null;
case 1:
var10000.eat();
return null;
case 2:
return new Boolean(var10000.equals(var3[0]));
case 3:
return var10000.toString();
case 4:
return new Integer(var10000.hashCode());
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
}
参考:
- https://github.com/cglib/cglib/wiki
- https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html
- https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
- https://www.baeldung.com/cglib
- https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/index.html