前篇文章動態代理(一)——JDK中的動態代理中詳細介紹了JDK動態代理的Demo實現,api介紹,原理詳解。這篇文章繼續討論Java中的動態代理,並提及了Java中動態代理的幾種實現方式。這里繼續介紹CGLIB代理方式。
CGLIB動態代理在AOP、RPC中都有所使用,是Java體系中至關重要的一塊內容。本篇文章的主要目標:
- 掌握使用CGLIB生成代理類
- 深入理解CGLIB的代理原理
從以上目標出發,本篇文章主要從以下幾個方面逐步深入探索CGLIB:
- CGLIB的使用Demo
- CGLIB重要API介紹
- CGLIB代理原理
- 總結
一.CGLIB的使用Demo
使用CGLIB的大致分為四步驟:
- 創建被代理對象
- 創建方法攔截器
- 創建代理對象
- 調用代理對象
1.創建被代理對象
public class EchoServiceImpl implements EchoService {
public void echo(String message) {
System.out.println(message);
}
public void print(String message) {
System.out.println(message);
}
public int test() {
return 1;
}
public final void finalTest() {
System.out.println("I am final method.");
}
}
2.創建方法攔截器
public class EchoServiceInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before invoking!");
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("after invoking!");
return object;
}
}
接口MethodInterceptor是CGLIB庫提供,用於應用開發者根據自己的業務邏輯進行擴展實現。
3.創建代理對象
//Enhancer是生成代理類的工廠
Enhancer enhancer = new Enhancer();
//設置代理的超類,即被代理對象
enhancer.setSuperclass(EchoServiceImpl.class);
//設置攔截方法
enhancer.setCallback(new EchoServiceInterceptor());
//生成代理對象
EchoService echoService = (EchoService) enhancer.create();
4.調用代理對象
echoService.echo("test");
執行結果:
before invoking!
test
after invoking!
二.CGLIB重要API介紹
CGLIB庫的體積很小,但是學習難度確非常高,畢竟涉及到bytecode。所以該篇文章后續關於原理介紹,只涉及代理原理方面,關於如何生成代理對象的方面,由於個人能力所及,不敢妄加說明。
下面是CGLIB的包結構,每個包都是負責一個模塊功能,定義非常明確,負責單一的功能職責:
-
net.sf.cglib.core
低級別的字節碼操作的類,它們直接與ASM相關 -
net.sf.cglib.transform
在運行或者構建時轉換類文件的一些類 -
net.sf.cglib.proxy
創建代理和方法攔截器定義的類 -
net.sf.cglib.reflect
實現快速反射的一些基礎類 -
net.sf.cglib.util
用於集合排序的一些工具類 -
net.sf.cglib.beans
與JavaBean相關的類
雖然cglib包含了如此多的功能模塊,但是對於使用者,我們並不需要關注如此多的細節,只需要掌握幾個重要的接口:

在看完上面的Demo,應該對Enhancer有一定了解。Enhancer字面義即增強,也正如其表述,Enhancer就是用來創建代理對象的接口。其中create方法可以生成代理對象,實際就是工廠模式。
在生成對象前,需要做關於代理方面的配置:
- 配置被代理對象(目標),即setSuperClass設置超類型,該superClass即Enhancer中持有的Class對象;
- 配置統一攔截方法(中間人),即setCallBack設置回調接口,對應上圖的CallBack。AOP的實現使用methodInterpretor型CallBack;
- 可選性的配置攔截過濾器(核驗流程),即setCallBackFilter,對應上圖的CallBackFiler;
Enhancer的create api提供了生成代理對象的。以上即在編寫cglib動態代理過程中使用的幾個重要api。雖然字節碼技術是非常晦澀深奧,但是cglib以簡單易用的api使字節碼增強技術變得非常容易上手。
通過以上的demo示例和幾個重要api的介紹應該都能掌握使用cglib庫生成代理類。下面依然通過反編譯的方式繼續深入cglib的動態代理的調用原理。
三.CGLIB代理原理
1.整體架構與調用過程概覽
在詳細查看被代理對象的原理之前,先了解下cglib的整體架構圖:

從圖中可以看出,cglib在字節碼層面的操作技術主要依賴ASM提供的能力。在上節中提到的net.sf.cglib.core包,正是與ASM相關。CGLIB上層直接面向應用層,將深奧晦澀的字節碼技術包裝成應用易用能理解的api,為aop,dynaminc proxy等技術提供了實現基礎。
在反編譯看代理對象的源代碼之前,先看下代理調用的過程圖:

