背景

 JDK 動態代理存在的一些問題:

呼叫效率低

 JDK 通過反射實現動態代理呼叫,這意味著低下的呼叫效率:

  1. 每次呼叫 Method.invoke() 都會檢查方法的可見性、校驗引數是否匹配,過程涉及到很多 native 呼叫,具體參見JNI 呼叫開銷

  2. 反射呼叫涉及動態類解析,這種不可預測性,導致被反射呼叫的程式碼無法被 JIT 內聯優化,具體參見反射呼叫方法

 可以通過java.lang.invoke.MethodHandle來規避以上問題,但是這不在本文討論的範圍。

只能代理介面

 java.lang.reflect.Proxy只支援通過介面生成代理類,這意味著 JDK 動態代理只能代理介面,無法代理具體的類。

 對於一些外部依賴或者現有模組來說,無法通過該方式實現動態代理。

應用場景

 CGLib 是一款用於實現高效動態代理的位元組碼增強庫,通過位元組碼生成技術,動態編譯生成代理類,從而將反射呼叫轉換為普通的方法呼叫。

 下面通過兩個案例體驗一下 CGLib 的使用方式。

案例一:Weaving

 現有一個輸出問候語句的類 Greet,現在有個新需求:在輸出內容前後加上姓名,實現個性化輸出。下面通過 CGLib 實現該功能:


class Greet { // 需要被增強目標類
public String hello() { return "hello"; }
public String hi() { return "hi"; }
public String toString() { return "@Greet"; }
} public class Weaving { // 模擬切面織入過程 // 增加 before: 字首(模擬前置通知)
static MethodInterceptor adviceBefore = (target, method, args, methodProxy) -> "before:" + methodProxy.invokeSuper(target, args); // 增加 :after 字尾(模擬後置通知)
static MethodInterceptor adviceAfter = (target, method, args, methodProxy) -> methodProxy.invokeSuper(target, args) + ":after"; // 通知
static Callback[] advices = new Callback[] { NoOp.INSTANCE/*預設*/, adviceBefore, adviceAfter }; // 切入點
static CallbackFilter pointCut = method -> {
switch (method.getName()) {
case "hello" : return 1; // hello() 方法植入前置通知
case "hi" : return 2; // hi() 方法植入後置通知
default: return 0; // 其他方法不新增通知
}
}; public static void main(String[] args) throws InterruptedException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Greet.class); // 設定目標類
enhancer.setCallbacks(advices); // 設定通知 Advice
enhancer.setCallbackFilter(pointCut); // 設定切點 PointCut
Greet greet = (Greet) enhancer.create(); // 建立 Proxy 物件
System.out.println(greet.hello());
System.out.println(greet.hi());
System.out.println(greet.toString());
TimeUnit.HOURS.sleep(1);
}
}

案例二:Introduction

 隨著業務發展,系統需要支援法語的問候 FranceGreet,在不修改現有業務程式碼的前提下,可以通過 CGLib 實現該功能:

interface FranceGreet { // 支援新功能的介面
String bonjour();
} class FranceGreeting implements Dispatcher { // 新介面的實現
private final FranceGreet delegate = () -> "bonjour";
@Override
public Object loadObject() throws Exception {
return delegate;
}
} class FranceGreetingMatcher implements CallbackFilter { // 將新介面呼叫委託給 Dispatcher
@Override
public int accept(Method method) {
return method.getDeclaringClass().equals(FranceGreet.class) ? 1 : 0;
}
} public class Introduction { // 模擬引入新介面 public static void main(String[] args) throws InterruptedException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Greet.class);
enhancer.setInterfaces(new Class[]{FranceGreet.class}); // 擴充套件新介面
enhancer.setCallbacks(new Callback[]{ NoOp.INSTANCE, new FranceGreeting()}); // 實現新介面
enhancer.setCallbackFilter(new FranceGreetingMatcher()); // 關聯介面與實現
Greet greet = (Greet) enhancer.create();
System.out.println(greet.hello()); // 原方法不受影響
System.out.println(greet.hi());
FranceGreet franceGreet = (FranceGreet) greet;
System.out.println(franceGreet.bonjour()); // 新介面方法正常呼叫
TimeUnit.HOURS.sleep(1);
}
}

