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)代理,核心在于運(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)對象是否有接口,以及對性能、依賴的考量。
解決方案
1. JDK Proxy實(shí)現(xiàn)動態(tài)代理
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)代理
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ù)雜的代理場景。