Java中如何實(shí)現(xiàn)動態(tài)代理?CGLib與JDK Proxy對比

jdk proxy基于接口,通過反射生成代理類,適用于有接口的目標(biāo)對象;cglib通過字節(jié)碼生成子類,適用于無接口的類。1. jdk proxy要求目標(biāo)類實(shí)現(xiàn)接口,通過invocationhandler攔截方法調(diào)用,適合接口編程場景。2. cglib不依賴接口,使用methodinterceptor攔截方法,通過繼承目標(biāo)類生成代理,適合無接口的類。3. 選擇時(shí)優(yōu)先考慮jdk proxy,若目標(biāo)類無接口則使用cglib。兩者性能差異在現(xiàn)代jvm中通常可忽略,實(shí)際應(yīng)用中應(yīng)根據(jù)是否具備接口及依賴管理進(jìn)行決策。

Java中如何實(shí)現(xiàn)動態(tài)代理?CGLib與JDK Proxy對比

Java中實(shí)現(xiàn)動態(tài)代理,核心在于運(yùn)行時(shí)創(chuàng)建代理對象,攔截并增強(qiáng)目標(biāo)對象的方法調(diào)用。JDK Proxy基于接口,通過反射機(jī)制在運(yùn)行時(shí)生成代理類;而CGLib則不依賴接口,它通過修改字節(jié)碼、生成目標(biāo)類的子類來實(shí)現(xiàn)代理。選擇哪種方式,往往取決于你的目標(biāo)對象是否有接口,以及對性能、依賴的考量。

Java中如何實(shí)現(xiàn)動態(tài)代理?CGLib與JDK Proxy對比

解決方案

1. JDK Proxy實(shí)現(xiàn)動態(tài)代理

Java中如何實(shí)現(xiàn)動態(tài)代理?CGLib與JDK Proxy對比

JDK Proxy是Java標(biāo)準(zhǔn)庫自帶的動態(tài)代理機(jī)制,它要求被代理的類必須實(shí)現(xiàn)一個(gè)或多個(gè)接口。

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

// 1. 定義業(yè)務(wù)接口 public interface Subject {     void request();     String sayHello(String name); }  // 2. 實(shí)現(xiàn)業(yè)務(wù)接口的真實(shí)對象 public class RealSubject implements Subject {     @Override     public void request() {         System.out.println("RealSubject: Executing request...");     }      @Override     public String sayHello(String name) {         return "RealSubject: Hello, " + name + "!";     } }  // 3. 定義一個(gè)InvocationHandler,用于處理代理實(shí)例上的方法調(diào)用 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;  public class MyInvocationHandler implements InvocationHandler {     private Object target; // 真實(shí)對象      public MyInvocationHandler(Object target) {         this.target = target;     }      /**      * proxy: 代理實(shí)例      * method: 調(diào)用的方法      * args: 方法參數(shù)      */     @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println("MyInvocationHandler: Before method " + method.getName());         // 調(diào)用真實(shí)對象的方法         Object result = method.invoke(target, args);         System.out.println("MyInvocationHandler: After method " + method.getName());         return result;     }      // 獲取代理對象     public Object getProxy() {         return Proxy.newProxyInstance(                 target.getClass().getClassLoader(), // 類加載器                 target.getClass().getInterfaces(),   // 真實(shí)對象實(shí)現(xiàn)的接口                 this                                 // InvocationHandler實(shí)例         );     } }  // 4. 使用示例 public class JdkProxyDemo {     public static void main(String[] args) {         RealSubject realSubject = new RealSubject();         MyInvocationHandler handler = new MyInvocationHandler(realSubject);         Subject proxySubject = (Subject) handler.getProxy(); // 獲取代理對象          proxySubject.request();         System.out.println(proxySubject.sayHello("World"));          // 驗(yàn)證代理對象類型         System.out.println("Proxy class: " + proxySubject.getClass().getName());         System.out.println("Is proxy an instance of Subject? " + (proxySubject instanceof Subject));     } }

2. CGLib實(shí)現(xiàn)動態(tài)代理

Java中如何實(shí)現(xiàn)動態(tài)代理?CGLib與JDK Proxy對比