原理簡析

 從前面的案例可以看到,CGLib 使用的方式很簡單,大致可以分為兩步:

  1. 配置 Enhancer
  • 設定需要代理的目標類與介面
  • 通過 Callback 設定需要增強的功能
  • 通過 CallbackFilter 將方法匹配到具體的 Callback
  1. 建立代理物件
  • 通過 CallbackFilter 獲取方法與 Callback 的關聯關係
  • 繼承目標類並重寫override方法,在呼叫程式碼中嵌入 Callback
  • 編譯動態生成的位元組碼生成代理類
  • 通過反射呼叫建構函式生成代理物件

Callback 分類

 此外,CGLib 支援多種 Callback,這裡簡單介紹幾種:

  • NoOp 不使用動態代理,匹配到的方法不會被重寫
  • FixedValue 返回固定值,被代理方法的返回值被忽略
  • Dispatcher 指定上下文,將代理方法呼叫委託給特定物件
  • MethodInterceptor 呼叫攔截器,用於實現環繞通知around advice

 其中 MethodInterceptor 最為常用,可以實現多種豐富的代理特性。

 但這類 Callback 也是其中最重的,會導致生成更多的動態類,具體原因後續介紹。

位元組碼生成過程

 底層通過 Enhancer.generateClass() 生成代理類,其具體過程不作深究,可以簡單概括為:

  1. 通過ClassVisitor獲取目標類資訊
  2. 通過ClassEmitter呼叫 asm 庫注入增強方法,並生成byte[] 形式的位元組碼
  3. 通過反射呼叫ClassLoader.defineClass()byte[]轉換為Class物件
  4. 將生成完成的代理類快取至LoadingCache,避免重複生成

生成的類結構

 通過 arthas 的 jad 命令可以觀察到,案例 Weaving 中實際生成了以下類:

  • 目標類:buttercup.test.Greet
  • 代理類:buttercup.test.Greet$$EnhancerByCGLIB(省略字尾)
  • 目標類 FastClass:buttercup.test.Greet$$FastClassByCGLIB(省略字尾)
  • 代理類 FastClass:buttercup.test.Greet$$EnhancerByCGLIB$$FastClassByCGLIB(省略字尾)

代理類

 代理類就是 Ehancer.create() 中為了建立代理物件動態生成的類,該類不但繼承了目標類,並且還重寫了需要被代理的方法。其命名規則為:目標類 + $$EnhancerByCGLIB

 在案例一中,我們分別給 Greet.hello()Greet.hi() 分別添加了攔截器Weaving.adviceBeforeWeaving.adviceAfter,下面我們分析代理類是如何完成這一功能的:

public class Greet$$EnhancerByCGLIB extends Greet implements Factory {

