<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          拋開Spring來說,如何自己實現(xiàn)Spring AOP?

          共 9151字,需瀏覽 19分鐘

           ·

          2021-09-19 12:34

          出處:http://segmentfault.com/a/1190000019148468


          正好遇到了一道這樣的題:拋開Spring來說,如何自己實現(xiàn)Spring AOP?

          就喜歡這樣的題,能把那些天天寫增刪改查從來不思考的人給PK下去,今天就和大家一切學習代理模式與Spring AOP。

          代理與裝飾器

          場景描述

          代理,即替代之意,可替代所有功能,即和原類實現(xiàn)相同的規(guī)范。

          代理模式和裝飾器模式很像,之前的裝飾器講的不是很好,這里換個例子再講一遍。

          寧靜的午后,來到咖啡館,想喝一杯咖啡。

          基礎實現(xiàn)

          給你一個咖啡接口:

          1. public interface Coffee {


          2.     /**

          3.      * 打印當前咖啡的原材料,即咖啡里有什么

          4.      */

          5.     void printMaterial();

          6. }

          一個默認的苦咖啡的實現(xiàn):

          1. public class BitterCoffee implements Coffee {


          2.     @Override

          3.     public void printMaterial() {

          4.         System.out.println("咖啡");

          5.     }

          6. }

          默認的點餐邏輯:

          1. public class Main {


          2.     public static void main(String[] args) {

          3.         Coffee coffee = new BitterCoffee();

          4.         coffee.printMaterial();

          5.     }

          6. }

          點一杯咖啡。

          裝飾器模式

          優(yōu)雅的服務生把咖啡端了上來,抿了一口,有些苦。

          想加點糖,對服務生說:“您好,請為我的咖啡加些糖”。

          1. /**

          2.  * 糖裝飾器,用來給咖啡加糖

          3.  */

          4. public class SugarDecorator implements Coffee {


          5.     /**

          6.      * 持有的咖啡對象

          7.      */

          8.     private final Coffee coffee;


          9.     public SugarDecorator(Coffee coffee) {

          10.         this.coffee = coffee;

          11.     }


          12.     @Override

          13.     public void printMaterial() {

          14.         System.out.println("糖");

          15.         this.coffee.printMaterial();

          16.     }

          17. }

          然后服務生就拿走了我的咖啡,去使用SugarDecorator為咖啡加糖,最后把加好糖的咖啡給我。

          1. public class Main {


          2.     public static void main(String[] args) {

          3.         Coffee coffee = new BitterCoffee();

          4.         coffee = new SugarDecorator(coffee);

          5.         coffee.printMaterial();

          6.     }

          7. }

          看一看咖啡的成分,對的,確實加上了糖!

          注意看這兩行:

          1. Coffee coffee = new BitterCoffee();        // 點了一杯苦咖啡

          2. coffee = new SugarDecorator(coffee);       // 給咖啡加了點糖

          裝飾器模式適合什么場景,我有一個對象,但是這個對象的功能不能令我滿意,我就拿裝飾器給他裝飾一下。

          代理模式

          周末了,又抱著iPad來到了咖啡館,準備享受一個寧靜的下午。

          “先生,請問您要喝點什么?”一旁禮貌的服務生上前問道。

          上次點的咖啡太苦了,這次直接要個加糖的吧。

          “我要一杯加糖咖啡?!?/p>

          1. public class CoffeeWithSugar implements Coffee {


          2.     private final Coffee coffee;


          3.     public CoffeeWithSugar() {

          4.         this.coffee = new BitterCoffee();

          5.     }


          6.     @Override

          7.     public void printMaterial() {

          8.         System.out.println("糖");

          9.         this.coffee.printMaterial();

          10.     }

          11. }

          這是加糖咖啡,其實內(nèi)部仍然是咖啡,只是加了些配方,就產(chǎn)生了一種新類,一種新的可以在菜單上呈現(xiàn)的飲品。

          點咖啡:

          1. public class Main {


          2.     public static void main(String[] args) {

          3.         Coffee coffee = new CoffeeWithSugar();

          4.         coffee.printMaterial();

          5.     }

          6. }

          正合我意,在咖啡的陪伴下度過了一個美好的下午。

          差別

          故事講完了,兩者實現(xiàn)的都是對原對象的包裝,持有原對象的實例,差別在于對外的表現(xiàn)。

          裝飾器模式:點了咖啡,發(fā)現(xiàn)太苦了,不是自己想要的,然后用裝飾器加了點糖。

          1. Coffee coffee = new BitterCoffee();

          2. coffee = new SugarDecorator(coffee);

          代理模式:直接就點的加糖咖啡。

          1. Coffee coffee = new CoffeeWithSugar();

          很細微的差別,希望大家不要弄混。

          批評

          去看代理模式相關的資料,五花八門,怎么理解的都有。

          我覺得菜鳥教程的代理模式解釋的最為正宗:在代理模式中,我們創(chuàng)建具有現(xiàn)有對象的對象,以便向外界提供功能接口。

          還有,網(wǎng)上許多設計模式的文章都是你抄我、我抄你,一個錯了,全都錯了。

          我覺得我需要糾正一下。誰說代理模式一定要用接口的???代理模式時設計模式,設計模式不分語言,假如一門語言中沒有接口,那它就不能代理模式了嗎?只是Java中的接口可以讓我們符合依賴倒置原則進行開發(fā),降低耦合。用抽象類可以嗎?可以。用類繼承可以嗎?也可以。

          思想明白了,用什么寫還不是像玩一樣?

          AOP

          設計模式是思想,所以我上面說的代理模式不是僅適用于接口便與Spring AOP息息相關。

          AOP:Aspect Oriented Programming,面向切面編程,是面向?qū)ο缶幊痰难a充。如果你不明白這句話,好好去學學面向?qū)ο缶椭罏槭裁戳恕?/p>

          我們會聲明切面,即切在某方法之前、之后或前后都執(zhí)行。而Spring AOP的實現(xiàn)就是代理模式。

          場景

          正好最近寫過短信驗證碼,就拿這個來當例子吧。

          1. public interface SMSService {


          2.     void sendMessage();

          3. }

          4. public class SMSServiceImpl implements SMSService {


          5.     @Override

          6.     public void sendMessage() {

          7.         System.out.println("【夢云智】您正在執(zhí)行重置密碼操作,您的驗證碼為:1234,5分鐘內(nèi)有效,請不要將驗證碼轉(zhuǎn)發(fā)給他人。");

          8.     }

          9. }

          主函數(shù):

          1. public class Main {


          2.     public static void main(String[] args) {

          3.         SMSService smsService = new SMSServiceImpl();

          4.         smsService.sendMessage();

          5.         smsService.sendMessage();

          6.     }

          7. }

          費用統(tǒng)計

          老板改需求了,發(fā)驗證碼要花錢,老板想看看一共在短信上花了多少錢。

          正常按Spring的思路,肯定是聲明一個切面,來切發(fā)短信的方法,然后在切面內(nèi)統(tǒng)計短信費用。

          只是現(xiàn)在沒有框架,也就是這道題:拋開Spring來說,如何自己實現(xiàn)Spring AOP?

          寫框架考慮的自然多些,我上文講的代理是靜態(tài)代理,編譯期間就決定好的。而框架實現(xiàn)卻是動態(tài)代理,需要在運行時生成代理對象,因為需要進行類掃描,看看哪些個類有切面需要生成代理對象。

          JDK動態(tài)代理

          編寫一個統(tǒng)計短信費用的類實現(xiàn)InvocationHandler接口。

          寫到這,終于明白為什么每次后臺Debug的時候都會跳轉(zhuǎn)到invoke方法。

          1. public class MoneyCountInvocationHandler implements InvocationHandler {


          2.     /**

          3.      * 被代理的目標

          4.      */

          5.     private final Object target;


          6.     /**

          7.      * 內(nèi)部存儲錢的總數(shù)

          8.      */

          9.     private Double moneyCount;


          10.     public MoneyCountInvocationHandler(Object target) {

          11.         this.target = target;

          12.         this.moneyCount = 0.0;

          13.     }


          14.     @Override

          15.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

          16.         Object result = method.invoke(target, args);

          17.         moneyCount += 0.07;

          18.         System.out.println("發(fā)送短信成功,共花了:" + moneyCount + "元");

          19.         return result;

          20.     }

          21. }

          將主函數(shù)里的smsService替換為使用MoneyCountInvocationHandler處理的代理對象。

          1. public class Main {


          2.     public static void main(String[] args) {

          3.         SMSService smsService = new SMSServiceImpl();

          4.         smsService = (SMSService) Proxy.newProxyInstance(Main.class.getClassLoader(),

          5.                                             new Class[]{SMSService.class},

          6.                                             new MoneyCountInvocationHandler(smsService));

          7.         smsService.sendMessage();

          8.         smsService.sendMessage();

          9.     }

          10. }

          根據(jù)InvocationHandler中的invoke方法動態(tài)生成一個類,該類實現(xiàn)SMSService接口,代理對象,就是用這個類實例化的。

          AOP實現(xiàn)

          上面的都實現(xiàn)了?寫一個AOP是不是也不是難事?

          主函數(shù)的代碼,應該放在IOC容器初始化中,掃描包,去看看哪些個類需要生成代理對象,然后構(gòu)造代理對象到容器中。

          然后在invoke方法里,把統(tǒng)計費用的邏輯改成切面的邏輯不就好了嗎?

          不足分析

          結(jié)束了嗎?當然沒有,上面的方法實現(xiàn)僅對接口有效。

          因為JDK的動態(tài)代理,是生成一個實現(xiàn)相應接口的代理類。但是Spring又不是只能通過接口注入。

          1. @Autowired

          2. private Type xxxxx;

          Spring的@Autowired是通過聲明的類型去容器里找符合的對象然后注進來的,接口是類型,類不也是類型嗎?

          1. @Autowired

          2. private SMSService smsService;

          這樣能注進來。

          1. @Autowired

          2. private SMSServiceImpl smsService;

          這樣呢?也能注進來。

          所以,JDK動態(tài)代理針對直接注入類類型的,就代理不了。

          cglib動態(tài)代理

          自古以來,從來都是時勢造英雄,而不是英雄創(chuàng)造了時代。

          出現(xiàn)了問題,自然會有英雄出來解決。拯救世界的就是cglib。

          JDK動態(tài)代理解決不了的,統(tǒng)統(tǒng)交給cglib。

          就這個來說:

          1. @Autowired

          2. private SMSServiceImpl smsService;


          不是使用接口注入的,JDK動態(tài)代理解決不了。cglib怎么解決的呢?它會根據(jù)當前的類,動態(tài)生成一個子類,在子類中織入切面邏輯。

          然后使用子類對象代理父類對象。這就是為什么我上面說:代理模式,不要拘泥于接口。

          所以織入成功的,都是子類能把父類覆蓋的方法。

          所以cglib也不是萬能的,方法是final的,子類重寫不了,它當然也無計可施了。

          總結(jié)

          讀書讀的是什么?是真正理解作者的思想,明白作者想歌頌什么、批判什么。

          框架學的是什么?不只是為了提高開發(fā)效率,而是在使用的時候,就像與設計者交流一樣,能真正明白框架設計者的思想,才算用明白一款框架。

          如果我們都能做到這般,又何愁設計不出一款真正屬于自己的框架呢?


          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧洲亚洲日本在线观看 | 波多野结衣国产42区 | 草逼网页91 | 在线日韩小视频 | 久久久久久久久久久久久久久女乱 |