CGLib (Code Generation Library) 是一個(gè)強(qiáng)大的高性能字節(jié)碼生成庫,它可以在運(yùn)行時(shí)擴(kuò)展Java類和實(shí)現(xiàn)java接口。它不需要被代理類實(shí)現(xiàn)接口,通過生成目標(biāo)類的子類來實(shí)現(xiàn)代理。

// 1. 定義一個(gè)普通類,無需實(shí)現(xiàn)接口 public class TargetClass {     public void doSomething() {         System.out.println("TargetClass: Doing something...");     }      public String greet(String name) {         return "TargetClass: Hello, " + name + "!";     } }  // 2. 定義一個(gè)MethodInterceptor,用于攔截方法調(diào)用 import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method;  public class MyMethodInterceptor implements MethodInterceptor {     /**      * obj: CGLib生成的代理對象      * method: 調(diào)用的方法      * args: 方法參數(shù)      * methodProxy: 用于調(diào)用父類方法(即目標(biāo)類方法)的代理      */     @Override     public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {         System.out.println("MyMethodInterceptor: Before method " + method.getName());         // 調(diào)用目標(biāo)對象的方法,這里通過methodProxy調(diào)用,性能更高         Object result = methodProxy.invokeSuper(obj, args);         System.out.println("MyMethodInterceptor: After method " + method.getName());         return result;     }      // 獲取代理對象     public Object getProxy(Class<?> targetClass) {         Enhancer enhancer = new Enhancer();         enhancer.setSuperclass(targetClass); // 設(shè)置目標(biāo)類作為父類         enhancer.setCallback(this);          // 設(shè)置回調(diào)         return enhancer.create();            // 創(chuàng)建代理對象     } }  // 3. 使用示例 public class CglibProxyDemo {     public static void main(String[] args) {         MyMethodInterceptor interceptor = new MyMethodInterceptor();         TargetClass proxyTarget = (TargetClass) interceptor.getProxy(TargetClass.class);          proxyTarget.doSomething();         System.out.println(proxyTarget.greet("CGLib User"));          // 驗(yàn)證代理對象類型         System.out.println("Proxy class: " + proxyTarget.getClass().getName());         System.out.println("Is proxy an instance of TargetClass? " + (proxyTarget instanceof TargetClass));     } }

JDK Proxy的工作原理與適用場景是什么?

JDK Proxy的工作原理,說白了就是Java在運(yùn)行時(shí),動態(tài)地為你生成一個(gè)實(shí)現(xiàn)了目標(biāo)接口的“假”類。這個(gè)“假”類,也就是代理類,它會把所有對接口方法的調(diào)用,都轉(zhuǎn)發(fā)給一個(gè)你提供的InvocationHandler實(shí)例去處理。InvocationHandler的invoke方法接收到這些調(diào)用后,就能在調(diào)用真實(shí)方法前后加入你的邏輯,比如日志記錄、事務(wù)管理、權(quán)限校驗(yàn)等等。它底層依賴的是Java的反射機(jī)制,通過Proxy.newProxyInstance()方法,JVM會為你創(chuàng)建一個(gè)臨時(shí)的、實(shí)現(xiàn)了指定接口的代理類字節(jié)碼,并加載到內(nèi)存中。

這種機(jī)制最典型的適用場景,就是那些基于接口編程的框架。比如spring AOP,當(dāng)你配置一個(gè)接口的Service進(jìn)行事務(wù)管理時(shí),Spring默認(rèn)就會使用JDK Proxy。還有rpc框架,客戶端 stub 通常就是通過JDK Proxy生成的,它把遠(yuǎn)程調(diào)用封裝成一個(gè)本地接口方法。再比如一些單元測試的Mock框架,也能用它來模擬接口行為。簡而言之,只要你的設(shè)計(jì)中大量使用了接口,并且希望在不修改原有代碼的前提下,為接口方法添加統(tǒng)一的橫切邏輯,JDK Proxy都是一個(gè)非常自然且強(qiáng)大的選擇。它簡潔、內(nèi)置,用起來也相當(dāng)順手。

CGLib動態(tài)代理的核心機(jī)制與優(yōu)勢在哪里?

