<img src="https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210925233820.png" width = "500" height = "400" alt="圖片名稱" align=center />
cglib 動態代理
cglib介紹
CGLIB 是一個開源專案,一個強大高效能高質量的程式碼生成庫,可以在執行期擴充 Java 類,實現 Java 介面等等。底層是使用一個小而快的位元組碼處理框架 ASM,從而轉換位元組碼和生成新的類。
理論上我們也可以直接用 ASM 來直接生成程式碼,但是要求我們對 JVM 內部,class 檔案格式,以及位元組碼的指令集都很熟悉。
這玩意不在 JDK 的包裡面,需要自己下載匯入或者 Maven 座標匯入。
我選擇 Maven
匯入, 加到 pom.xml
檔案:
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
Student.java
:
public class Student {
public void learn() {
System.out.println("我是學生,我想學習");
}
}
MyProxy.java
(代理類)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class StudentProxy implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println("代理前 -------");
proxy.invokeSuper(obj, args);
System.out.println("代理後 -------");
return null;
}
}
測試類(Test.java
)
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args) {
// 代理類class檔案存入本地磁碟方便我們反編譯檢視原始碼
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/aphysia/Desktop");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Student.class);
enhancer.setCallback(new StudentProxy());
Student student = (Student) enhancer.create();
student.learn();
}
}
執行之後的結果是:
CGLIB debugging enabled, writing to '/Users/xuwenhao/Desktop'
代理前 -------
我是學生,我想學習
代理後 -------
在我們選擇的資料夾裡面,生成了代理類的程式碼:
原始碼分析
我們先要代理的類,需要實現MethodInterceptor
(方法攔截器)介面,這個介面只有一個方法 intercept
,引數分別是:
- obj:需要增強的物件
- method:需要攔截的方法
- args:要被攔截的方法引數
- proxy:表示要觸發父類的方法物件
package net.sf.cglib.proxy;
import java.lang.reflect.Method;
public interface MethodInterceptor extends Callback {
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
再看回我們要建立代理類的方法 enhancer.create()
,這個方法的意思:如果需要,生成一個新類,並使用指定的回撥(如果有的話)來建立一個新的物件例項。使用超類的無引數建構函式。
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}
主要的方法邏輯我們得看 createHelper()
,除了校驗,就是呼叫 KEY_FACTORY.newInstance()
方法生成 EnhancerKey
物件,KEY_FACTORY
是靜態 EnhancerKey
介面,newInstance()
是介面裡面的一個方法,重點在super.create(key)
裡面,呼叫的是父類的方法:
private Object createHelper() {
// 校驗
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
AbstractClassGenerator
是 Enhancer
的父類,create(key)
方法的主要邏輯是獲取類載入器,快取獲取類載入資料,然後再反射構造物件,裡面有兩個創造例項物件的方法:
fistInstance()
: 不應該在常規流中呼叫此方法。從技術上講,{@link #wrapCachedClass(Class)}
使用{@link EnhancerFactoryData}
作為快取值,後者支援比普通的舊反射查詢和呼叫更快的例項化。出於向後相容性的原因,這個方法保持不變:只是以防它曾經被使用過。(我的理解是目前的邏輯不會走到這個分支,因為它比較忙,但是為了相容,這個case還儲存著),內部邏輯其實用的是ReflectUtils.newInstance(type)
。nextInstance()
: 真正的建立代理物件的類
protected Object create(Object key) {
try {
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
data = new ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
// 真正建立物件的方法
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
這個方法定義在AbstractClassGenerator
,但是實際上是呼叫子類 Enhancer
的實現,主要是通過獲取引數型別,引數,以及回撥物件,用這些引數反射生成代理物件。
protected Object nextInstance(Object instance) {
EnhancerFactoryData data = (EnhancerFactoryData) instance;
if (classOnly) {
return data.generatedClass;
}
Class[] argumentTypes = this.argumentTypes;
Object[] arguments = this.arguments;
if (argumentTypes == null) {
argumentTypes = Constants.EMPTY_CLASS_ARRAY;
arguments = null;
}
// 構造
return data.newInstance(argumentTypes, arguments, callbacks);
}
內部實現邏輯,呼叫的都是ReflectUtils.newInstance()
, 引數種類不一樣:
public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
setThreadCallbacks(callbacks);
try {
if (primaryConstructorArgTypes == argumentTypes ||
Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
return ReflectUtils.newInstance(primaryConstructor, arguments);
}
return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
} finally {
setThreadCallbacks(null);
}
}
跟進去到底,就是獲取構造器方法,反射方式構造代理物件,最終呼叫到的是 JDK 提供的方法:
public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) {
return newInstance(getConstructor(type, parameterTypes), args);
}
public static Object newInstance(final Constructor cstruct, final Object[] args) {
boolean flag = cstruct.isAccessible();
try {
if (!flag) {
cstruct.setAccessible(true);
}
Object result = cstruct.newInstance(args);
return result;
} catch (InstantiationException e) {
throw new CodeGenerationException(e);
} catch (IllegalAccessException e) {
throw new CodeGenerationException(e);
} catch (InvocationTargetException e) {
throw new CodeGenerationException(e.getTargetException());
} finally {
if (!flag) {
cstruct.setAccessible(flag);
}
}
}
開啟它自動生成的代理類檔案看看,就會發現其實也是生成那些方法,加上了一些增強方法:
生成的代理類繼承了原來的類:
public class Student$$EnhancerByCGLIB$$929cb5fe extends Student implements Factory {
...
}
看看生成的增強方法,其實是呼叫到 intercept()
方法,這個方法由我們前面自己實現,因此就完成了代理物件增強的功能:
public final void learn() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$learn$0$Method, CGLIB$emptyArgs, CGLIB$learn$0$Proxy);
} else {
super.learn();
}
}
cglib 和 jdk 動態代理有什麼區別
- jdk 動態代理是利用攔截器加上反射生成了一個代理介面的匿名類,執行方法的時候交給 InvokeHandler 處理。CGLIB 動態代理是使用了 ASM框架,修改原來的位元組碼,然後生成新的子類來處理。
- JDK 代理需要實現介面,但是CGLIB不強制。
- 在JDK1.6之前,cglib因為用了位元組碼生成技術,比反射效率高,但是之後jdk也進行了一些優化,效率上已經提升了。
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java原始碼解析
,JDBC
,Mybatis
,Spring
,redis
,分散式
,劍指Offer
,LeetCode
等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。