引言:在面試的過程中,經常會有面試官詢問關於動態代理的問題,特別是 JDK 動態代理與 CGLIB 動態代理的區別以及它們各自的套用場景。如果你對動態代理的概念和細節尚未了解,面對這樣的問題可能會感到不安。因此,本文旨在深入探討動態代理的原理和實踐,幫助你掌握這一關鍵知識點,從而在面試中占據有利地位。
題目
動態代理是基於什麽原理?
推薦解析
什麽是動態代理?
動態代理是一種在執行時動態生成代理物件,並在代理物件上進行方法呼叫的編程技術。它主要用於在不修改原有程式碼基礎上,增加或改變某些功能的執行流程。動態代理廣泛套用於 AOP(面向切面編程)、RPC(遠端程序呼叫)、事務管理等領域。在 Java 中,主要有兩種動態代理的實作方式:JDK 動態代理和 CGLIB 動態代理。
JDK 動態代理
JDK 動態代理是基於介面的代理技術。它使用
java.lang.reflect.Proxy
類和
java.lang.reflect.InvocationHandler
介面來建立代理物件。當你呼叫代理物件的任何方法時,呼叫會被轉發到
InvocationHandler
的
invoke
方法。你可以在這個
invoke
方法中定義攔截邏輯,比如前置處理、後置處理等。
為了使用 JDK 動態代理,你的類必須實作一個或多個介面。JDK 動態代理的局限性在於,它只能代理介面方法,如果你有一個類並希望代理其非介面方法,則不能使用 JDK 動態代理。
優點以及缺點
優點 :原生支持,無需引入額外依賴。
缺點 :只能代理介面,如果一個類沒有實作任何介面,則不能使用 JDK 動態代理。
操作步驟
1)定義一個介面及其實作類。
2)建立一個實作了
InvocationHandler
介面的類,在該類的
invoke
方法中定義代理邏輯。
3)透過
Proxy.newProxyInstance
方法動態建立介面的代理物件。
程式碼範例
// 定義一個介面
publicinterfaceHelloService{
voidsayHello(String name);
}
// 實作該介面的類
public classHelloServiceImplimplementsHelloService{
@Override
publicvoidsayHello(String name){
System.out.println("Hello, " + name);
}
}
//建立一個實作 InvocationHandler 介面的類
public classHelloServiceHandlerimplementsInvocationHandler{
//目標物件
private Object target;
publicHelloServiceHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("Before sayHello");
// 執行目標物件的方法
Object result = method.invoke(target, args);
System.out.println("After sayHello");
return result;
}
}
//透過 Proxy.newProxyInstance 方法動態建立介面的代理物件
publicstaticvoidmain(String[] args){
// 建立目標物件
HelloService target = new HelloServiceImpl();
// 建立呼叫處理器物件
HelloServiceHandler handler = new HelloServiceHandler(target);
// 建立代理物件
HelloService proxyInstance = (HelloService) Proxy.newProxyInstance(
target.get class().get classLoader(),
target.get class().getInterfaces(),
handler);
// 透過代理物件呼叫方法
proxyInstance.sayHello("World");
}
CGLIB 動態代理
CGLIB(Code Generation Library)是一個強大的、高效能、高品質的 Code 生成庫,它可以在執行時擴充套件 Java 類和實作 Java 介面。不同於 JDK 動態代理,CGLIB 不需要介面,它是透過繼承方式實作代理的。
CGLIB 底層透過使用一個小而快的字節碼處理框架 ASM ,來轉換字節碼並生成新的類。不僅可以代理普通類的方法,還能代理那些沒有介面的類的方法。
優點以及缺點
優點 :無需介面實作。在大量呼叫的場景下,其生成的代理物件在呼叫時效能比 JDK 動態代理高
缺點 :對 final 方法無效,需添加額外的依賴。
操作步驟
1)建立一個需要被代理的類。
2)建立一個繼承
MethodInterceptor
的代理類,在
intercept
方法中定義代理邏輯。
3)使用
Enhancer
類建立被代理類的子類別,並設定回呼。
程式碼範例
<!--引入 cglib依賴 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
//定義一個普通類
public classHelloService{
publicvoidsayHello(String name){
System.out.println("Hello, " + name);
}
}
//建立一個繼承 MethodInterceptor 的代理類 實作 intercept 方法
public classHelloServiceCglibimplementsMethodInterceptor{
private Object target;
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuper class(this.target.get class());
// 設定回呼
enhancer.setCallback(this);
// 建立代理物件
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)throws Throwable {
System.out.println("Before sayHello");
// 執行目標類的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("After sayHello");
return result;
}
}
//實作
publicstaticvoidmain(String[] args){
HelloService target = new HelloService();
HelloServiceCglib cglib = new HelloServiceCglib();
HelloService proxy = (HelloService) cglib.getInstance(target);
proxy.sayHello("World");
}
原理對比
實作方式 :JDK 動態代理透過反射機制呼叫介面方法,CGLIB 透過生成目標物件的子類別並覆蓋其方法實作代理。
效能 :在呼叫方法的次數非常多的情況下,CGLIB 的效能可能優於 JDK 動態代理,但差異通常不大。CGLIB 初始化的代理物件比 JDK 動態代理慢,因為它需要生成新的類。
使用場景 :
如果目標物件實作了介面,推薦使用 JDK 動態代理。
如果目標物件沒有實作介面,或者有特定需求需要透過繼承方式代理,則使用 CGLIB。
其他補充
魚聰明 AI 的回答:
魚聰明 AI 地址:https://www.yucongming.com/
靜態代理知識補充
靜態代理是一種設計模式,它在程式執行前就已經存在代理類的程式碼,代理類和目標物件實作相同的介面或繼承相同的父類。透過代理類來間接存取目標物件,從而在不修改目標物件程式碼的情況下,增加或改變某些功能的執行流程。靜態代理通常用於控制對目標物件的存取,或在呼叫目標物件的方法前後添加額外的功能,如安全檢查、事務處理、日誌記錄等。
靜態代理的特點
編譯時增加功能 :靜態代理的實作在編譯階段就已經完成,所有的增強功能都需要在代理類中顯式編寫。
程式碼冗余 :如果有多個類需要代理,每個類都需要一個對應的代理類,這會導致大量的重復程式碼。
緊耦合 :代理類和目標物件之間的關系在編譯時就已經確定,增加或修改代理類需要重新編譯。
靜態代理的實作
靜態代理的實作涉及到四個角色:
1) 介面( Interface ) :定義了目標物件和代理物件共同遵循的操作集合。
2) 目標物件( Target Object ) :實作了介面的類,定義了要執行的具體操作。
3) 代理物件( Proxy Object ) :同樣實作了介面,用於包裝目標物件,可以在呼叫目標物件的方法前後執行一些附加操作。
4) 客戶端( Client ) :使用代理物件的使用者。
簡單實作
假設有一個簡單的介面和實作類,介面定義了一個
sayHello
方法:
// 定義介面
publicinterfaceHelloService{
voidsayHello(String name);
}
// 實作介面的目標類
public classHelloServiceImplimplementsHelloService{
@Override
publicvoidsayHello(String name){
System.out.println("Hello, " + name);
}
}
// 靜態代理類 建立一個代理類來增強 sayHello 方法:
public classHelloServiceProxyimplementsHelloService{
private HelloService helloService;
publicHelloServiceProxy(HelloService helloService){
this.helloService = helloService;
}
@Override
publicvoidsayHello(String name){
System.out.println("Before sayHello"); // 前置增強
helloService.sayHello(name);
System.out.println("After sayHello"); // 後置增強
}
}
//實作
publicstaticvoidmain(String[] args){
HelloService helloService = new HelloServiceImpl();
HelloService proxy = new HelloServiceProxy(helloService);
proxy.sayHello("World");
}
推薦文章和書籍
文章:https://zhuanlan.zhihu.com/p/86293659
書籍:【 Java 核心技術卷 I 】
歡迎交流
在閱讀完本文後,你應該對 Java 中的動態代理機制,包括 JDK 動態代理和 CGLIB 動態代理,有了深入的理解。掌握了這些代理技術,你一定能在討論 Java 設計模式、面向切面編程( AOP )以及中介軟體開發時,與面試官或同行進行深入的交流。本文最後提出三個問題,歡迎大家在評論區分享觀點,以促進共同進步:
1)在哪些場景下應該優先考慮使用 JDK 動態代理和 CGLIB 動態代理,以最佳化程式結構或增強功能?
2)如何合理選擇和套用 JDK 動態代理與 CGLIB 動態代理,以構建靈活且高效的 AOP 框架或中介軟體?
3)JDK 動態代理和 CGLIB 動態代理在效能、適用場景和易用性方面有何不同,這些差異如何影響套用的設計和選擇?
這些問題旨在深化對 Java 動態代理技術及其在實際套用中的作用的理解,期待你的參與和貢獻。
👇🏻 點選下方閱讀原文,獲取魚皮往期編程幹貨
往期推薦