AOP 技術原理——代理模式全面總結


前言

非常重要的一個設計模式,也很常見,很多框架都有它的影子。定義就不多說了。兩點:

1、為其它對象提供一個代理服務,間接控制對這個對象的訪問,聯想 Spring 事務機制,在合適的方法上加個 transaction 注解,就分分鍾實現了事務。

2、除了1,代理對象還能充當中介的角色。

為什么要有代理模式?

如果希望不給原有對象附加太多的責任(和本對象無關的冗余代碼),但是還想能為其實現新功能,那么代理模式就是做這個的。還是聯系 Spring 事務機制,很好的應用場景。

實際生活里,可以聯系租房,大家租房一般都會找中介,這個中介就是代理對象的角色,它解耦(分離)了房東的一部分責任,因為房東太忙了,或者房東不屑於做這些事情,故交給代理對象去做。

一句話:解耦合,提高擴展性

靜態代理模式

顧名思義,代理類被顯示的指定了,即代理在代碼里被寫死了。實現最簡單,也很少用,但是能幫助快速理解思想

public interface StaticProxy {
    void dosth();
}
//////////////接下來是很熟悉的做法,實現這個借口
public class RealRole implements StaticProxy {

    @Override
    public void dosth() {
        System.out.println("do sth");
    }
}
///////////然后重要的角色——代理類,聯系 Spring 事務機制
public class ProxyRole implements StaticProxy {
    private StaticProxy staticProxy;

    public ProxyRole() {
        this.staticProxy = new RealRole();
    }

    @Override
    public void dosth() {
        // 真正業務邏輯之前的處理,比如加上事務控制
        before();
        this.staticProxy.dosth(); // 真正的業務邏輯處理,比如數據庫的 crud
        after(); // 善后處理,比如,事務提交
    }

    private void after() {
        System.out.println("after dosth");
    }

    private void before() {
        System.out.println("before dosth");
    }
}
////////執行
public class ProxyMain {
    public static void main(String[] args) {
        StaticProxy staticProxy = new ProxyRole();
        staticProxy.dosth();
    }
}

打印==============

before dosth
do sth
after dosth

如上就是最簡單的靜態代理模式的實現,很直觀,就是使用委托的思想,把責任轉移到被代理的對象上,代理類實現非業務相關的功能

缺陷

靜態代理非常簡單,但是它的缺陷也是顯然的,因為靜態代理的代理關系在 IDE 編譯時就確定了,如果接口改變了,不僅實現類要改變,代理類也要改變,代理類和接口之間的耦合非常嚴重。

動態代理模式

和靜態代理相反,代理類不是寫死的,而是動態的創建。又分為兩種實現方案:

基於 JDK實現

也很簡單,就是利用 Java 的 API 來實現代理類,即我們不用自己寫代理類了,也就是上面例子里的類——ProxyRole。到這里,其實也能猜出來,本質就是利用 Java 的反射機制在程序運行期動態的創建接口的實現類,並生產代理對象而已,如此一來,就能避免實現的接口——StaticProxy 改變了,導致代理類也跟着變的場景發生。下面看實現代碼:

首先寫好需要實現的接口,和具體實現類

public interface DynamicProxy {
    void dosth();
}
////////////
public class DynamicRealRole implements DynamicProxy {
    @Override
    public void dosth() {
        System.out.println("do sth");
    }
}

然后,要實現 JDK 的一個接口——InvocationHandler,Java 的動態代理機制中,有兩個重要的類,一個是 InvocationHandler 接口,一個是 Proxy 類。

注意,DynamicProxyRole 不是代理類,代理類我們不需要自己寫,它是 JDK 動態生成給我們的(反射機制)

public class DynamicProxyRole implements InvocationHandler {
    private Object object; // 被代理的對象

    public DynamicProxyRole(Object object) { // 構造注入
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object ret = method.invoke(object, args);
        after();

        return ret;
    }

    private void after() {
        System.out.println("after dosth");
    }

    private void before() {
        System.out.println("before dosth");
    }
}

InvocationHandler 接口只有一個方法 —— invoke,參數也很好理解,分別是:

proxy:代理的真實對象,也就是實現類的對象

method:要調用真實對象的某個方法的Method對象,也就是會調用 dosth 的方法的對象

args:調用真實對象某個方法時接受的參數,沒有就是空數組

最后寫運行類

public class DynamicMain {
    private static DynamicProxy realRole = new DynamicRealRole(); // 被代理的類,也就是實現類
    private static DynamicProxyRole dynamicProxyRole = new DynamicProxyRole(realRole);

    public static void main(String[] args) {
        // 通過JDK動態代理獲取被代理對象(實現類的對象)的代理對象,該代理類實現了指定的需要去代理的接口,也就是第2個參數
        DynamicProxy dynamicProxyObj = (DynamicProxy) Proxy.newProxyInstance(
                realRole.getClass().getClassLoader(), // 被代理的類的加載器
                realRole.getClass().getInterfaces(), // 被代理的類需要實現的接口,可以有多個
                dynamicProxyRole // 必須是實現了 InvocationHandler 接口的類,invoke 方法里寫業務邏輯和代理方法
        );
        dynamicProxyObj.dosth();
    }
}

Proxy 類的作用是動態創建一個代理對象,也就是代理對象不需要我們自己寫。Proxy 提供了許多的方法,用的最多的是 newProxyInstance,注釋里也寫了:

其中第一個參數是被代理的類的加載器,傳入的目的是告訴 JDK 由哪個類加載器對生成的代理進行加載。其實就是真實的類(實現類)的對象的加載器。

第二個參數是代理類需要實現的接口,可以多個。其實就是接口 DynamicProxy,很好理解,在靜態代理模式中,我們就需要手動實現這個接口,來實現代理類。

第三個參數就是實現了InvocationHandler接口的類即可,原因是此類里有 invoke 方法,而通過 Proxy 的 newProxyInstance 方法生成的代理類去調用接口方法(dosth)時,對方法(dosth)的調用會自動委托給 InvocationHandler 接口的 invoke 方法,這樣也就實現了代理模式。

綜上,代理對象就實現了在程序運行時產生。進一步要知道,所有的 JDK 動態代理都會繼承 java.lang.reflect.Proxy,同時還會實現我們指定的接口(Proxy 的 newProxyInstance 第二個參數里的接口)。

看到這里,也確定,JDK 動態代理核心就是反射思想的應用,沒什么新鮮的東西。

缺陷

JDK 動態代理這種方式只能代理接口,這是其缺陷

基於 CGLib 實現

Java動態代理是基於接口實現的,如果對象沒有實現接口,那么可以用 CGLIB 類庫實現,它的原理是基於繼承實現代理類。代碼也不難

首先,寫一個類,其沒有實現接口,此時前面的 JDK 動態代理就無法使用了。

public class NoInterfaceReal {
    public void dosth() {
        System.out.println("do sth");
    }
}

其次,需要實現CGLIB 類庫提供的接口——MethodInterceptor

在這之前,先下載CGLib 包

        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.10</version>
        </dependency>

然后實現其提供的接口——MethodInterceptor,關鍵方法是 intercept

public class CGLibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object ret = proxy.invokeSuper(obj, args);
        after();

        return ret;
    }

    private void after() {
        System.out.println("after dosth");
    }

    private void before() {
        System.out.println("before dosth");
    }
}

實現了 MethodInterceptor 接口后,后續生成的代理對象對 dosth 方法的調用會被轉發到 intercept 方法,自然也就實現了代理模式。

最后,通過 CGLIB 動態代理生成代理對象,就完成了代理模式,非常簡單

public class CGLibMain {
    public static void main(String[] args) {
        CGLibProxy cgLibProxy = new CGLibProxy();
        NoInterfaceReal proxy = (NoInterfaceReal) Enhancer.create(NoInterfaceReal.class, cgLibProxy);
        proxy.dosth();
    }
}

通過CGLib 的Enhancer類 create 了一個代理對象,參數傳入需要被代理的類(可以不是接口),和實現了 MethodInterceptor 接口的類的對象即可。CGLib 就會為我們自動生成繼承了被代理的類的代理對象,通過代理對象調用 dosth 方法,其調用會被委托給第二個參數里的 intercept 方法。

綜上得知:

1、CGLib 底層是利用 asm 字節碼框架實現的,該框架可以在 Java 程序運行時對字節碼進行修改和動態生成,故它可以代理普通類,具體細節是通過繼承和重寫需要被代理的類(NoInterfaceReal)來實現。

2、CGLib 可以實現對方法的代理,即可以實現攔截(只代理)某個方法。

3、通過CGLib 的 Enhancer 類來create 代理對象。而對這個對象所有非final方法的調用都會委托給 MethodInterceptor 接口的 intercept,我們可以在該方法內部寫攔截代碼,最后在通過調用MethodProxy 對象的 invokeSuper() 方法,把調用轉發給真實對象

缺陷

無法對 final 類、或者 final 方法進行代理

代理模式的性能對比

直接搬運結論:CGLib 底層基於asm 框架實現,比 Java 反射性能好,但是比 JDK 動態代理稍微慢一些

代理模式的缺點

主要是性能問題,什么增加系統復雜度等都不是事兒。同等條件,用代理,肯定比不用代理要慢一些。

代理模式和裝飾器模式對比

實現方式上很像,但是目標不一樣,后者是為了給類(對象)增加新的功能,不改變API,前者除了這些作用,目標主要是為了使用中間人(代理角色)給本類(對象)減少負擔。

參見:對復合(協作)算法/策略的封裝方法——裝飾模式總結

代理模式的應用

非常常見了,AOP非常典型,還有各種框架的攔截器機制,數據庫切換等工具。。。

歡迎關注

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

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



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