系列文章索引:
- 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);
}
}
參考: