在日常的開發中,Spring AOP 是一個非常常用的功能,談到 AOP,自然離不開動態代理,
那么,基于 JDK 和 CGLib 如何實作動態代理,他們之間的區別和適用場景是什么呢?接下來,我們一起來探討一下這個問題,
JDK 如何實作動態代理?
話不多說,我們直接對照著代碼來查看,
代碼示例
Hello 介面
public interface HelloInterface {
/**
* 代理的目標方法
*/
void sayHello();
/**
* 未被代理處理的方法
*/
void noProxyMethod();
}
Hello 實作類
public class HelloImpl implements HelloInterface {
@Override
public void sayHello() {
System.out.println("proxyMethod:sayHello");
}
@Override
public void noProxyMethod() {
System.out.println("noProxyMethod");
}
}
MyInvocationHandler 實作 InvocationHandler 介面類
public class MyInvocationHandler implements InvocationHandler {
/**
* 目標物件
*/
private Object target;
/**
* 構造方法
*
* @param target
*/
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("sayHello".equals(methodName)) {
// 比方說,mybaitis 中的 PooledConnection 利用 jdk 動態代理重新實作了 close 方法
System.out.println("change method");
return null;
}
System.out.println("invoke method");
Object result = method.invoke(target, args);
return result;
}
}
動態代理神奇的地方就是:
- 代理物件是在程式運行時產生的,而不是編譯期;
- 對代理物件的所有介面方法呼叫都會轉發到InvocationHandler.invoke()方法,在invoke()方法里我們可以加入任何邏輯,比如修改方法引數,加入日志功能、安全檢查功能等,
??注意:從 Object 中繼承的方法,JDK Proxy 會把hashCode()、equals()、toString()這三個非介面方法轉發給 InvocationHandler,其余的 Object 方法則不會被轉發,詳見 JDK Proxy官方檔案,

代碼測驗
public class MyDynamicProxyTest {
public static void main(String[] args) {
HelloInterface hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 構造代碼實體
HelloInterface proxyInstance = (HelloInterface) Proxy.newProxyInstance(
HelloImpl.class.getClassLoader(),
HelloImpl.class.getInterfaces(),
handler);
// 代理呼叫方法
proxyInstance.sayHello();
proxyInstance.noProxyMethod();
}
}
列印的日志資訊如下:

關鍵要點
結合上面的演示,我們小結一下 JDK 動態代理的實作,包括三個步驟:
- 1.定義一個介面比如上面的 HelloInterface,Jdk 的動態代理是基于介面,這就是代理介面,
- 2.撰寫介面實作類比如上面的 HelloImpl,這個就是目標物件,也就是被代理的物件類,
- 3.撰寫一個實作 InvocationHandler 介面的類,代理類的方法呼叫會被轉發到該類的 invoke() 方法,比如上面的 MyInvocationHandler,
CGLib 如何實作動態代理?
代碼示例
Hello 類
無需定義和實作介面,
public class Hello {
public String sayHello(String name) {
System.out.println("Hello," + name);
return "Hello," + name;
}
}
CglibMethodInterceptor 實作 MethodInterceptor
/**
* 實作一個MethodInterceptor,方法呼叫會被轉發到該類的intercept()方法,
*/
public class CglibMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("intercept param is " + Arrays.toString(args));
System.out.println("before===============" + method);
// 這里可以實作增強的邏輯處理s
Object result = methodProxy.invokeSuper(obj, args);
// 這里可以實作增強的邏輯處理
System.out.println("after===============" + method);
return result;
}
}
??注意:對于從Object中繼承的方法,CGLIB代理也會進行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不會(因為其他方法是 final,無法被代理),CGLIB 無法代理她們,
pom 依賴
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
代碼測驗
public class CglibTest {
/**
* 在需要使用 Hello 的時候,通過CGLIB動態代理獲取代理物件
*/
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Hello.class);
enhancer.setCallback(new CglibMethodInterceptor());
// 給目標物件創建一個代理物件
Hello hello = (Hello) enhancer.create();
hello.sayHello("Alan");
}
列印的日志如下:

