1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        面試官:拋開Spring來說,如何自己實(shí)現(xiàn)Spring AOP?

        共 6043字,需瀏覽 13分鐘

         ·

        2021-12-15 21:46




        1、藍(lán)綠發(fā)布、滾動(dòng)發(fā)布、灰度發(fā)布,有什么區(qū)別?這下明白了

        2、為什么我勸你放棄了Restful API?

        3、公司規(guī)定所有接口都用 POST請(qǐng)求,這是為什么?

        4、為什么阿里強(qiáng)制 boolean 類型變量不能使用 is 開頭?

        5、MyBatis批量插入幾千條數(shù)據(jù),請(qǐng)慎用foreach


        | 引言

        翻開to-do,注解認(rèn)證中答應(yīng)大家要講解代理模式。

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

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

        | 代理與裝飾器

        場景描述

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

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

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

        基礎(chǔ)實(shí)現(xiàn)

        給你一個(gè)咖啡接口:

        public?interface?Coffee?{

        ????/**
        ?????*?打印當(dāng)前咖啡的原材料,即咖啡里有什么
        ?????*/

        ????void?printMaterial();
        }

        一個(gè)默認(rèn)的苦咖啡的實(shí)現(xiàn):

        public?class?BitterCoffee?implements?Coffee?{

        ????@Override
        ????public?void?printMaterial()?{
        ????????System.out.println("咖啡");
        ????}
        }

        默認(rèn)的點(diǎn)餐邏輯:

        public?class?Main?{

        ????public?static?void?main(String[]?args)?{
        ????????Coffee?coffee?=?new?BitterCoffee();
        ????????coffee.printMaterial();
        ????}
        }

        點(diǎn)一杯咖啡。

        裝飾器模式

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

        想加點(diǎn)糖,對(duì)服務(wù)生說:“您好,請(qǐng)為我的咖啡加些糖”。

        /**
        ?*?糖裝飾器,用來給咖啡加糖
        ?*/

        public?class?SugarDecorator?implements?Coffee?{

        ????/**
        ?????*?持有的咖啡對(duì)象
        ?????*/

        ????private?final?Coffee?coffee;

        ????public?SugarDecorator(Coffee?coffee)?{
        ????????this.coffee?=?coffee;
        ????}

        ????@Override
        ????public?void?printMaterial()?{
        ????????System.out.println("糖");
        ????????this.coffee.printMaterial();
        ????}
        }

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

        public?class?Main?{

        ????public?static?void?main(String[]?args)?{
        ????????Coffee?coffee?=?new?BitterCoffee();
        ????????coffee?=?new?SugarDecorator(coffee);
        ????????coffee.printMaterial();
        ????}
        }

        看一看咖啡的成分,對(duì)的,確實(shí)加上了糖!

        注意看這兩行:

        Coffee?coffee?=?new?BitterCoffee();????????//?點(diǎn)了一杯苦咖啡
        coffee?=?new?SugarDecorator(coffee);???????//?給咖啡加了點(diǎn)糖

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

        代理模式

        周末了,又抱著iPad來到了咖啡館,準(zhǔn)備享受一個(gè)寧靜的下午。

        “先生,請(qǐng)問您要喝點(diǎn)什么?”一旁禮貌的服務(wù)生上前問道。

        上次點(diǎn)的咖啡太苦了,這次直接要個(gè)加糖的吧。

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

        public?class?CoffeeWithSugar?implements?Coffee?{

        ????private?final?Coffee?coffee;

        ????public?CoffeeWithSugar()?{
        ????????this.coffee?=?new?BitterCoffee();
        ????}

        ????@Override
        ????public?void?printMaterial()?{
        ????????System.out.println("糖");
        ????????this.coffee.printMaterial();
        ????}
        }

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

        點(diǎn)咖啡:

        public?class?Main?{

        ????public?static?void?main(String[]?args)?{
        ????????Coffee?coffee?=?new?CoffeeWithSugar();
        ????????coffee.printMaterial();
        ????}
        }

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

        差別

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

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

        Coffee?coffee?=?new?BitterCoffee();
        coffee?=?new?SugarDecorator(coffee);

        代理模式:直接就點(diǎn)的加糖咖啡。

        Coffee?coffee?=?new?CoffeeWithSugar();

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

        批評(píng)

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

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

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

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

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

        | AOP

        設(shè)計(jì)模式是思想,所以我上面說的代理模式不是僅適用于接口便與Spring AOP息息相關(guān)。

        AOPAspect Oriented Programming,面向切面編程,是面向?qū)ο缶幊痰难a(bǔ)充。如果你不明白這句話,好好去學(xué)學(xué)面向?qū)ο缶椭罏槭裁戳恕?/span>

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

        場景

        正好最近寫過短信驗(yàn)證碼,就拿這個(gè)來當(dāng)例子吧。

        public?interface?SMSService?{

        ????void?sendMessage();
        }
        public?class?SMSServiceImpl?implements?SMSService?{

        ????@Override
        ????public?void?sendMessage()?{
        ????????System.out.println("【夢(mèng)云智】您正在執(zhí)行重置密碼操作,您的驗(yàn)證碼為:1234,5分鐘內(nèi)有效,請(qǐng)不要將驗(yàn)證碼轉(zhuǎn)發(fā)給他人。");
        ????}
        }

        主函數(shù):

        public?class?Main?{

        ????public?static?void?main(String[]?args)?{
        ????????SMSService?smsService?=?new?SMSServiceImpl();
        ????????smsService.sendMessage();
        ????????smsService.sendMessage();
        ????}
        }

        費(fèi)用統(tǒng)計(jì)

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

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

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

        寫框架考慮的自然多些,我上文講的代理是靜態(tài)代理,編譯期間就決定好的。而框架實(shí)現(xiàn)卻是動(dòng)態(tài)代理,需要在運(yùn)行時(shí)生成代理對(duì)象,因?yàn)樾枰M(jìn)行類掃描,看看哪些個(gè)類有切面需要生成代理對(duì)象。

        JDK動(dòng)態(tài)代理

        編寫一個(gè)統(tǒng)計(jì)短信費(fèi)用的類實(shí)現(xiàn)InvocationHandler接口。

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

        public?class?MoneyCountInvocationHandler?implements?InvocationHandler?{

        ????/**
        ?????*?被代理的目標(biāo)
        ?????*/

        ????private?final?Object?target;

        ????/**
        ?????*?內(nèi)部存儲(chǔ)錢的總數(shù)
        ?????*/

        ????private?Double?moneyCount;

        ????public?MoneyCountInvocationHandler(Object?target)?{
        ????????this.target?=?target;
        ????????this.moneyCount?=?0.0;
        ????}

        ????@Override
        ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
        ????????Object?result?=?method.invoke(target,?args);
        ????????moneyCount?+=?0.07;
        ????????System.out.println("發(fā)送短信成功,共花了:"?+?moneyCount?+?"元");
        ????????return?result;
        ????}
        }

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

        public?class?Main?{

        ????public?static?void?main(String[]?args)?{
        ????????SMSService?smsService?=?new?SMSServiceImpl();
        ????????smsService?=?(SMSService)?Proxy.newProxyInstance(Main.class.getClassLoader(),
        ????????????????????????????????????????????new?Class[]
        {SMSService.class},
        ????????????????????????????????????????????new?MoneyCountInvocationHandler(smsService))
        ;
        ????????smsService.sendMessage();
        ????????smsService.sendMessage();
        ????}
        }



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

        AOP實(shí)現(xiàn)

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

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

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

        不足分析

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

        因?yàn)?/span>JDK的動(dòng)態(tài)代理,是生成一個(gè)實(shí)現(xiàn)相應(yīng)接口的代理類。但是Spring又不是只能通過接口注入。

        @Autowired
        private?Type?xxxxx;

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

        @Autowired
        private?SMSService?smsService;

        這樣能注進(jìn)來。

        @Autowired
        private?SMSServiceImpl?smsService;

        這樣呢?也能注進(jìn)來。

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

        cglib動(dòng)態(tài)代理

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

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

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

        就這個(gè)來說:

        @Autowired
        private?SMSServiceImpl?smsService;

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

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

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

        所以cglib也不是萬能的,方法是final的,子類重寫不了,它當(dāng)然也無計(jì)可施了。

        | 總結(jié)

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

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

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


        轉(zhuǎn)自:張喜碩

        鏈接:https://segmentfault.com/a/1190000019148468


        最近熱文閱讀:

        1、為什么我勸你放棄了Restful API?
        2、Java8 Stream:2萬字20個(gè)實(shí)例,玩轉(zhuǎn)集合的篩選、歸約、分組、聚合
        3、公司規(guī)定所有接口都用 POST請(qǐng)求,這是為什么?
        4、為什么阿里強(qiáng)制 boolean 類型變量不能使用 is 開頭?
        5、面試官:InnoDB中一棵B+樹可以存放多少行數(shù)據(jù)?
        6、MyBatis批量插入幾千條數(shù)據(jù),請(qǐng)慎用foreach
        7、有了 for (;;) ,為什么還需要while (true) ?到底哪個(gè)更快?
        8、名企公開掛“加班真好”標(biāo)語,員工稱一年被免費(fèi)“白嫖”600多小時(shí)!網(wǎng)友看不下去了,稽查部門展開調(diào)查...
        9、面試官:為什么 Java 不把基本類型放在堆中?我竟然答不上來。。
        10、IDEA 注釋模板這樣搞!
        關(guān)注公眾號(hào),你想要的Java都在這里


        瀏覽 45
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            艹屄视频网站 | 69av在线播放 | 日夜夜操 | 粉嫩高潮美女一区二区三区 | 肉体秘书hd日本在线观看 | 日本熟女性爱视频播放 | 99黄色| 啊灬啊灬啊灬快灬高潮了女视频 | 友田真希一区二区 | 操12p |