從圖中可以看出:
- 客戶端調用代理對象的被代理方法
- 代理對象將調用委派給方法攔截器統一接口intercept
- 方法攔截器中執行前置操作,然后調用方法代理的統一接口invokeSuper
- 方法代理的invokeSuper初始化代理對象的和被代理對象的fastClass
- 初始化后,再調用代理對象的fastClass
- 代理對象的fastClass能夠fast的調用代理的代理對象
- 代理對象再調用被代理對象的被代理方法
- 調用棧彈出,到intercept中再執行后置操作,方法調用結束
通過以上的過程再來看下它們之間的UML:

在Enhancer中的配置的被代理對象、統一回調的最終都被聚合到生成的代理對象中。(工廠模式,零件組裝成產品)
代理對象聚合同一方法回調、繼承被代理對象,聚合方法代理的。
2.反編譯代理類字節碼
CGLIB提供生成代理類的class文件的配置項。在CGLIB中提供了DebuggingClassWriter類用於將字節碼的byte字節寫入class文件中。
public byte[] toByteArray() {
return (byte[]) java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
// 獲取代理類的字節內容
byte[] b = ((ClassWriter) DebuggingClassWriter.super.cv).toByteArray();
if (debugLocation != null) {
// 轉換生成的class文件路徑分隔符
String dirs = className.replace('.', File.separatorChar);
try {
// 創建class文件
new File(debugLocation + File.separatorChar + dirs).getParentFile().mkdirs();
File file = new File(new File(debugLocation), dirs + ".class");
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
try {
// 將字節內容寫入class文件
out.write(b);
} finally {
out.close();
}
if (traceCtor != null) {
file = new File(new File(debugLocation), dirs + ".asm");
out = new BufferedOutputStream(new FileOutputStream(file));
try {
ClassReader cr = new ClassReader(b);
PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
ClassVisitor tcv = (ClassVisitor)traceCtor.newInstance(new Object[]{null, pw});
cr.accept(tcv, 0);
pw.flush();
} finally {
out.close();
}
}
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
return b;
}
});
}
從以上可以看出只要配置文件的生成路徑變量debugLocation即可,再來看下該變量初始化賦值情況
static {
// 從System中取出屬性DEBUG_LOCATION_PROPERTY賦值給文件class文件生成路徑變量
debugLocation = System.getProperty(DEBUG_LOCATION_PROPERTY);
if (debugLocation != null) {
System.err.println("CGLIB debugging enabled, writing to '" + debugLocation + "'");
try {
Class clazz = Class.forName("org.objectweb.asm.util.TraceClassVisitor");
traceCtor = clazz.getConstructor(new Class[]{ClassVisitor.class, PrintWriter.class});
} catch (Throwable ignore) {
}
}
}
可以看出只要應用啟動時
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
"/Users/lixinyou/Documents/code-space/java/java-base/java-proxy/target/proxy/impl");
設置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY屬性,在運行時CGLIB便會生成代理類的class。
這種方式與JDK的動態代理中的class文件生成方式一致。這種用法,在日常應用開發中也可借鑒,利用應用啟動參數的不同,可以在運行時改變行為,代碼具有強擴展性。
3.fastClass機制
在看代理機制源碼之前,做好一切准備。再來了解下CGLIB中關於實現快速調用的fastClass機制。
在JDK的動態代理中使用反射調用目標對象,在CGLIB中為了更好的提升性能,采用fastClass機制。
FastClass機制:將類的方法信息解析出來,然后為其建立索引。調用的時候,只要傳索引,就能找到相應的方法進行調用。
- 為所有的方法建立索引
- 調用前先根據方法信息尋找到索引
- 調用時根據索匹配相應的方法進行直接調用
CGLIB在字節碼層面將方法和索引的對應關系建立,避免了反射調用:
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -2055565910:
if (var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
return 11;
}
break;
case -1980342926:
if (var10000.equals("print(Ljava/lang/String;)V")) {
return 6;
}
break;
case -1860420502:
if (var10000.equals("CGLIB$clone$7()Ljava/lang/Object;")) {
return 24;
}
break;
case -1725733088:
if (var10000.equals("getClass()Ljava/lang/Class;")) {
return 29;
}
break;
}
return -1;
}
上述獲取方法的索引,下述代碼再根據索引進行調用:
public Object invoke(int index, Object var2, Object[] var3) throws InvocationTargetException {
e77dd5ce var10000 = (e77dd5ce)var2;
try {
switch(index) {
case 0:
return new Boolean(var10000.equals(var3[0]));
case 1:
return var10000.toString();
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
4.運行時的代理類
CGLIB運行時,實際會生成三個class:
- 代理類
- 代理類對應的fastClass
- 被代理類的fastClass
如上述Demo中生成的代理類和相應的代理類:
- EchoServiceImpl$$EnhancerByCGLIB$$e77dd5ce$$FastClassByCGLIB$$1b37f797.class
- EchoServiceImpl$$EnhancerByCGLIB$$e77dd5ce.class
- EchoServiceImpl$$FastClassByCGLIB$$44f86581.class
下面就看下生成的類的代碼片段,理解下CGLIB的運行時代理原理。
首先看下生成的代理類
public class EchoServiceImpl$$EnhancerByCGLIB$$e77dd5ce extends EchoServiceImpl implements Factory {
代理類是繼承自被代理類,這里與JDK的不同是,JDK是實現了接口。
下面的即是對echo方法的代理方法:
public final void echo(String var1) {
// 獲取方法攔截器
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) { // 如果不為空,則調用其統一攔截
var10000.intercept(this, CGLIB$echo$2$Method, new Object[]{var1}, CGLIB$echo$2$Proxy);
} else { // 如果為空,則直接調用父類即被代理類的方法
super.echo(var1);
}
}
最為讓人關注的是intercept方法調用時的參數MethodProxy:CGLIB$echo$2$Proxy
static {
CGLIB$STATICHOOK1();
}
static void CGLIB$STATICHOOK1() {
CGLIB$echo$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "echo", "CGLIB$echo$2");
}
在代理類被加載時,執行靜態方法CGLIB$STATICHOOK1(),創建了echo方法對應的方法代理。
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賦值。c1代表被代理類,c2代表代理類。desc代理方法和被代理方法的參數信息,name1是被代理方法名,name2是代理方法名。
下面再看來下intercepte方法中調用的methodProxy的invokeSuper方法:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
// 先進行初始化
init();
// 獲取fastClassInfo對象
FastClassInfo fci = fastClassInfo;
// 獲取代理類對應的fastClass對象並按索引調用
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
先看下初始化中所執行的邏輯:
private void init()
{
/*
* Using a volatile invariant allows us to initialize the FastClass and
* method index pairs atomically.
*
* Double-checked locking is safe with volatile in Java 5. Before 1.5 this
* code could allow fastClassInfo to be instantiated more than once, which
* appears to be benign.
*/
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對象是單例。所以初始化方法只會在第一次調用代理方法的時候,才響應的進行對其初始化。
初始化后,就將代理類和代理方法的索引獲取到了,然后再按照索引直接對代理方法進行調用:
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
e77dd5ce var10000 = (e77dd5ce)var2;
int var10001 = var1;
try {
switch(var10001) {
case 19:
var10000.CGLIB$echo$2((String)var3[0]);
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
上述考慮到篇幅和簡潔的原因,這里只摘取了case:19的代碼片段。
通過索引直接調用代理類代理方法:CGLIB$echo$2:
final void CGLIB$echo$2(String var1) {
super.echo(var1);
}
在該方法中再調用被代理類(繼承了被代理類)的被代理方法。
至此,CGLIB的代理調用原理就是以上的內容。
四.總結
CGLIB是一種字節碼增強庫,利用其提供的字節碼技術可以實現動態代理。其底層依賴ASM字節碼技術。
CGLIB的動態代理與JDK動態代理的不同點:
- JDK動態代理必須需要接口,JDK代理是基於接口進行動態代理。CGLIB中既支持對接口的代理,也支持對對象的代理。
- CGLIB動態代理使用fastClass機制實現快速調用被代理類,JDK中使用了反射方式調用被代理。所以CGLIB的動態代理的方式性能上更有優勢。
- CGLIB額外對來源於Object中的finalize和clone方法也做了攔截代理,JDK只為了equals、hashCode、toString進行代理
注:JDK中生成的代理類已經靜態解析了方法對象作為代理類的靜態變量,類似做緩存,從而部分解決反射的性能問題。
CGLIB的動態代理與JDK動態代理的相同點:
- 都具有統一接口,JDK動態代理中中間統一接口是InvocationHandler,CGLIB中是MethodInteceptor。
- 生成的代理類和其中的方法都是final
參考
neoremind/dynamic-proxy
Are there alternatives to cglib
Spring AOP 實現原理與 CGLIB 應用
深入淺出CGlib-打造無入侵的類代理
CGLib: The Missing Manual
Create Proxies Dynamically Using CGLIB Library