關鍵要點
結合上面的演示,我們小結一下 CGLIB 動態代理的實作:
- 1.實作一個MethodInterceptor,方法呼叫被轉發到該類的 intercept() 方法,
- 2.使用 Enhancer 獲取代理物件,并呼叫對應的方法,
JDK Vs CgLib
Java 1.3 后,提供了動態代理技術,允許我們開發者在運行期創建介面的代理實體,后來這項技術被用到了很多地方(比如 Spring AOP),
JDK 動態代理主要對應到 java.lang.reflect 包下邊的兩個類:Proxy 和 InvocationHandler,
其中 InvocationHandler 是一個介面,可以通過實作該介面定義橫切邏輯,
舉個例子,在方法執行前后列印的日志(這里只是為了說明,實際應用一般不會只是簡單地列印日志,一般用于日志、安全、事務等場景),并通過「反射機制」呼叫目標類的代碼,動態地將橫切邏輯和業務邏輯編織在一起,
- JDK 動態代理有一個限制:它只能為介面創建代理實體,對于沒有通過介面定義業務方法的類,如何創建動態代理實體呢?答案就是 CGLib,
- CGLIB(Code Generation Library))是一個底層基于 ASM 的位元組碼生成庫,它允許我們在「運行時」修改和動態生成位元組碼,CGLIB 通過繼承方式實作代理,在子類中采用方法攔截的方式攔截所有父類方法的呼叫并順勢織入橫切邏輯,
JDK 和 CGLib 動態代理區別
1. JDK 動態代理實作原理
- 通過實作 InvocationHandler 介面創建自己的呼叫處理器
- 通過為 Proxy 類指定 ClassLoader 物件和一組 interface 創建動態代理
- 通過反射機制獲取動態代理類的建構式,其唯一引數型別就是呼叫處理器介面型別
- 通過建構式創建動態代理類實體,構造時呼叫處理器物件作為引數傳入
JDK 動態代理是面向介面的代理模式,如果被代理目標沒有介面則無能為力,
例如,Spring 通過 Java 的反射機制生產被代理介面的新的匿名實作類,重寫了 AOP 的增強方法,
2. CGLib 動態代理原理
利用 ASM 開源包,對代理物件類的 class 檔案加載進來,通過修改其位元組碼生成子類來處理,
3. 兩者對比
- JDK 動態代理是面向介面的;
- CGLib 動態代理是通過位元組碼底層繼承代理類來實作,如果被代理類被 final 關鍵字所修飾,則無法被代理,
4.適用場景
- 如果被代理的物件是個實作了介面的實作類,那么可以使用 JDK 動態代理,例如,Spring 會使用 JDK 動態代理來完成操作(Spirng 默認采用)
- 如果被代理的物件沒有實作介面,只有實作類,那么只能使用 CGLib 實作動態代理(JDK 不支持),例如,被代理物件是沒有介面的實作類,Spring 強制使用 CGLib 實作的動態代理,
性能對比
網上有人對于不同版本的 jdk 進行了測驗,經過多次試驗,測驗結果大致如下:
- 在 JDK 1.6 和 1.7 時,JDK 動態代理的速度要比 CGLib 要慢,但是并沒有某些書上寫的10倍差距那么夸張,
- 在 JDK 1.8 時,JDK 動態代理的速度比 CGLib 快很多,
[idea] 很多時候,性能差異不一定是我們選擇某種方式的絕對因素,我們更應該去考慮該技術適用的場景,
例如,我們應用中絕大多數的性能差異可能主要在集中在磁盤 I/O,網路帶寬等因素,動態代理這點性能差異可能只是占了非常小的比例,
如果覺得本文對你有幫助,可以關注一下我公眾號,回復關鍵字【技術】即可各種編程資料以及面試題大禮包!還有更多技術干貨文章以及架構資料共享,大家一起學習進步!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/342034.html
標籤:其他