  private static final Object[] CGLIB$emptyArgs = new Object[0]; // 預設空引數
private static final Callback[] CGLIB$STATIC_CALLBACKS; // 靜態 Callback(忽略)
private static final ThreadLocal CGLIB$THREAD_CALLBACKS; // 用於給建構函式傳遞 Callback // 通過 MethodProxy 代理 Greet.hell() 方法
private static final Method CGLIB$hello$0$Method;
private static final MethodProxy CGLIB$hello$0$Proxy; // 通過 MethodProxy 代理 Greet.hi() 方法
private static final Method CGLIB$hi$1$Method;
private static final MethodProxy CGLIB$hi$1$Proxy; private boolean CGLIB$BOUND; // 判斷 Callback 是否已經初始化
private NoOp CGLIB$CALLBACK_0; // 預設不攔截,直接呼叫目標類方法
private MethodInterceptor CGLIB$CALLBACK_1; // Weaving.adviceBefore(增加 before: 字首)
private MethodInterceptor CGLIB$CALLBACK_2; // Weaving.adviceAfter(增加 :after 字尾) static {
Greet$$EnhancerByCGLIB.CGLIB$STATICHOOK1();
} static void CGLIB$STATICHOOK1() { // 靜態初始化
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
Class<?> clazz = Class.forName("buttercup.test.Greet");
Class<?> clazz2 = Class.forName("buttercup.test.Greet$$EnhancerByCGLIB");
Method[] methodArray = ReflectUtils.findMethods(new String[]{"hello", "()Ljava/lang/String;", "hi", "()Ljava/lang/String;"}, clazz.getDeclaredMethods());
CGLIB$hello$0$Method = methodArray[0];
CGLIB$hello$0$Proxy = MethodProxy.create(clazz, clazz2, "()Ljava/lang/String;", "hello", "CGLIB$hello$0");
CGLIB$hi$1$Method = methodArray[1];
CGLIB$hi$1$Proxy = MethodProxy.create(clazz, clazz2, "()Ljava/lang/String;", "hi", "CGLIB$hi$1");
} // 通過 ThreadLocal 傳參
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] callbackArray) {
CGLIB$THREAD_CALLBACKS.set(callbackArray);
} // 工廠方法,建立增強後的物件
public Object newInstance(Callback[] callbackArray) {
Greet$$EnhancerByCGLIB.CGLIB$SET_THREAD_CALLBACKS(callbackArray);
Greet$$EnhancerByCGLIB Greet$$EnhancerByCGLIB = new Greet$$EnhancerByCGLIB();
Greet$$EnhancerByCGLIB.CGLIB$SET_THREAD_CALLBACKS(null);
return Greet$$EnhancerByCGLIB;
} // 重寫 hello() 方法通知 CALLBACK_1
public final String hello() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_1;
if (methodInterceptor == null) { // 初始化 CALLBACK_1
Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_1;
}
if (methodInterceptor != null) { // 呼叫攔截器 Weaving.adviceBefore
return (String)methodInterceptor.intercept(this, CGLIB$hello$0$Method, CGLIB$emptyArgs, CGLIB$hello$0$Proxy);
}
return super.hello();
} // 重寫 hi() 方法通知 CALLBACK_2
public final String hi() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_2;
if (methodInterceptor == null) { // 初始化 CALLBACK_2
Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_2;
}
if (methodInterceptor != null) { // 呼叫攔截器 Weaving.adviceAfter
return (String)methodInterceptor.intercept(this, CGLIB$hi$1$Method, CGLIB$emptyArgs, CGLIB$hi$1$Proxy);
}
return super.hi();
} // 直接呼叫目標類的 hello()
final String CGLIB$hello$0() {
return super.hello();
} // 直接呼叫目標類的 hi()
final String CGLIB$hi$1() {
return super.hi();
} public Greet$$EnhancerByCGLIB() {
Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
} private static final void CGLIB$BIND_CALLBACKS(Object object) {
block2: {
Object object2;
Greet$$EnhancerByCGLIB Greet$$EnhancerByCGLIB;
block3: {
Greet$$EnhancerByCGLIB = (Greet$$EnhancerByCGLIB) object;
// 如果已經初始化過,則直接返回
if (Greet$$EnhancerByCGLIB.CGLIB$BOUND) break block2;
Greet$$EnhancerByCGLIB.CGLIB$BOUND = true;
if (object2 = CGLIB$THREAD_CALLBACKS.get()) != null) break block3; // 從 ThreadLocal 獲取 Callback 引數
if ((object2 = CGLIB$STATIC_CALLBACKS) == null) break block2;
}
Callback[] callbackArray = (Callback[])object2; // 初始化 Callback 引數
Greet$$EnhancerByCGLIB greet$$EnhancerByCGLIB = Greet$$EnhancerByCGLIB;
greet$$EnhancerByCGLIB.CGLIB$CALLBACK_2 = (MethodInterceptor)callbackArray[2];
greet$$EnhancerByCGLIB.CGLIB$CALLBACK_1 = (MethodInterceptor)callbackArray[1];
greet$$EnhancerByCGLIB.CGLIB$CALLBACK_0 = (NoOp)callbackArray[0];
}
}
}

 在動態生成的類中,可以看到 CGLib 為每個被代理的方法建立了 MethodProxy 物件。

 該物件替代了 Method.invoke() 功能,是實現高效方法呼叫的的關鍵。下面我們以 Greet.hello() 為例對該類進行分析:

