<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 AOP的坑!很多人都犯過!

          共 4389字,需瀏覽 9分鐘

           ·

          2020-11-18 02:23

          △Hollis, 一個對Coding有著獨特追求的人△
          這是Hollis的第?317?篇原創(chuàng)分享
          作者 l Hollis
          來源 l Hollis(ID:hollischuang)
          前幾天,我剛剛發(fā)布過一篇文章《自定義注解!絕對是程序員裝逼的利器!!》,介紹過如何使用Spring AOP + 自定義注解來提升代碼的優(yōu)雅性。
          很多讀者看完之后表示用起來很爽,但是后臺也有人留言說自己配置了Spring的AOP之后,發(fā)現(xiàn)切面不生效。
          其實,這個問題我在用的過程中也遇到過,而且還是同一個問題一天之內(nèi)遇到了兩次。
          說明這個問題很容易被忽略,并且這個問題帶來的后果可能是極其嚴(yán)重的。那么,我們就來簡單回顧一下問題是怎么樣的。



          問題重現(xiàn)
          最初我定義了一個注解,希望可以方便統(tǒng)一的對一些數(shù)據(jù)庫操作做緩存。于是就有了以下代碼:
          首先,定義一個注解:

          @Target(ElementType.METHOD)

          @Retention(RetentionPolicy.RUNTIME)

          public?@interface?Cacheable?{

          ????/**

          ?????*?策略名稱,需要保證唯一

          ?????*?@return

          ?????*/


          ????public?String?keyName();

          ????/**

          ?????*?超時時長,單位:秒

          ?????*?@return

          ?????*/


          ????public?int?expireTime();

          }

          然后自定義一個切面,對所有使用了該注解的方法進行切面處理:

          @Aspect

          @Component

          public?class?StrategyCacheAspect?{

          ????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(FacadeAspect.class);

          ????@Around("@annotation(com.hollis.cache.StrategyCache)")

          ????public?Object?cache(ProceedingJoinPoint?pjp)?throws?Throwable?{

          ????????//?先查緩存,如果緩存中有值,直接返回。如果緩存中沒有,先執(zhí)行方法,再將返回值存儲到緩存中。

          ????}

          }

          然后就可以使用該注解了,使用方法如下:

          @Component

          public?class?StrategyService?extends?BaseStrategyService??{

          ????public?PricingResponse?getFactor(Map<String,?String>?pricingParams)?{

          ????????//?做一些參數(shù)校驗,以及異常捕獲相關(guān)的事情

          ????????return?this.loadFactor(tieredPricingParams);

          ????}

          ????@Override

          ????@StrategyCache(keyName?=?"key0001",?expireTime?=?60?*?60?*?2)

          ????private?PricingResponse?loadFactor(Map<String,?String>?pricingParams)?{

          ????????//代碼執(zhí)行

          ????}

          }

          以上,對loadFactor方法增加了切面,為了方便使用,我們還定義了一個getFactor方法,設(shè)置為public,方便外部調(diào)用。
          但是,在調(diào)試過程中,我發(fā)現(xiàn)我們設(shè)置在loadFactor方法上面的切面并沒有成功,無法執(zhí)行切面類。
          于是開始排查問題具體是什么。



          問題排查
          為了排查這個問題,首先是把所有的代碼檢查一遍,看看切面的代碼是不是有問題,有沒有可能有手誤打錯了字之類的。
          但是發(fā)現(xiàn)都沒有。于是就想辦法找找問題。
          接下來我把loadFactor的訪問權(quán)限從private改成public,發(fā)現(xiàn)沒有效果。
          然后我嘗試著在方法外直接調(diào)用loadFactor而不是getFactor。
          發(fā)現(xiàn)這樣做就可以成功的執(zhí)行到切面里面了。
          發(fā)現(xiàn)這一現(xiàn)象的時候,我突然恍然大悟,直捶大腿。原來如此,原來如此,就應(yīng)該是這樣的。
          我突然就想到了問題的原因。其實原因挺簡單的,也是我之前了解到過的原理,但是在問題剛剛發(fā)生的時候我并沒有想到這里,而是通過debug,發(fā)現(xiàn)這個現(xiàn)象之后我才突然想到這個原理。
          那么,就來說說為什么會發(fā)生這樣的問題。



          代理的調(diào)用方式
          我們發(fā)現(xiàn)上面的問題關(guān)鍵在于loadFactor方法被調(diào)用的方式不同。我們知道,方法的調(diào)用通常有以下幾種方式:
          1、在類內(nèi)部,通過this進行自調(diào)用:

          public?class?SimplePojo?implements?Pojo?{

          ????public?void?foo()?{

          ????????//?this?next?method?invocation?is?a?direct?call?on?the?'this'?reference

          ????????this.bar();

          ????}

          ????public?void?bar()?{

          ????????//?some?logic...

          ????}

          }

          2、在類外部,通過該類的對象進行調(diào)用

          public?class?Main?{

          ????public?static?void?main(String[]?args)?{

          ????????Pojo?pojo?=?new?SimplePojo();

          ????????//?this?is?a?direct?method?call?on?the?'pojo'?reference

          ????????pojo.foo();

          ????}

          }

          類關(guān)系及調(diào)用過程中如下圖:
          ?
          如果是靜態(tài)方法,也可以通過類直接調(diào)用。
          3、在類外部,通過該類的代理對象進行調(diào)用:

          public?class?Main?{

          ????public?static?void?main(String[]?args)?{

          ????????ProxyFactory?factory?=?new?ProxyFactory(new?SimplePojo());

          ????????factory.addInterface(Pojo.class);

          ????????factory.addAdvice(new?RetryAdvice());

          ????????Pojo?pojo?=?(Pojo)?factory.getProxy();

          ????????//?this?is?a?method?call?on?the?proxy!

          ????????pojo.foo();

          ????}

          }

          類關(guān)系及調(diào)用過程中如下圖:
          ?
          那么,Spring的AOP其實是第三種調(diào)用方式,就是通過代理對象調(diào)用,只有這種調(diào)用方式,才能夠在真正的對象的執(zhí)行前后,能夠讓代理對象也執(zhí)行相關(guān)代碼,才能起到切面的作用。
          而對于使用this的方式調(diào)用,這種只是自調(diào)用,并不會使用代理對象進行調(diào)用,也就無法執(zhí)行切面類。





          問題解決
          那么,我們知道了,想要真正的執(zhí)行代理,那么就需要通過代理對象進行調(diào)用而不是使用this調(diào)用的方式。
          那么,這個問題的解決辦法也就是想辦法通過代理對象來調(diào)用目標(biāo)方法即可。
          這種問題的解決網(wǎng)上有很多種辦法,這里介紹一個相對簡單的。其他的更多的辦法大家可以在網(wǎng)上找到一些案例。搜索關(guān)鍵詞"AOP 自調(diào)用"即可。

          獲取代理對象進行調(diào)用

          我們需要修改一下前面的StrategyService的代碼,修改成以下內(nèi)容:

          @Component

          public?class?StrategyService{

          ????public?PricingResponse?getFactor(Map?pricingParams)?{

          ????????//?做一些參數(shù)校驗,以及異常捕獲相關(guān)的事情

          ????????//?這里不使用this.loadFactor而是使用AopContext.currentProxy()調(diào)用,目的是解決AOP代理不支持方法自調(diào)用的問題

          ????????if?(AopContext.currentProxy()?instanceof?StrategyService)?{

          ????????????return?((StrategyService)AopContext.currentProxy()).loadFactor(tieredPricingParams);

          ????????}?else?{

          ????????????//?部分實現(xiàn)沒有被代理過,則直接進行自調(diào)用即可

          ????????????return?loadFactor(tieredPricingParams);

          ????????}

          ????}

          ????@Override

          ????@StrategyCache(keyName?=?"key0001",?expireTime?=?60?*?60?*?2)

          ????private?PricingResponse?loadFactor(Map?oricingParams)?{

          ????????//代碼執(zhí)行

          ????}

          }

          即使用AopContext.currentProxy()獲取到代理對象,然后通過代理對象調(diào)用對應(yīng)的方法。
          還有個地方需要注意,以上方式還需要將Aspect的expose-proxy設(shè)置成true。如果是配置文件修改:

          ?class="true"?expose-proxy="true"/>

          如果是SpringBoot,則修改應(yīng)用啟動入口類的注解:

          @EnableAspectJAutoProxy(exposeProxy?=?true)

          public?class?Application?{

          }





          總結(jié)
          以上,我們分析并解決了一個Spring AOP不支持方法自調(diào)用的問題。
          AOP失敗這個問題,其實還是很嚴(yán)重的,因為如果發(fā)生非預(yù)期的失效,那么直接問題就是沒有執(zhí)行切面方法,更嚴(yán)重的后果可能是諸如事務(wù)未生效、日志未打印、緩存未查詢等各種問題。
          所以,還是建議大家看完此文之后,統(tǒng)查一下自己的代碼,是否存在方法自調(diào)用的情況。這種情況下,任何切面都是無法生效的!

          往期推薦

          自定義注解!絕對是程序員裝逼的利器!!


          Oracle慌了!華為終于對JDK下手了!


          美國大選的背后,黑客"粉絲"竟然有這些飯圈騷操作!


          本文由“壹伴編輯器”提供技術(shù)支
          ?

          直面Java第329期:哪個命令可以監(jiān)控虛擬機各種運行狀態(tài)信息?

          深入并發(fā)第013期:拓展synchronized——鎖優(yōu)化


          如果你喜歡本文,
          請長按二維碼,關(guān)注?Hollis.
          轉(zhuǎn)發(fā)至朋友圈,是對我最大的支持。

          點個?在看?
          喜歡是一種感覺
          在看是一種支持
          ↘↘↘
          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  久久精品国产99久久不卡 | 好想操骚逼无码视频 | 亚洲狼在线 | 欧美亚洲91 | 天天干天天射天天日 |