背景
JDK 動態代理存在的一些問題:
呼叫效率低
JDK 通過反射實現動態代理呼叫,這意味著低下的呼叫效率:
-
每次呼叫
Method.invoke()
都會檢查方法的可見性、校驗引數是否匹配,過程涉及到很多 native 呼叫,具體參見 JNI 呼叫開銷 -
反射呼叫涉及動態類解析,這種不可預測性,導致被反射呼叫的程式碼無法被 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 使用的方式很簡單,大致可以分為兩步:
- 配置 Enhancer
- 設定需要代理的目標類與介面
- 通過
Callback
設定需要增強的功能 - 通過
CallbackFilter
將方法匹配到具體的Callback
- 建立代理物件
- 通過
CallbackFilter
獲取方法與Callback
的關聯關係 - 繼承目標類並重寫
override
方法,在呼叫程式碼中嵌入 Callback - 編譯動態生成的位元組碼生成代理類
- 通過反射呼叫建構函式生成代理物件
Callback 分類
此外,CGLib 支援多種 Callback,這裡簡單介紹幾種:
NoOp
不使用動態代理,匹配到的方法不會被重寫FixedValue
返回固定值,被代理方法的返回值被忽略Dispatcher
指定上下文,將代理方法呼叫委託給特定物件MethodInterceptor
呼叫攔截器,用於實現環繞通知around advice
其中 MethodInterceptor
最為常用,可以實現多種豐富的代理特性。
但這類 Callback
也是其中最重的,會導致生成更多的動態類,具體原因後續介紹。
位元組碼生成過程
底層通過 Enhancer.generateClass()
生成代理類,其具體過程不作深究,可以簡單概括為:
- 通過
ClassVisitor
獲取目標類資訊 - 通過
ClassEmitter
呼叫 asm 庫注入增強方法,並生成byte[]
形式的位元組碼 - 通過反射呼叫
ClassLoader.defineClass()
將byte[]
轉換為Class
物件 - 將生成完成的代理類快取至
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.adviceBefore
與 Weaving.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
來實現高效的方法呼叫,其主要職責有兩個
- 方法對映:解析 Class 物件併為每個 Constructor 與 Method 指定一個整數索引值 index
- 方法呼叫:通過 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();
}
/* 忽略多餘的屬性與方法 */
}
本文案例僅涉及 MethodInterceptor
與 Dispatcher
,這兩個 Callback 也是 Spring AOP 實現的關鍵,後續將繼續分析相關的原始碼實現。