public class MethodProxy {

  private Signature sig1; // 目標類方法簽名:hello()Ljava/lang/String;
private Signature sig2; // 代理類方法簽名:CGLIB$hello$0()Ljava/lang/String; private MethodProxy.CreateInfo createInfo; /* 省略初始化過程 */ private static class CreateInfo {
Class c1; // 目標類 buttercup.test.Greet
Class c2; // 代理類 buttercup.test.Greet$$EnhancerByCGLIB
} private final Object initLock = new Object();
private volatile MethodProxy.FastClassInfo fastClassInfo; private static class FastClassInfo {
FastClass f1; // 目標類 FastClass :
FastClass f2; // 代理類 FastClass :
int i1; // 方法在 f1 中對應的索引
int i2; // 方法在 f2 中對應的索引
} // 只在 MethodProxy 被呼叫時載入 FastClass,減少不必要的類生成(lazy-init)
private void init() {
if (fastClassInfo == null) {
synchronized (initLock) {
if (fastClassInfo == null) {
MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
fci.f1 = helper(ci, ci.c1); //
fci.f2 = helper(ci, ci.c2); //
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
fastClassInfo = new FastClassInfo(); }
}
}
} // 根據 Class 物件生成 FastClass
private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
Generator g = new Generator();
g.setType(type);
return g.create();
} // 呼叫 buttercup.test.Greet.hello()
// 但實際上會呼叫代理類的 EnhancerByCGLIB.hello() 實現
public Object invoke(Object obj, Object[] args) throws Throwable {
init();
return fastClassInfo.f1.invoke(fci.i1, obj, args);
} // 呼叫 buttercup.test.Greet$$EnhancerByCGLIB.CGLIB$hello$0()
// 通過 super.hello() 呼叫目標類的 Greet.hello() 實現
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
init();
return fastClassInfo.f2.invoke(fci.i2, obj, args);
}
}

 可以看到 MethodProxy 的呼叫實際是通過 FastClass 完成的,這是 CGLib 實現高效能反射呼叫的祕訣,下面來解析這個類的細節。

目標類 FastClass

 為了規避反射帶來的效能消耗,cglib 定義了 FastClass 來實現高效的方法呼叫,其主要職責有兩個

  1. 方法對映:解析 Class 物件併為每個 Constructor 與 Method 指定一個整數索引值 index
  2. 方法呼叫:通過 switch(index) 的方式,將反射呼叫轉化為硬編碼呼叫
abstract public class FastClass {

    // 對映:根據方法名稱與引數型別,獲取其對應的 index
public abstract int getIndex(String methodName, Class[] argClass); // 呼叫:根據 index 找到指定的方法,並進行呼叫
public abstract Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException; }

其命名規則為:目標類 + $$FastClassByCGLIB。下面具體分析一下對目標類 Greet 對應的 FastClass

public class Greet$$FastClassByCGLIB extends FastClass {

