Spring AOP代理模式的介紹

常見問題

  • Spring AOP用的是哪種設計模式?
  • 談談你對代理模式的理解?
  • 靜態代理和動態代理有什麼區別?
  • 如何實現動態代理?
  • Spring AOP中用的是哪種代理技術?

什麼是Spring AOP?

  • AOP面向切面編程,每個人各司其職,靈活組合,達到一種可配置的、可插拔的程序結構。AOP的實現原理就是代理模式。程序中,通過代理,可以詳細控制訪問某個類或者某個類對象的方法,在調用這個方法前做前置處理,調用這個方法後做後置處理。

什麼是代理模式?

  • 核心作用:通過代理,控制對對象的訪問。設計思路:定義一個抽象角色,讓代理角色和真實角色分別取實現它。
    • 真實角色:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用。只關注真正的業務邏輯。
    • 代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方式來實現抽象方法,並且可以在前後附加自己的操作。

靜態代理模式

  • 就舉明星唱歌這個例子,根據上面提供的設計思路,首先我們需要創建明星這個抽象角色,
/**

* 明星接口類

* @author shengwu ni

* @date 2018-12-07

*/

public interface Star {


   /**

    * 唱歌方法

    */

   void sing();


}
  • 靜態代理需要創建真實角色和代理角色,分別實現唱歌這個接口,真實角色很簡單,直接實現即可,因爲真實角色的主要任務就是唱歌。
/**
* 真實明星類
* @author shengwu ni
* @date 2018-12-08
*/
public class RealStar implements Star {

   @Override
   public void sing() {
       System.out.println("明星本人開始唱歌……");
   }
}
  • 代理類就需要做點工作了,我們思考一下,代理只是在明星唱歌前後做一些準備和收尾的事,唱歌這件事還得明星親自上陣,代理做不了。所以代理類裏面是肯定要將真實的對象傳進來。有了思路,我們將代理類寫出來。
/**
* 明星的靜態代理類
*
* @author shengwu ni
* @date 2018-12-08
*/
public class ProxyStar implements Star {

   /**
    * 接收真實的明星對象
    */
   private Star star;

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

   @Override
   public void sing() {
       System.out.println("代理先進行談判……");
       // 唱歌只能明星自己唱
       this.star.sing();
       System.out.println("演出完代理去收錢……");
   }

}
  • 這樣的話,邏輯就非常清晰了。在代理類中,可以看到,維護了一個Star對象,通過構造方法傳進來一個真實的Star對象給其賦值,然後在唱歌這個方法裏,使用真實對象來唱歌。所以說面談、收錢都是由代理對象來實現的,唱歌是代理對象讓真實對象來做。下面寫個客戶端測試下。
/**
* 測試客戶端
* @author shengwu ni
* @date 2018-12-08
*/
public class Client {

   /**
    * 測試靜態代理結果
    * @param args args
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       Star proxy = new ProxyStar(realStar);

       proxy.sing();
   }
}
  • 讀者可以自己運行下結果,靜態代理比較簡單。動態代理比靜態代理使用的更廣泛,動態代理在本質上,代理類不用我們來管,我們完全交給工具去生成代理類即可。動態代理一般有兩種方式:JDK 動態代理和 CGLIB 動態代理。

JDK 動態代理

  • 既然動態代理不需要我們去創建代理類,那我們只需要編寫一個動態處理器就可以了。真正的代理對象由 JDK 在運行時爲我們動態的來創建。
/**
* 動態代理處理類
*
* @author shengwu ni
* @date 2018-12-08
*/
public class JdkProxyHandler {

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

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

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

                   System.out.println("代理先進行談判……");
                   // 唱歌需要明星自己來唱
                   Object object = method.invoke(realStar, args);
                   System.out.println("演出完代理去收錢……");

