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

定義

什麼是代理模式?

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

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

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

Proxy:代理主題角色。負責讀具體主題角色的引用,通過真實角色的業務邏輯方法來實現抽象方法,並在前後可以附加自己的操作。

用類圖來表示的話大概如下:

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

 

我們可以用舉一個電影演員拍戲的例子,一般來說,演員最主要的工作就是演戲,其他的事可以交給他的經紀人去做,例如談合同,安排檔期等等,而負責這些場外工作的經紀人就相當於 Proxy ,而負責核心業務的演員就是 RealSubject 。

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

 

這就是代理模式的設計思路,除此之外,代理模式分爲靜態代理和動態代理,靜態代理是我們自己創建一個代理類,而動態代理是程序自動幫我們生成一個代理類,可以在程序運行時再生成對象,下面分別對它們做介紹。

靜態代理

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

public interface Star {
 // 演戲
 void act();
}

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

/**
 * 演員,也就是具體的主題角色
 *
 * @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 這個方法,該方法包含了三個參數,

指定動態處理器,

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

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 動態代理就派上用場了。

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

 

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的底層實現時,我們還能吹一波呢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章