<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>

          記一次循環(huán)依賴踩坑

          共 4371字,需瀏覽 9分鐘

           ·

          2020-08-29 10:58


          下面我講述下這次踩坑的過程,主要涉及的知識點(diǎn)有三個:模板方法Bean加載順序循環(huán)依賴

          這次踩坑的起因要從模板方法說起,最近寫的一個需求,在Manager中需要對A、B、C三類數(shù)據(jù)進(jìn)行處理,處理過程類似且較多,而只是數(shù)據(jù)類型和細(xì)節(jié)上有些差異。為了復(fù)用,自然想到了用模板方法重寫,這也是我第一次嘗試在Spring中使用模板方法,然后就踩坑了T T。

          下面我大概重現(xiàn)下場景,在Manager中有一個fun方法會根據(jù)傳入的type使用相應(yīng)的工具類處理數(shù)據(jù),工具類是通過屬性注入的UtilAUtilBUtilCManager中還有一個preHandle方法做一些數(shù)據(jù)預(yù)處理,后續(xù)會用到,但不是現(xiàn)在。

          @Component
          public?class?Manager?{

          ?@Autowired
          ?private?UtilA?utilA;

          ?@Autowired
          ?private?UtilB?utilB;

          ?@Autowired
          ?private?UtilC?utilC;

          ?public?void?fun(String?type,?String?data)?{
          ??switch?(type)?{
          ???case?"A"?:
          ????utilA.process(data);
          ????break;
          ???case?"B"?:
          ????utilB.process(data);
          ????break;
          ???case?"C":
          ????utilC.process(data);
          ????break;
          ???default:
          ????utilA.doProcess(data);
          ??}
          ?}

          ?public?String?preHandle(String?data)?{
          ??//?我是一個假預(yù)處理...我什么都沒做,嘿嘿
          ??return?data;
          ?}

          }

          UtilAUtilBUtilC都繼承了一個模板類Templateprocess方法是一個模板方法用于處理數(shù)據(jù),同時調(diào)用了doProcess抽象方法,其具體邏輯將由UtilAUtilBUtilC實現(xiàn)。

          public?abstract?class?Template?{

          ?public?void?process(String?data)?{
          ????????//?我是一個模板方法...我可以做很多工作,省得兒子們都寫一遍
          ????????//?而特殊的工作交給doProcess由兒子們來具體實現(xiàn)
          ??doProcess(data);
          ?}

          ?protected?abstract?void?doProcess(String?data);

          }

          UtilA為例,如下:

          @Component
          public?class?UtilA?extends?Template?{
          ?@Override
          ?protected?void?doProcess(String?data)?{
          ??System.out.println("我是A,處理數(shù)據(jù):"?+?data);
          ?}
          }

          模板方法我們都寫出來了,沒什么問題。但現(xiàn)在我還有這樣一個需求,我要在process方法中調(diào)用ManagerpreHandle方法(別問我為啥不直接復(fù)制過來,實際情況更復(fù)雜些,在preHandle中還用到了很多其他方法和依賴,所以最好是復(fù)用),因此需要在Template中獲得Manager的實例,可是Template是一個抽象類,都沒法實例化成Bean,更別提依賴注入了。這里我的解決辦法是,引入了一個SpringContextHolder,這是一個ApplicationContext的包裝類,通過它來獲得Manager實例,其定義如下:

          @Component
          public?class?SpringContextHolder?implements?ApplicationContextAware?{

          ?private?static?ApplicationContext?applicationContext;

          ?@Override
          ?public?void?setApplicationContext(ApplicationContext?context)?throws?BeansException?{
          ??applicationContext?=?context;
          ?}

          ?public?static??T?getBean(String?name)?{
          ??return?(T)?applicationContext.getBean(name);
          ?}
          ?
          }

          然后是改寫Template類,在構(gòu)造函數(shù)中獲得Manager實例,然后在process方法就可以順利調(diào)用preHandle方法了。

          public?abstract?class?Template?{

          ?private?Manager?manager;

          ?public?Template()?{
          ??manager?=?SpringContextHolder.getBean("manager");
          ?}

          ?public?void?process(String?data)?{
          ??manager.preHandle(data);
          ??doProcess(data);
          ?}

          ?protected?abstract?void?doProcess(String?data);

          }

          下面是主函數(shù),開始運(yùn)行了:

          public?class?Main?{
          ?public?static?void?main(String[]?args)?{
          ??ApplicationContext?context?=?new?ClassPathXmlApplicationContext("spring-context.xml");
          ??Manager?manager?=?(Manager)?context.getBean("manager");
          ??manager.fun("A",?"123");
          ?}
          }

          調(diào)用managerfun方法,由于我們傳入的參數(shù)是"A",所以將會使用utilA處理數(shù)據(jù)。一切看起來都很好,但這時候就遇到第一個問題了,啟動容器時,會加載UtilA,將調(diào)用構(gòu)造器進(jìn)行實例化,而在構(gòu)造器中我們指定通過SpringContextHoldergetBean方法來獲得manager,這時由于SpringContextHolder還未被加載,所以applicationContextnull,因此會報出空指針問題,所以我們需要保證在加載UtilA之前先加載SpringContextHolder,也就是控制Bean的加載順序。我們可以借助@DependsOn注解,加在UtilA上,并傳入?yún)?shù)“springContextHolder”,當(dāng)加載UtilA時就會先完成SpringContextHolder的加載。

          @Component
          @DependsOn("springContextHolder")
          public?class?UtilA?extends?Template?{
          ?@Override
          ?protected?void?doProcess(String?data)?{
          ??System.out.println("我是A,處理數(shù)據(jù):"?+?data);
          ?}
          }

          這下搞定了,能跑了。當(dāng)我把代碼上傳到測試環(huán)境,應(yīng)用無法啟動了。一看日志,是發(fā)生了循環(huán)依賴,Spring容器起不來。仔細(xì)一看,確實發(fā)生了循環(huán)依賴。Manager中通過屬性注入UtilA,而UtilA的父類Template在構(gòu)造函數(shù)中通過getBean獲得Manger。可是問題來了,為什么我在本地能運(yùn)行,而測試環(huán)境卻報錯了?說細(xì)點(diǎn)就是,為什么本地不會發(fā)生循環(huán)依賴,而測試環(huán)境會發(fā)生循環(huán)依賴。如果你之前看過《Spring源碼-循環(huán)依賴(附25張調(diào)試截圖)》或者對循環(huán)依賴有所了解,想必已經(jīng)知道如果X和Y都是屬性注入的循環(huán)依賴,Spring能通過三級緩存解決,不會報錯,而對于X和Y都是構(gòu)造器注入的循環(huán)依賴,Spring是無法解決的,會報錯。現(xiàn)在的情況是,我一處用了屬性注入,而另一處用了構(gòu)造器注入。所以猜想,在本地是先加載的Manager,先做的屬性注入,所以不報錯,而測試環(huán)境是先加載的UtilA,先做的構(gòu)造器注入,所以產(chǎn)生循環(huán)依賴錯誤。為什么兩個環(huán)境的加載順序不同呢?查了些資料,Spring自動掃描的加載順序和hashCode有關(guān),而hashCode和操作系統(tǒng)有關(guān),所以兩個環(huán)境的操作系統(tǒng)不同可能會導(dǎo)致加載順序不同。這也就是本地環(huán)境和測試環(huán)境運(yùn)行結(jié)果不同的原因了。

          下面說下怎么解決這個問題,大概的思路有兩種:

          1. 去除構(gòu)造器依賴;
          2. 控制加載順序。

          第一種方法,就是不要在構(gòu)造器中獲取依賴了,我們可以在process方法中獲取:

          public?abstract?class?Template?{

          ?private?Manager?manager;

          ?public?Template()?{
          ?}

          ?public?void?process(String?data)?{
          ??manager?=?SpringContextHolder.getBean("manager");
          ??manager.preHandle(data);
          ??doProcess(data);
          ?}

          ?protected?abstract?void?doProcess(String?data);

          }

          第二種方法,就是控制Manager始終在UtilA之前加載,利用@DependsOn注解:

          @Component
          @DependsOn({"springContextHolder",?"manager"})
          public?class?UtilA?extends?Template?{
          ?@Override
          ?protected?void?doProcess(String?data)?{
          ??System.out.println("我是A,處理數(shù)據(jù):"?+?data);
          ?}
          }

          我最后采用的是方法一,考慮的是只需要修改一處即可,第二種方法需要修改三個子類,改動處較多。大家如果遇到這種問題,還是根據(jù)自己的實際情況來解決。

          最后總結(jié)下,自己這次踩坑的原因有兩點(diǎn):

          1. 在學(xué)習(xí)循環(huán)依賴時,只考慮到了X和Y都用屬性注入或構(gòu)造器注入,沒思考過X使用屬性注入、Y使用構(gòu)造器注入是否會發(fā)生循環(huán)依賴問題。
          2. 對Bean的加載順序缺乏關(guān)注。為了保證程序的正確運(yùn)行,Bean的加載順序需要保證正確。

          瀏覽 49
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  热门视频 - 91爱爱 | 蘑菇视频在线观看隐藏线路 | 波多野结衣网在线 | 久久久久无码国产精品不卡 | 欧美户外操逼 |