1. 程式人生 > >靜態代理和動態代理(jdk/cglib)詳解

靜態代理和動態代理(jdk/cglib)詳解

##### **1.靜態代理模式** ![](https://img2020.cnblogs.com/blog/1054413/202008/1054413-20200811114656604-915657843.png) 代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色負責定義RealSubject和Proxy角色應該實現的介面;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的Request請求,呼叫realsubject 對應的request功能來實現業務功能,自己不真正做業務。 靜態代理的簡單實現: Subject角色 ```java public interface ToBPayment { void pay(); } ``` ```java public interface ToCPayment { void pay(); } ``` RealSubject角色: ```java public class ToBPaymentImpl implements ToBPayment { @Override public void pay() { System.out.println("以公司的名義進行支付"); } } ``` ```java public class ToCPaymentImpl implements ToCPayment { @Override public void pay() { System.out.println("以使用者的名義進行支付"); } } ``` Proxy角色: ```java public class AlipayToB implements ToBPayment { ToBPayment toBPayment; public AlipayToB(ToBPayment toBPayment){ this.toBPayment = toBPayment; } @Override public void pay() { beforePay(); toBPayment.pay(); afterPay(); } private void beforePay() { System.out.println("從招行取款"); } private void afterPay() { System.out.println("支付給xx"); } } ``` ```java public class AlipayToC implements ToCPayment { ToCPayment toCPayment; public AlipayToC(ToCPayment toCPayment){ this.toCPayment = toCPayment; } @Override public void pay() { beforePay(); toCPayment.pay(); afterPay(); } private void beforePay() { System.out.println("從招行取款"); } private void afterPay() { System.out.println("支付給xx"); } } ``` 當在程式碼階段規定這種代理關係,Proxy類通過編譯器編譯成class檔案,當系統執行時,此class已經存在了。這種靜態的代理模式固然在訪問無法訪問的資源,**增強現有的介面業務功能方面有很大的優點**,**但是大量使用這種靜態代理,會使我們系統內的類的規模增大,並且不易維護**;並且由於Proxy和RealSubject的功能 本質上是相同的,Proxy只是起到了中介的作用,這種代理在系統中的存在,導致系統結構比較臃腫和鬆散。 **靜態代理模式的優點**:增強現有的介面業務功能方面有很大的優點。 **靜態代理模式的缺點**:大量使用這種靜態代理,會使我們系統內的類的規模增大,並且不易維護。 ##### **二.動態代理模式**   為了解決這個問題,就有了動態地建立Proxy的想法:在執行狀態中,需要代理的地方,根據Subject 和RealSubject,動態地建立一個Proxy,用完之後,就會銷燬,這樣就可以避免了Proxy 角色的class在系統中冗雜的問題了。 ​ 由於JVM通過位元組碼的二進位制資訊載入類的,那麼,如果我們在執行期系統中,遵循Java編譯系統組織.class檔案的格式和結構,生成相應的二進位制資料,然後再把這個二進位制資料載入轉換成對應的類,這樣,就完成了在程式碼中,動態建立一個類的能力了。 ![](https://img2020.cnblogs.com/blog/1054413/202008/1054413-20200811114712605-1495380997.png) 在執行時期可以按照Java虛擬機器規範對class檔案的組織規則生成對應的二進位制位元組碼。當前有很多開源框架可以完成這些功能,如ASM,Javassist。    **JDK動態代理和cgLib動態代理** 在面向物件的程式設計之中,如果我們想要約定Proxy 和RealSubject可以實現相同的功能,有兩種方式:   a.一個比較直觀的方式,就是定義一個功能介面,然後讓Proxy 和RealSubject來實現這個介面。   b.還有比較隱晦的方式,就是通過繼承。因為如果Proxy 繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還可以通過重寫RealSubject中的方法,來實現多型。 **JDK動態代理** **其中JDK中提供的建立動態代理的機制,是以a 這種思路設計的,而cglib 則是以b思路設計的。** - **JDK的動態代理建立機制----通過介面** 比如現在想為RealSubject這個類建立一個動態代理物件,JDK主要會做以下工作:   1. 獲取 RealSubject上的所有介面列表;   2. 確定要生成的代理類的類名,預設為:com.sun.proxy.$ProxyXXXX ;   3. 根據需要實現的介面資訊,在程式碼中動態建立 該Proxy類的位元組碼;   4. 將對應的位元組碼轉換為對應的class 物件;   5. 建立InvocationHandler 例項handler,用來處理Proxy所有方法呼叫;   6. Proxy 的class物件 以建立的handler物件為引數,例項化一個proxy物件 簡單實現如下: ```java public class AlipayInvocationHandler implements InvocationHandler { private Object targetObject; public AlipayInvocationHandler(Object targetObject){ this.targetObject = targetObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforePay(); Object result = method.invoke(targetObject, args); afterPay(); return result; } private void beforePay() { System.out.println("從招行取款"); } private void afterPay() { System.out.println("支付給xx"); } } ``` ```java public class JdkDynamicProxyUtil { public static T newProxyInstance(T targetObject, InvocationHandler handler){ //1.獲取對應的類載入器 ClassLoader classLoader = targetObject.getClass().getClassLoader(); //2.獲取代理的所有介面 Class[] interfaces = targetObject.getClass().getInterfaces(); return (T)Proxy.newProxyInstance(classLoader, interfaces, handler); } } ``` - **cglib 生成動態代理類的機制----通過類繼承:** JDK中提供的生成動態代理類的機制有個鮮明的特點是: 某個類必須有實現的介面,而生成的代理類也只能代理某個類介面定義的方法,如果某個類沒有實現介面,那麼這個類就不能同JDK產生動態代理了。 幸好我們有cglib。“CGLIB(Code Generation Library),是一個強大的,高效能,高質量的Code生成類庫,它可以在執行期擴充套件Java類與實現Java介面。” cglib 建立某個類A的動態代理類的模式是:   1. 查詢A上的所有非final的public型別的方法定義;   2. 將這些方法的定義轉換成位元組碼;   3. 將組成的位元組碼轉換成相應的代理的class物件;   4. 實現MethodInterceptor介面,用來處理對代理類上所有方法的請求(這個介面和JDK動態代理InvocationHandler的功能和角色是一樣的 簡單實現如下: ```java public class AlipayMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { beforePay(); Object result = methodProxy.invokeSuper(o, args); afterPay(); return result; } private void beforePay() { System.out.println("從招行取款"); } private void afterPay() { System.out.println("支付給xx"); } } ``` ```java public class CglibUtil { public static T createProxy(T targetObject, MethodInterceptor methodInterceptor){ return (T)Enhancer.create(targetObject.getClass(), methodInterceptor); } } ``` 1. JDK動態代理和cglib位元組碼生成的區別? - JDK動態代理只能對實現了介面的類生成代理,而不能針對類。 - Cglib是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,並覆蓋其中方法的增強,但是因為採用的是繼承 ,所以該類或方法最好不要生成final,對於final類或方法,是無法繼承的。 2.如何選擇是用jdk動態代理還是cglib動態代理? Spring如何選擇是用JDK還是cglib i. 當bean實現介面時,會用JDK代理模式 ii. 當bean沒有實現介面,用cglib實現 參考部落格:https://www.cnblogs.com/rinack/p/7742