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

前言

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

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

二、除了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();
    }
}

打印==============ide

before dosth
do sth
after dosth工具

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

缺陷

靜態代理很是簡單,可是它的缺陷也是顯然的,由於靜態代理的代理關係在 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 方法。

綜上得知:

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

二、CGLib 能夠實現對方法的代理,便可以實現攔截(只代理)某個方法。

三、經過CGLib 的 Enhancer 類來create 代理對象。而對這個對象全部非final方法的調用都會委託給 MethodInterceptor 接口的 intercept,咱們能夠在該方法內部寫攔截代碼,最後在經過調用MethodProxy 對象的 invokeSuper() 方法,把調用轉發給真實對象

缺陷

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

代理模式的性能對比

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

代理模式的缺點

主要是性能問題,什麼增長系統複雜度等都不是事兒。同等條件,用代理,確定比不用代理要慢一些。

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

實現方式上很像,可是目標不同,後者是爲了給類(對象)增長新的功能,不改變API,前者除了這些做用,目標主要是爲了使用中間人(代理角色)給本類(對象)減小負擔。

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

代理模式的應用

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

相關文章
相關標籤/搜索