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


1.靜態代理模式

代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色負責定義RealSubject和Proxy角色應該實現的接口;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的Request請求,調用realsubject 對應的request功能來實現業務功能,自己不真正做業務。

靜態代理的簡單實現:

Subject角色

public interface ToBPayment {
    void pay();
}
public interface ToCPayment {
    void pay();
}

RealSubject角色:

public class ToBPaymentImpl implements ToBPayment {
    @Override
    public void pay() {
        System.out.println("以公司的名義進行支付");
    }
}
public class ToCPaymentImpl implements ToCPayment {
    @Override
    public void pay() {
        System.out.println("以用戶的名義進行支付");
    }
}

Proxy角色:

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");
    }
}
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文件的格式和結構,生成相應的二進制數據,然后再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態創建一個類的能力了。

在運行時期可以按照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對象

簡單實現如下:

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");
    }
}
public class JdkDynamicProxyUtil {
    public static <T>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的功能和角色是一樣的

簡單實現如下:

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");
    }
}
public class CglibUtil {
    public static <T>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/7742682.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM