設計模式:代理模式是什麼,Spring AOP還和它有關係?

接着學習設計模式系列,今天講解的是代理模式。面試

定義

什麼是代理模式?設計模式

代理模式,也叫委託模式,其定義是給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。它包含了三個角色:bash

Subject:抽象主題角色。能夠是抽象類也能夠是接口,是一個最普通的業務類型定義。maven

RealSubject:具體主題角色,也就是被代理的對象,是業務邏輯的具體執行者。ide

Proxy:代理主題角色。負責讀具體主題角色的引用,經過真實角色的業務邏輯方法來實現抽象方法,並在先後能夠附加本身的操做。性能

用類圖來表示的話大概以下:學習

咱們能夠用舉一個電影演員拍戲的例子,通常來講,演員最主要的工做就是演戲,其餘的事能夠交給他的經紀人去作,例如談合同,安排檔期等等,而負責這些場外工做的經紀人就至關於 Proxy,而負責核心業務的演員就是 RealSubject

這就是代理模式的設計思路,除此以外,代理模式分爲靜態代理和動態代理,靜態代理是咱們本身建立一個代理類,而動態代理是程序自動幫咱們生成一個代理類,能夠在程序運行時再生成對象,下面分別對它們作介紹。測試

靜態代理

靜態代理在程序運行以前,代理類.class文件就已經被建立了。仍是用上面演員演戲的例子,在靜態代理模式中,咱們要先建立一個抽象主題角色 Starui

public interface Star {
    // 演戲
    void act();
}
複製代碼

接下來就是建立具體的主題角色和代理主題角色,分別實現這個接口,先建立一個具體的主題角色 Actorthis

/**
 * 演員,也就是具體的主題角色
 *
 * @author Tao
 * @since 2019/7/9 18:34
 */
public class Actor implements Star {
    public void act() {
        System.out.println("演員演戲~~~");
    }
}
複製代碼

而後就是建立代理主題角色,也就是代理類,代理類自己並不負責核心業務的執行流程,演戲這事還得明星本身來。因此在代理類中須要將真實對象引入,下面是具體的代碼實現:

/**
 * 代理對象
 * @author Tao
 * @since 2019/7/9 18:43
 */
public class Agent implements Star {
    /**
     * 接收真實的明星對象
     */
    private Star star;

    /**
     * 經過構造方法傳進來真實的明星對象
     *
     * @param star star
     */
    public Agent(Star star) {
        this.star = star;
    }

    public void act() {
        System.out.println("籤合同");
        star.act();
        System.out.println("演完戲就收錢了");
    }
}
複製代碼

代碼的邏輯仍是比較清晰的,經過維護一個Star對象,能夠在act裏調用具體主題角色的業務邏輯,而且在覈心邏輯先後能夠作一些輔助操做,好比籤合同,收錢等,這樣代理模式的角色就都分工完成了,最後用一個場景類來驗證下:

public class Client {
    public static void main(String[] args) {
        Star actor = new Actor();
        Agent agent = new Agent(actor);
        agent.act();
    }
}
複製代碼

運行的結果以下:

籤合同 演員演戲~~~ 演完戲就收錢了

動態代理

動態代理分爲兩種,分別是JDK動態代理和 CGLIB 動態代理,怎麼又分了,代理模式分類真多,不過來都來了,就都學習一下吧。

JDK動態代理

前面說了,在動態代理中咱們再也不須要再手動的建立代理類,咱們只須要編寫一個動態處理器就能夠了。真正的代理對象由JDK再運行時幫咱們動態的來建立。

/**
 * 動態代理處理類
 *
 * @author Tao
 * @since 2019/7/9 19:04
 */
public class JdkProxyHandler {

    /**
     * 用來接收真實明星對象
     */
    private Object star;

    /**
     * 經過構造方法傳進來真實的明星對象
     *
     * @param star star
     */
    public JdkProxyHandler(Star star) {
        super();
        this.star = star;
    }

    /**
     * 給真實對象生成一個代理對象實例
     *
     * @return Object
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(star.getClass().getClassLoader(),
                star.getClass().getInterfaces(), (proxy, method, args) -> {

                    System.out.println("籤合同");
                    // 執行具體的業務邏輯
                    Object object = method.invoke(star, args);
                    System.out.println("演出完經紀人去收錢……");

                    return object;
                });
    }
}
複製代碼

這裏說一下Proxy.newProxyInstance 這個方法,該方法包含了三個參數,

  • ClassLoader loader:指定當前目標對象使用的類加載器,獲取加載器的方法是固定的;
  • Class<?>[] interfaces:指定目標對象實現的接口的類型,使用泛型方式確認類型;
  • InvocationHandler:指定動態處理器,執行目標對象的方法時會觸發事件處理器的方法。

寫完了動態代理實現類,咱們寫個場景類測試下,

public class Client {
    public static void main(String[] args) {
        Star actor = new Actor();
        // 建立動態代理對象實例
        Star jdkProxy = (Star) new JdkProxyHandler(actor).getProxyInstance();
        jdkProxy.act();
    }
}
複製代碼

執行結果正常輸出:

籤合同 演員演戲~~~ 演出完代理去收錢……

因而可知,JDK 動態代理確實發揮了代理的功能,相對於靜態代理,JDK 動態代理大大減小了咱們的開發任務,同時減小了對業務接口的依賴,下降了耦合度。但它一樣有缺陷,就是動態代理的實現類須要類實現接口來完成代理的業務,也就是說它始終沒法擺脫僅支持interface代理的桎梏,這是設計上的缺陷。而這時CGLIB 動態代理就派上用場了。

CGLIB 動態代理

CGLib採用了很是底層的字節碼技術,其原理是經過字節碼技術爲一個類建立子類,並在子類中採用方法攔截的技術攔截全部父類方法的調用,順勢織入橫切邏輯。但由於採用的是繼承,因此不能對final修飾的類進行代理。下面咱們寫一個關於CGLib的動態代理類,值得說下的是,CGLib所在的依賴包不是JDK自己就有的,因此咱們須要額外引入,若是是用maven來管理的話,就能夠直接引入以下的依賴:

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.3</version>
    </dependency>
</dependencies>
複製代碼

使用 CGLIB 須要實現 MethodInterceptor 接口,並重寫intercept 方法,在該方法中對原始要執行的方法先後作加強處理。該類的代理對象可使用代碼中的字節碼加強器來獲取。具體的代碼以下:

public class CglibProxy implements MethodInterceptor {
    /**
     * 維護目標對象
     */
    private Object target;

    public Object getProxyInstance(final Object target) {
        this.target = target;
        // Enhancer類是CGLIB中的一個字節碼加強器,它能夠方便的對你想要處理的類進行擴展
        Enhancer enhancer = new Enhancer();
        // 將被代理的對象設置成父類
        enhancer.setSuperclass(this.target.getClass());
        // 回調方法,設置攔截器
        enhancer.setCallback(this);
        // 動態建立一個代理類
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("籤合同");
        // 執行具體的業務邏輯
        Object result = methodProxy.invoke(o, objects);
        System.out.println("演出完經紀人去收錢……");
        return result;
    }
}

場景測試類:
複製代碼
public class Client {
    public static void main(String[] args) {
        Star actor = new Actor();
        // 建立動態代理對象實例
        Star proxy = (Star) new CglibProxy().getProxyInstance(actor);
        proxy.act();
    }
}
複製代碼

能夠看出,測試類的邏輯和JDK動態代理差很少,其實套路都是同樣的,其實技術實現不一樣。

總結一下CGLIB代理模式: CGLIB建立的動態代理對象比JDK建立的動態代理對象的性能更高,可是CGLIB建立代理對象時所花費的時間卻比JDK多得多。因此對於單例的對象,由於無需頻繁建立對象,用CGLIB合適,反之使用JDK方式要更爲合適一些。同時因爲CGLib因爲是採用動態建立子類的方法,對於final修飾的方法沒法進行代理。

擴展知識

這裏擴展一個知識點,那就是Spring AOP的底層實現,爲何在這裏說起呢?由於Spring AOP的底層實現就是基於代理模式,而JDK 動態代理和 CGLIB 動態代理均是實現 Spring AOP 的基礎。咱們能夠看下AOP的部分底層源碼:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            // 判斷目標類是不是接口或者目標類是否Proxy類型,如果則使用JDK動態代理
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            // 使用CGLIB的方式建立代理對象
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            // 上面條件都不知足就使用JDK的提供的代理方式生成代理對象
            return new JdkDynamicAopProxy(config);
        }
    }
}

複製代碼

源碼的判斷邏輯並不難,主要是根據目標類是不是接口或者Proxy類型來判斷使用哪一種代理模式建立代理對象,使用的代理模式正是JDK動態代理和CGLIB 動態代理技術。因而可知,瞭解代理模式仍是很重要的,起碼之後面試官問AOP的底層實現時,咱們還能吹一波呢,哈哈~~~

相關文章
相關標籤/搜索