CGLib的核心機(jī)制與JDK Proxy大相徑庭。它不依賴接口,而是通過字節(jié)碼技術(shù),直接生成目標(biāo)類的子類。當(dāng)你請求CGLib為一個(gè)類創(chuàng)建代理時(shí),它會利用ASM庫(一個(gè)字節(jié)碼操作和分析框架)在運(yùn)行時(shí)生成一個(gè)新的類,這個(gè)新類繼承自你的目標(biāo)類,并且會覆蓋目標(biāo)類中的所有非final方法。在這些被覆蓋的方法中,CGLib會插入調(diào)用你提供的MethodInterceptor的邏輯。MethodInterceptor的intercept方法中,你就可以實(shí)現(xiàn)自己的增強(qiáng)邏輯,并通過MethodProxy來調(diào)用父類(即原始目標(biāo)類)的方法。

CGLib的優(yōu)勢顯而易見:它能代理那些沒有實(shí)現(xiàn)接口的普通類。這在很多場景下非常有用,比如一些遺留系統(tǒng),或者你就是不想為每個(gè)類都定義一個(gè)接口。另一個(gè)常常被提及的優(yōu)勢是性能。由于CGLib生成的是目標(biāo)類的子類,直接調(diào)用子類方法,理論上比JDK Proxy通過反射調(diào)用invoke方法要快一些。不過,在現(xiàn)代JVM的優(yōu)化下,這種性能差異在絕大多數(shù)應(yīng)用中幾乎可以忽略不計(jì),除非你的應(yīng)用對代理的調(diào)用是極其頻繁且性能敏感的核心瓶頸。對我個(gè)人而言,CGLib更多的是解決“能否代理”的問題,而非“性能高低”的問題。

JDK Proxy與CGLib在實(shí)際應(yīng)用中該如何選擇?

在實(shí)際開發(fā)中,選擇JDK Proxy還是CGLib,我通常會從幾個(gè)維度去考量,而不是盲目地偏向某一方。

我個(gè)人在做選擇時(shí),通常會先看有沒有接口。如果被代理的類已經(jīng)實(shí)現(xiàn)了接口,并且我只需要代理接口定義的方法,那么JDK Proxy往往是我的首選。它內(nèi)置于JDK,無需額外依賴,代碼也相對簡潔直觀。這就像是,如果有一條現(xiàn)成的、平坦的公路能到目的地,我肯定不會去開辟一條新的山路。

另一邊廂,如果目標(biāo)類沒有實(shí)現(xiàn)任何接口,或者我需要代理的是一個(gè)具體的類,而不僅僅是它的接口方法(比如,一個(gè)類中的某些方法并沒有在任何接口中定義),那么CGLib就成了唯一的選擇。很多框架,比如Spring AOP,在發(fā)現(xiàn)目標(biāo)對象沒有實(shí)現(xiàn)接口時(shí),會自動切換到CGLib來創(chuàng)建代理。這種智能切換省去了開發(fā)者手動判斷的麻煩。

再者說,性能差異在大多數(shù)業(yè)務(wù)場景下并不構(gòu)成決定性因素。現(xiàn)代JVM的JIT編譯器對反射調(diào)用的優(yōu)化已經(jīng)相當(dāng)出色,兩者之間的性能差距遠(yuǎn)沒有想象中那么大。如果你真的遇到了性能瓶頸,通常需要從更宏觀的架構(gòu)設(shè)計(jì)或算法層面去優(yōu)化,而不是糾結(jié)于代理方式的微小性能差異。

最后,考慮到外部依賴。JDK Proxy是JDK自帶的,而CGLib需要引入第三方庫。雖然這通常不是什么大問題,但在某些對依賴管理極其嚴(yán)格的環(huán)境中,內(nèi)置方案可能會更受歡迎。

所以,我的經(jīng)驗(yàn)是:優(yōu)先考慮JDK Proxy,因?yàn)樗霸保划?dāng)且僅當(dāng)JDK Proxy無法滿足需求(即需要代理沒有接口的類)時(shí),才轉(zhuǎn)向使用CGLib。 這種選擇策略既能利用JDK的內(nèi)置能力,又能應(yīng)對更復(fù)雜的代理場景。

以上就是Java中如何實(shí)現(xiàn)

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊7 分享