                   return object;
               });
   }
}
  • 這裏說一下 Proxy.newProxyInstance() 方法,該方法接收三個參數:第一個參數指定當前目標對象使用的類加載器,獲取加載器的方法是固定的;第二個參數指定目標對象實現的接口的類型;第三個參數指定動態處理器,執行目標對象的方法時,會觸發事件處理器的方法。網上針對第三個參數的寫法都是 new 一個匿名類來處理,我這直接用的 Java8 裏面的 lamda 表達式來寫的,都一樣。底層原理使用的是反射機制。接下來寫一個客戶端程序來測試下。
/**
* 測試客戶端
* @author shengwu ni
* @date 2018-12-08
*/
public class Client {

   /**
    * 測試JDK動態代理結果
    * @param args args
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       // 創建一個代理對象實例
       Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance();

       proxy.sing();
   }
}
  • 可以看出,創建一個真實的對象,送給 JdkProxyHandler 就可以創建一個代理對象了。

  • 我們對 JDK 動態代理做一個簡單的總結:相對於靜態代理,JDK 動態代理大大減少了我們的開發任務,同時減少了對業務接口的依賴,降低了耦合度。JDK 動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler 來處理。但是 JDK 動態代理有個缺憾,或者說特點:JDK 實現動態代理需要實現類通過接口定義業務方法。也就是說它始終無法擺脫僅支持 interface 代理的桎梏,因爲它的設計就註定了這個遺憾。

CGLIB 動態代理

  • 由上面的分析可知,JDK 實現動態代理需要實現類通過接口定義業務方法,那對於沒有接口的類,如何實現動態代理呢,這就需要 CGLIB 了。

  • CGLIB 採用了非常底層的字節碼技術,其原理是通過字節碼技術爲一個類創建子類,並在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。但因爲採用的是繼承,所以不能對final修飾的類進行代理。我們來寫一個 CBLIB 代理類。

/**
* cglib代理處理類
* @author shengwu ni
* @date 2018-12-08
*/
public class CglibProxyHandler 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();
   }

   @Override
   public Object intercept(Object object, Method method, Object[] args,
           MethodProxy methodProxy) throws Throwable {

       System.out.println("代理先進行談判……");
       // 唱歌需要明星自己來唱
       Object result = methodProxy.invokeSuper(object, args);
       System.out.println("演出完代理去收錢……");
       return result;
   }
}
  • 使用 CGLIB 需要實現 MethodInterceptor 接口,並重寫intercept 方法,在該方法中對原始要執行的方法前後做增強處理。該類的代理對象可以使用代碼中的字節碼增強器來獲取。接下來寫個客戶端測試程序。
/**
* 測試客戶端
* @author shengwu ni
* @date 2018-12-08
*/
public class Client {

   /**
    * 測試Cglib動態代理結果
    * @param args args
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);

       proxy.sing();
   }
}
  • 這個客戶端測試程序和 JDK 動態代理的邏輯一模一樣,所以也可以看出,代理模式中的動態代理,其實套路都是相同的,只是使用了不同的技術而已。

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

  • 當然了,不管是哪種動態代理技術,在上面的代碼裏,要代理的類中可能不止一種方法,有時候我們需要對特定的方法進行增強處理,所以可以對傳入的 method 參數進行方法名的判斷,再做相應的處理。

Spring AOP 採用哪種代理?

  • JDK動態代理和CGLIB動態代理均是實現Spring AOP的基礎。Spring5 中的源碼。

  • 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進行動態代理或者目標類沒有接口,那麼使用CGLIB的方式創建代理對象
         return new ObjenesisCglibAopProxy(config);
       }
       else {
           // 上面的三個方法沒有一個爲true,那使用JDK的提供的代理方式生成代理對象
         return new JdkDynamicAopProxy(config);
       }
     }
       //其他方法略……
    }
    
  • config.isOptimize() || config.isProxyTargetClass()都是false

  • hasNoUserSuppliedProxyInterfaces(config)判斷代理的對象是否有實現接口,有實現接口的話直接走JDK分支,即使用JDK的動態代理。

  • Spring AOP中的代理使用總結:如果目標對象實現了接口,默認情況下回採用JDK動態代理實現AOP;如果目標對象沒有實現接口,則採用CGLIB庫,Spring會自動在JDK動態代理和CGLIB動態代理之間轉換。

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