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

          構(gòu)造器注入與循環(huán)依賴

          共 6652字,需瀏覽 14分鐘

           ·

          2021-06-23 11:41

          接上一篇《邪惡的字段注入》,再談?wù)剺?gòu)造器注入。

          上一篇《邪惡的字段注入》主要是針對(duì)隨處可見的字段注入現(xiàn)狀提出了批評(píng),并強(qiáng)烈推薦使用構(gòu)造器注入。在歷數(shù)各種字段注入的缺點(diǎn),也就是相對(duì)來(lái)說使用構(gòu)造器注入的優(yōu)點(diǎn)后,只發(fā)現(xiàn)了 java 世界里構(gòu)造器注入的一個(gè)缺點(diǎn),那就是得多寫點(diǎn)代碼(當(dāng)然也以 js 工程師的角度,對(duì) java 工程師進(jìn)行了勸退,因?yàn)?js 的構(gòu)造器注入連這個(gè)僅有的缺點(diǎn)都沒有)。

          但是實(shí)際上,即便在 java 的世界里需要多寫點(diǎn)代碼,也根本不是問題,因?yàn)橛?IDE 來(lái)幫忙。那還等什么,快來(lái)改造吧!

          怎么做

          將光標(biāo)定位到字段注入代碼處,按下 Alt + Enter:


          在彈出的 popup 里選擇第一個(gè),回車,就自動(dòng)完成了字段注入到構(gòu)造器注入的改造:

          可以看到,使用構(gòu)造器注入,連 @Autowired 關(guān)鍵字都可以省略。

          構(gòu)造器注入還有什么缺點(diǎn)?

          上面用 IDE 的強(qiáng)大功能抵消了《邪惡的字段注入》中提到的構(gòu)造器注入的唯一缺點(diǎn),不過仍然有人不喜歡構(gòu)造器注入,說原因是構(gòu)造器注入在極端情況下有循環(huán)依賴異常問題。但是對(duì)于循環(huán)依賴,就可以使用字段注入,并且是 Spring 官方文檔推薦的方式。似乎 ——

          構(gòu)造器注入不如字段注入強(qiáng)大。

          這其實(shí)也是誤解了。首先,對(duì)于循環(huán)依賴,這是明顯的代碼壞味道,應(yīng)該首先反思設(shè)計(jì),是不是有明顯的 BUG?這里涉及到一個(gè)價(jià)值觀問題,對(duì)于缺陷或者異常,是盡早暴露出來(lái) fail fast,還是盡量把問題隱藏得越久越好?我站在 fail fast 這一邊,所以我認(rèn)為,構(gòu)造器注入即使不如字段注入強(qiáng)大也不要緊,因?yàn)檫@個(gè)時(shí)候首先應(yīng)該反思代碼設(shè)計(jì)。

          但是,構(gòu)造器注入并非不如字段注入強(qiáng)大,通過在構(gòu)造器注入里使用 @Lazy 注解,也可以解決循環(huán)依賴異常問題。

          總結(jié):無(wú)論什么場(chǎng)景,構(gòu)造器注入都優(yōu)于字段注入。


          以下詳細(xì)說說循環(huán)依賴的異常問題:

          什么是循環(huán)依賴?

          當(dāng) bean A 依賴 bean B,并且 bean B 也依賴 bean A 時(shí),就產(chǎn)生了循環(huán)依賴:

          Bean A -> Bean B -> Bean A

          當(dāng)然,有時(shí)候這種循環(huán)依賴比較含蓄(隱藏得比較深):

          Bean A -> Bean B -> Bean C -> Bean D -> Bean E -> Bean A

          Spring 中發(fā)生了什么?

          當(dāng) Spring 上下文加載所有 bean 時(shí),會(huì)依照一定的順序創(chuàng)建 bean 從而使得他們可以完全工作。對(duì)于沒有循環(huán)依賴的情況,比如這樣的依賴關(guān)系:

          Bean A -> Bean B -> Bean C

          Spring 會(huì)先創(chuàng)建 bean C,然后是 bean B(同時(shí)將 bean C 注入 B),最后創(chuàng)建 bean A(同時(shí)把 bean B 注入 A)。

          但是當(dāng)有循環(huán)依賴時(shí),Spring 無(wú)法決定那個(gè) bean 應(yīng)該最先被創(chuàng)建出來(lái),因?yàn)樗鼈兿嗷ヒ蕾?。在這樣的情況下,Spring 只好在加載上下文時(shí)拋出 BeanCurrentlyInCreationException 異常。

          當(dāng)然,這種異常只會(huì)在你使用構(gòu)造器注入時(shí)拋出;如果使用別的注入方式的話,由于依賴只會(huì)在它們實(shí)際被使用時(shí)而非在上下文加載時(shí)被注入,所以不會(huì)有異常拋出。

          舉個(gè)例子

          定義兩個(gè)互相依賴的 bean,并且使用構(gòu)造器注入:

          @Component
          public class CircularDependencyA {

          private CircularDependencyB circB;

          @Autowired
          public CircularDependencyA(CircularDependencyB circB) {
          this.circB = circB;
          }

          }

          @Component
          public class CircularDependencyB {

          private CircularDependencyA circA;

          @Autowired
          public CircularDependencyB(CircularDependencyA circA) {
          this.circA = circA;
          }
          }

          接著我們寫一個(gè)配置類用來(lái)測(cè)試,就命名為 TestConfig 吧,它指定了掃描組件的基準(zhǔn)包名。假設(shè)上面的 bean 定義在包“com.hardway.circular”里:

          @Configuration
          @ComponentScan(basePackages = { "com.hardway.circular" })
          public class TestConfig {
          }

          最后我們寫一個(gè) JUnit 測(cè)試來(lái)檢查循環(huán)依賴。測(cè)試可以是空的,因?yàn)槲覀冎灰|發(fā)上下文加載即可檢測(cè)到循環(huán)依賴。

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration(classes = { TestConfig.class })
          public class CircularDependencyTest {

          @Test
          public void givenCircularDependency_whenConstructorInjection_thenItFails() {
          // 測(cè)試可以是空的,因?yàn)槲覀冎灰|發(fā)上下文加載即可檢測(cè)到循環(huán)依賴。
          }
          }


          嘗試運(yùn)行測(cè)試,得到如下異常:

          BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
          Requested bean is currently in creation: Is there an unresolvable circular reference?


          繞過的方法


          前面說了,發(fā)生循環(huán)依賴,首先應(yīng)該重新思考代碼設(shè)計(jì)。不過這里還是完整地列出所有可以繞過異常的辦法。

          重新設(shè)計(jì)

          如果發(fā)生循環(huán)依賴,很有可能出現(xiàn)了設(shè)計(jì)問題:職責(zé)劃分不合理。這時(shí)候應(yīng)該重新設(shè)計(jì)組件讓它們的層次結(jié)構(gòu)變得合理和良好,從而不需要循環(huán)依賴。

          如果真的因?yàn)槟承┰虿荒苤匦略O(shè)計(jì)組件(比如遺留代碼、不允許被修改的代碼、沒時(shí)間沒資源做完整的重新設(shè)計(jì)等……),那么你可以嘗試:

          使用 @Lazy

          打破環(huán)的簡(jiǎn)單辦法是讓 Spring 對(duì) bean 進(jìn)行懶惰的初始化,即:不要完全初始化 bean,而是創(chuàng)建一個(gè)代理來(lái)將它注入到其他 bean 中。被注入的 bean 僅僅在第一次被使用時(shí)才會(huì)完全初始化。

          比如你可以把前面的組件 CircularDependencyA 改造成這樣:

          @Component
          public class CircularDependencyA {

          private CircularDependencyB circB;

          @Autowired
          public CircularDependencyA(@Lazy CircularDependencyB circB) {
          this.circB = circB;
          }
          }

          如果你再次運(yùn)行測(cè)試,就會(huì)看到錯(cuò)誤不見了。

          使用設(shè)置器/字段注入

          Spring 官方文檔提出的,也是最流行的(但是我不推薦)繞過方式是使用設(shè)置器注入。簡(jiǎn)單來(lái)說就是你將 bean 們的纏繞方式從構(gòu)造器注入改成設(shè)置器注入(或者字段注入),就能搞定問題。通過這個(gè)方式 Spring 只創(chuàng)建 bean,但是在依賴被真正用到之前都不會(huì)事先注入。

          我們可以把前面的例子中的類改成使用設(shè)置器注入,然后添加另一個(gè) message 字段到 CircularDependencyB 中以便于寫一個(gè)合適的單元測(cè)試:

          @Component
          public class CircularDependencyA {

          private CircularDependencyB circB;

          @Autowired
          public void setCircB(CircularDependencyB circB) {
          this.circB = circB;
          }

          public CircularDependencyB getCircB() {
          return circB;
          }
          }

          @Component
          public class CircularDependencyB {

          private CircularDependencyA circA;

          private String message = "Hi!";

          @Autowired
          public void setCircA(CircularDependencyA circA) {
          this.circA = circA;
          }

          public String getMessage() {
          return message;
          }
          }


          單元測(cè)試改成這樣:

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration(classes = { TestConfig.class })
          public class CircularDependencyTest {

          @Autowired
          ApplicationContext context;

          @Bean
          public CircularDependencyA getCircularDependencyA() {
          return new CircularDependencyA();
          }

          @Bean
          public CircularDependencyB getCircularDependencyB() {
          return new CircularDependencyB();
          }

          @Test
          public void givenCircularDependency_whenSetterInjection_thenItWorks() {
          CircularDependencyA circA = context.getBean(CircularDependencyA.class);

          Assert.assertEquals("Hi!", circA.getCircB().getMessage());
          }
          }


          上面用到一些注解,解釋如下:

          @Bean:告訴 Spring 框架必須使用這些方法來(lái)獲取需要被注入的 bean 的實(shí)現(xiàn)。

          @Test: 測(cè)試要從上下文中獲取 CircularDependencyA 這個(gè) bean,然后斷言它的 CircularDependencyB 已經(jīng)被穩(wěn)妥地注入了,并檢查它的 message 屬性的值。

          使用 @PostConstruct


          另一個(gè)打破環(huán)的方式是通過在眾多 bean 中的其中一個(gè)上面應(yīng)用 @Autowired 來(lái)注入依賴,然后在一個(gè)方法上面應(yīng)用 @PostConstruct 注解來(lái)設(shè)置其他的依賴。

          比如有這樣的 bean 相關(guān)的代碼:

          @Component
          public class CircularDependencyA {

          @Autowired
          private CircularDependencyB circB;

          @PostConstruct
          public void init() {
          circB.setCircA(this);
          }

          public CircularDependencyB getCircB() {
          return circB;
          }
          }
          @Component
          public class CircularDependencyB {

          private CircularDependencyA circA;

          private String message = "Hi!";

          public void setCircA(CircularDependencyA circA) {
          this.circA = circA;
          }

          public String getMessage() {
          return message;
          }
          }


          然后可以運(yùn)行之前寫好的同樣的測(cè)試,你可以看到依賴能夠被穩(wěn)妥地注入并且不會(huì)拋出循環(huán)依賴異常。

          實(shí)現(xiàn) ApplicationContextAware 和 InitializingBean

          如果有 bean 實(shí)現(xiàn)了 ApplicationContextAware,則這個(gè) bean 就能夠訪問 Spring 上下文并能夠從中抽取到其他的 bean。而通過實(shí)現(xiàn) InitializingBean 可以指示這個(gè) bean 在當(dāng)其所有的屬性都被設(shè)置好后必須執(zhí)行一些動(dòng)作;在這個(gè)場(chǎng)景下我們手動(dòng)設(shè)置依賴。

          bean 相關(guān)的代碼像這樣:

          @Component
          public class CircularDependencyA implements ApplicationContextAware, InitializingBean {

          private CircularDependencyB circB;

          private ApplicationContext context;

          public CircularDependencyB getCircB() {
          return circB;
          }

          @Override
          public void afterPropertiesSet() throws Exception {
          circB = context.getBean(CircularDependencyB.class);
          }

          @Override
          public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
          context = ctx;
          }
          }
          @Component
          public class CircularDependencyB {

          private CircularDependencyA circA;

          private String message = "Hi!";

          @Autowired
          public void setCircA(CircularDependencyA circA) {
          this.circA = circA;
          }

          public String getMessage() {
          return message;
          }
          }

          再一次運(yùn)行之前的測(cè)試可以驗(yàn)證仍然能夠通過并且沒有異常。

          總結(jié)

          本文接續(xù)《邪惡的字段注入》,強(qiáng)烈推薦使用構(gòu)造器注入,并且手把手地講解了如何將字段注入改造成構(gòu)造器注入,以及反駁了構(gòu)造器注入不如字段注入的觀點(diǎn)。同時(shí)對(duì)循環(huán)依賴這個(gè)極端場(chǎng)景進(jìn)行了舉例說明,列舉了所有可能的繞過方法,并展示了通過 @Lazy 注解,構(gòu)造器注入仍然優(yōu)于字段注入,但是最優(yōu)的方案是重新設(shè)計(jì)代碼,因?yàn)槌霈F(xiàn)循環(huán)依賴是一個(gè)設(shè)計(jì)缺陷的表征。

          盡管 Spring 官方文檔更喜歡用設(shè)置器注入和字段注入,但是我仍然更加推崇構(gòu)造器注入。因?yàn)樵O(shè)置器/字段注入以及其他的幾種繞過方法基本上都是阻攔了 Spring 對(duì) bean 的初始化和注入的管理,然后手動(dòng)來(lái)進(jìn)行,這違背了使用 Spring 的初衷。


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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色综合 | 精品国产一区二区三区麻豆传媒 | 成人三级片在线观看 |