【轉載】求求你齐蔽,下次面試別再問我什么是 Spring AOP 和代理了两疚!

我們知道,Spring 中 AOP 是一大核心技術含滴,也是面試中經(jīng)常會被問到的問題卧晓,最近我在網(wǎng)上也看到很多面試題航徙,其中和 Spring AOP 相關的就有不少饺谬,這篇文章主要來總結下相關的技術點早抠,希望對大家有用。

  1. 幾個常見的問題

針對這一塊的東西碑韵,一般下面幾個問題面試官問的比較多:

?   Spring AOP用的是哪種設計模式赡茸?
?   談談你對代理模式的理解?
?   靜態(tài)代理和動態(tài)代理有什么區(qū)別祝闻?
?   如何實現(xiàn)動態(tài)代理占卧?
?   Spring AOP中用的是哪種代理技術?

如果這些問題都能回答的很流暢的話治筒,說明對代理這一塊的基本知識有一定的了解了屉栓。因為我們在實際開發(fā)中,寫業(yè)務代碼會更多耸袜,所以這一塊的東西友多,大部分人可能知道個一二,但是如果讓他們很有條理的表達出來堤框,可能就不那么容易了域滥。

  1. 什么是 Spring AOP?

一般面試官問到這個問題蜈抓,面試者基本上都會回答:AOP 就是面向切面編程启绰。其實這真的是句廢話,這么回答真的沒有任何意義沟使。

或許你可以給面試官舉個例子:歌星都有好多助理委可,歌星最重要的一件事就是唱歌,其他事他不用關注,比如唱歌前可能需要和其他人談合作着倾,還要布置場地拾酝,唱歌后還要收錢等等,這些統(tǒng)統(tǒng)交給他對應的助理去做卡者。也許哪一天蒿囤,這個歌星做慈善,免費唱歌了崇决,不收錢了材诽,那么就可以把收錢這個助力給辭退了。這就是 AOP恒傻,每個人各司其職脸侥,靈活組合,達到一種可配置的碌冶、可插拔的程序結構湿痢。AOP 的實現(xiàn)原理就是代理模式涝缝。

在程序中也是如此扑庞,通過代理,可以詳細控制訪問某個或者某類對象的方法拒逮,在調(diào)用這個方法前做前置處理罐氨,調(diào)用這個方法后做后置處理。

  1. 什么是代理模式滩援?

代理模式的核心作用就是通過代理栅隐,控制對對象的訪問。它的設計思路是:定義一個抽象角色玩徊,讓代理角色和真實角色分別去實現(xiàn)它租悄。

真實角色:實現(xiàn)抽象角色,定義真實角色所要實現(xiàn)的業(yè)務邏輯恩袱,供代理角色調(diào)用泣棋。它只關注真正的業(yè)務邏輯,比如歌星唱歌畔塔。

代理角色:實現(xiàn)抽象角色潭辈,是真實角色的代理,通過真實角色的業(yè)務邏輯方法來實現(xiàn)抽象方法澈吨,并在前后可以附加自己的操作把敢,比如談合同,布置場地谅辣,收錢等等修赞。

這就是代理模式的設計思路。代理模式分為靜態(tài)代理和動態(tài)代理桑阶。靜態(tài)代理是我們自己創(chuàng)建一個代理類柏副,而動態(tài)代理是程序自動幫我們生成一個代理熙尉,我們就不用管了。下面我詳細介紹一下這兩種代理模式搓扯。

  1. 靜態(tài)代理模式

就舉明星唱歌這個例子检痰,根據(jù)上面提供的設計思路,首先我們需要創(chuàng)建明星這個抽象角色锨推,

/**

  • 明星接口類

  • @author shengwu ni

  • @date 2018-12-07

*/

public interface Star {

/**

* 唱歌方法

*/

void sing();

}

靜態(tài)代理需要創(chuàng)建真實角色和代理角色铅歼,分別實現(xiàn)唱歌這個接口,真實角色很簡單换可,直接實現(xiàn)即可椎椰,因為真實角色的主要任務就是唱歌。

/**

  • 真實明星類

  • @author shengwu ni

  • @date 2018-12-08
    */
    public class RealStar implements Star {

    @Override
    public void sing() {
    System.out.println("明星本人開始唱歌……");
    }
    }

代理類就需要做點工作了沾鳄,我們思考一下慨飘,代理只是在明星唱歌前后做一些準備和收尾的事,唱歌這件事還得明星親自上陣译荞,代理做不了瓤的。所以代理類里面是肯定要將真實的對象傳進來。有了思路吞歼,我們將代理類寫出來圈膏。

/**

  • 明星的靜態(tài)代理類

  • @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對象給其賦值尿褪,然后在唱歌這個方法里,使用真實對象來唱歌得湘。所以說面談杖玲、收錢都是由代理對象來實現(xiàn)的,唱歌是代理對象讓真實對象來做忽刽。下面寫個客戶端測試下天揖。

/**

  • 測試客戶端

  • @author shengwu ni

  • @date 2018-12-08
    */
    public class Client {

    /**

    • 測試靜態(tài)代理結果

    • @param args args
      */
      public static void main(String[] args) {
      Star realStar = new RealStar();
      Star proxy = new ProxyStar(realStar);

      proxy.sing();
      }
      }

讀者可以自己運行下結果,靜態(tài)代理比較簡單跪帝。動態(tài)代理比靜態(tài)代理使用的更廣泛今膊,動態(tài)代理在本質上,代理類不用我們來管伞剑,我們完全交給工具去生成代理類即可斑唬。動態(tài)代理一般有兩種方式:JDK 動態(tài)代理和 CGLIB 動態(tài)代理。

  1. JDK 動態(tài)代理

既然動態(tài)代理不需要我們?nèi)?chuàng)建代理類,那我們只需要編寫一個動態(tài)處理器就可以了恕刘。真正的代理對象由 JDK 在運行時為我們動態(tài)的來創(chuàng)建缤谎。

/**

  • 動態(tài)代理處理類

  • @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() 方法,該方法接收三個參數(shù):第一個參數(shù)指定當前目標對象使用的類加載器,獲取加載器的方法是固定的褐着;第二個參數(shù)指定目標對象實現(xiàn)的接口的類型坷澡;第三個參數(shù)指定動態(tài)處理器,執(zhí)行目標對象的方法時,會觸發(fā)事件處理器的方法含蓉。網(wǎng)上針對第三個參數(shù)的寫法都是 new 一個匿名類來處理频敛,我這直接用的 Java8 里面的 lamda 表達式來寫的,都一樣馅扣。底層原理使用的是反射機制斟赚。接下來寫一個客戶端程序來測試下。

/**

  • 測試客戶端

  • @author shengwu ni

  • @date 2018-12-08
    */
    public class Client {

    /**

    • 測試JDK動態(tài)代理結果

    • @param args args
      */
      public static void main(String[] args) {
      Star realStar = new RealStar();
      // 創(chuàng)建一個代理對象實例
      Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance();

      proxy.sing();
      }
      }

可以看出差油,創(chuàng)建一個真實的對象拗军,送給 JdkProxyHandler 就可以創(chuàng)建一個代理對象了。

我們對 JDK 動態(tài)代理做一個簡單的總結:相對于靜態(tài)代理蓄喇,JDK 動態(tài)代理大大減少了我們的開發(fā)任務发侵,同時減少了對業(yè)務接口的依賴,降低了耦合度公罕。JDK 動態(tài)代理是利用反射機制生成一個實現(xiàn)代理接口的匿名類器紧,在調(diào)用具體方法前調(diào)用InvokeHandler 來處理。但是 JDK 動態(tài)代理有個缺憾楼眷,或者說特點:JDK 實現(xiàn)動態(tài)代理需要實現(xiàn)類通過接口定義業(yè)務方法。也就是說它始終無法擺脫僅支持 interface 代理的桎梏熊尉,因為它的設計就注定了這個遺憾罐柳。

  1. CGLIB 動態(tài)代理

由上面的分析可知,JDK 實現(xiàn)動態(tài)代理需要實現(xiàn)類通過接口定義業(yè)務方法狰住,那對于沒有接口的類张吉,如何實現(xiàn)動態(tài)代理呢,這就需要 CGLIB 了催植。

CGLIB 采用了非常底層的字節(jié)碼技術肮蛹,其原理是通過字節(jié)碼技術為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術攔截所有父類方法的調(diào)用创南,順勢織入橫切邏輯伦忠。但因為采用的是繼承,所以不能對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中的一個字節(jié)碼增強器,它可以方便的對你想要處理的類進行擴展
    Enhancer enhancer = new Enhancer();
    // 將被代理的對象設置成父類
    enhancer.setSuperclass(this.target.getClass());
    // 回調(diào)方法,設置攔截器
    enhancer.setCallback(this);
    // 動態(tài)創(chuàng)建一個代理類
    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 需要實現(xiàn) MethodInterceptor 接口赋咽,并重寫intercept 方法旧噪,在該方法中對原始要執(zhí)行的方法前后做增強處理。該類的代理對象可以使用代碼中的字節(jié)碼增強器來獲取脓匿。接下來寫個客戶端測試程序淘钟。

/**

  • 測試客戶端

  • @author shengwu ni

  • @date 2018-12-08
    */
    public class Client {

    /**

    • 測試Cglib動態(tài)代理結果

    • @param args args
      */
      public static void main(String[] args) {
      Star realStar = new RealStar();
      Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);

      proxy.sing();
      }
      }

這個客戶端測試程序和 JDK 動態(tài)代理的邏輯一模一樣,所以也可以看出陪毡,代理模式中的動態(tài)代理日月,其實套路都是相同的,只是使用了不同的技術而已缤骨。

我們也對 CGLIB 動態(tài)代理做一下總結:CGLIB 創(chuàng)建的動態(tài)代理對象比 JDK 創(chuàng)建的動態(tài)代理對象的性能更高爱咬,但是 CGLIB 創(chuàng)建代理對象時所花費的時間卻比 JDK 多得多。所以對于單例的對象绊起,因為無需頻繁創(chuàng)建對象精拟,用 CGLIB 合適,反之使用JDK方式要更為合適一些虱歪。同時由于 CGLIB 由于是采用動態(tài)創(chuàng)建子類的方法蜂绎,對于final修飾的方法無法進行代理。

當然了笋鄙,不管是哪種動態(tài)代理技術师枣,在上面的代碼里,要代理的類中可能不止一種方法萧落,有時候我們需要對特定的方法進行增強處理践美,所以可以對傳入的 method 參數(shù)進行方法名的判斷,再做相應的處理找岖。

  1. Spring AOP 采用哪種代理陨倡?

JDK 動態(tài)代理和 CGLIB 動態(tài)代理均是實現(xiàn) Spring AOP 的基礎。對于這一塊內(nèi)容许布,面試官問的比較多兴革,他們往往更想聽聽面試者是怎么回答的,有沒有看過這一塊的源碼等等蜜唾。

針對于這一塊內(nèi)容杂曲,我們看一下 Spring 5 中對應的源碼是怎么說的。

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動態(tài)代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 配置了使用CGLIB進行動態(tài)代理或者目標類沒有接口擎勘,那么使用CGLIB的方式創(chuàng)建代理對象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面的三個方法沒有一個為true,那使用JDK的提供的代理方式生成代理對象
return new JdkDynamicAopProxy(config);
}
}
//其他方法略……
}

從上述源碼片段可以看出泌霍,是否使用 CGLIB 是在代碼中進行判斷的货抄,判斷條件是 config.isOptimize()述召、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。

其中蟹地,config.isOptimize() 與 config.isProxyTargetClass()默認返回都是 false积暖,這種情況下判斷結果就由

hasNoUserSuppliedProxyInterfaces(config)的結果決定了。

簡單來說怪与,

hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的對象是否有實現(xiàn)接口夺刑,有實現(xiàn)接口的話直接走 JDK 分支,即使用 JDK 的動態(tài)代理分别。

所以基本上可以總結出 Spring AOP 中的代理使用邏輯了:如果目標對象實現(xiàn)了接口遍愿,默認情況下會采用 JDK 的動態(tài)代理實現(xiàn) AOP;如果目標對象沒有實現(xiàn)了接口耘斩,則采用 CGLIB 庫沼填,Spring 會自動在 JDK 動態(tài)代理和 CGLIB 動態(tài)代理之間轉換。

當然括授,源碼我也沒讀那么深坞笙,暫且就只能寫到這,后面深入了荚虚,有新的見解再給大家分享薛夜。還記得文章開頭的幾個問題嗎?相信你讀到這里版述,心中應該已經(jīng)有了答案了梯澜。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渴析,隨后出現(xiàn)的幾起案子晚伙,更是在濱河造成了極大的恐慌,老刑警劉巖檬某,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撬腾,死亡現(xiàn)場離奇詭異,居然都是意外死亡恢恼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門胰默,熙熙樓的掌柜王于貴愁眉苦臉地迎上來场斑,“玉大人,你說我怎么就攤上這事牵署÷┮” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵奴迅,是天一觀的道長青责。 經(jīng)常有香客問我挺据,道長,這世上最難降的妖魔是什么脖隶? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任扁耐,我火速辦了婚禮,結果婚禮上产阱,老公的妹妹穿的比我還像新娘婉称。我一直安慰自己,他們只是感情好构蹬,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布王暗。 她就那樣靜靜地躺著,像睡著了一般庄敛。 火紅的嫁衣襯著肌膚如雪俗壹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天藻烤,我揣著相機與錄音绷雏,去河邊找鬼。 笑死隐绵,一個胖子當著我的面吹牛之众,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播依许,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棺禾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了峭跳?” 一聲冷哼從身側響起膘婶,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛀醉,沒想到半個月后悬襟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡拯刁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年脊岳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垛玻。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡割捅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出帚桩,到底是詐尸還是另有隱情亿驾,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布账嚎,位于F島的核電站莫瞬,受9級特大地震影響儡蔓,放射性物質發(fā)生泄漏。R本人自食惡果不足惜疼邀,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一喂江、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧檩小,春花似錦开呐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至阻肿,卻和暖如春瓦戚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丛塌。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工较解, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赴邻。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓印衔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姥敛。 傳聞我的和親對象是個殘疾皇子奸焙,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容