    public Greet$$FastClassByCGLIB(Class clazz) {
super(clazz);
} // 獲取 index 的最大值
public int getMaxIndex() {
return 4; // 當前 FastClass 總共支援 5 個方法
//索引值分別為 0:hello(), 1:hi(), 2:equals(), 3:hasCode(), 4:toString()
} // 根據方法名稱以及引數型別,獲取到指定方法對應的 index
public int getIndex(String methodName, Class[] argClass) {
switch (methodName.hashCode()) {
case 3329: {
if (!methodName.equals("hi")) break;
switch (argClass.length) {
case 0: { return 1; } // hi() 對應 index 為 1
}
break;
}
case 99162322: {
if (!methodName.equals("hello")) break;
switch (argClass.length) {
case 0: { return 0; } // hello() 對應 index 為 0
}
break;
}
/* 忽略 Object 方法 */
}
return -1;
} // 根據方法簽名,獲取到指定方法對應的 index
public int getIndex(Signature signature) {
String sig = ((Object)signature).toString();
switch (sig.hashCode()) {
case 397774237: {
if (!sig.equals("hello()Ljava/lang/String;")) break;
return 0; // hello() 對應 index 為 0
}
case 1155503180: {
if (!sig.equals("hi()Ljava/lang/String;")) break;
return 1; // hi() 對應 index 為 1
}
/* 忽略 Object 方法 */
}
return -1;
} // 方法呼叫(硬編碼呼叫)
public Object invoke(int n, Object obj, Object[] args) throws InvocationTargetException {
Greet greet = (Greet) obj;
switch (n) { // 通過 index 指定目標函式
case 0: { return greet.hello(); } // 通過索引 0 呼叫 hello()
case 1: { return greet.hi(); } // 通過索引 1 呼叫 hi()
/* 忽略 Object 方法 */
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
} // 建構函式(硬編碼呼叫)
public Object newInstance(int n, Object[] argClass) throws InvocationTargetException {
switch (n) {
case 0: { return new Greet(); }
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}

代理類 FastClass

 之前提及過:使用 MethodInterceptor 會比其他 Callback 生成更多的動態類,這是因為需要支援 MethodProxy.invokeSuper() 呼叫:

public interface MethodInterceptor extends Callback {

    // 所有生成的代理方法都呼叫此方法
// 大多數情況需要通過 MethodProxy.invokeSuper() 來實現目標類的呼叫
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}

 MethodProxy.invokeSuper() 通過呼叫代理類中帶 $CGLIB$ 字首的方法,繞過被重寫的代理方法,避免出現無限遞迴。

 為了保證呼叫效率,需要對代理類也生成 FastClass

public class Greet$$EnhancerByCGLIB$$FastClassByCGLIB extends FastClass {

    public Object invoke(int n, Object obj, Object[] args) throws InvocationTargetException {
Greet$$EnhancerByCGLIB greet$$EnhancerByCGLIB = (Greet$$EnhancerByCGLIB)obj;
switch (n) {
case 9: { // 呼叫目標類的原始 Greet.hello() 方法
return greet$$EnhancerByCGLIB.CGLIB$hello$0();
}
case 10: { // 呼叫目標類的原始 Greet.hi() 方法
return greet$$EnhancerByCGLIB.CGLIB$hi$1();
}
/* 忽略其他 Enhancer 方法 */
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
} public int getIndex(String methodName, Class[] argClass) {
switch (methodName.hashCode()) {
case 1837078673: {
if (!methodName.equals("CGLIB$hi$1")) break;
switch (argClass.length) {
case 0: { return 10; }
}
break;
}
case 1891304123: {
if (!methodName.equals("CGLIB$hello$0")) break;
switch (argClass.length) {
case 0: { return 9; }
}
break;
}
/* 忽略其他 Enhancer 方法 */
}
return -1;
} public int getIndex(Signature signature) {
String sig = ((Object)signature).toString();
switch (sig.hashCode()) {
case -1632605946: {
if (!sig.equals("CGLIB$hello$0()Ljava/lang/String;")) break;
return 9;
}
case 540391388: {
if (!sig.equals("CGLIB$hi$1()Ljava/lang/String;")) break;
return 10;
}
/* 忽略其他 Enhancer 方法 */
}
return -1;
} }

補充

 案例 Introduction 中僅使用了 Dispatcher,因此只生成了代理類,未使用到 FastClass

public class Greet$$EnhancerByCGLIB extends Greet implements FranceGreet, Factory {

    private NoOp CGLIB$CALLBACK_0;
private Dispatcher CGLIB$CALLBACK_1; public final String bonjour() {
Dispatcher dispatcher = this.CGLIB$CALLBACK_1;
if (dispatcher == null) {
Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
dispatcher = this.CGLIB$CALLBACK_1;
}
return ((FranceGreet)dispatcher.loadObject()).bonjour();
} /* 忽略多餘的屬性與方法 */
}

 本文案例僅涉及 MethodInterceptorDispatcher,這兩個 Callback 也是 Spring AOP 實現的關鍵,後續將繼續分析相關的原始碼實現。

附錄

JIT 編譯優化