国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

香港云服务器
服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - 關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

2022-01-03 17:50JavaEdge. Java教程

有時(shí)我們會(huì)使用@Autowired自動(dòng)注入,同時(shí)也存在注入到集合、數(shù)組等復(fù)雜類型的場(chǎng)景。這都是方便寫 bug 的場(chǎng)景,本篇文章帶你了解Spring @Autowired依賴注入的坑

做不到雨露均沾

經(jīng)常會(huì)遇到,required a single bean, but 2 were found。

根據(jù)ID移除學(xué)生
DataService是個(gè)接口,其實(shí)現(xiàn)依賴Oracle:

現(xiàn)在期望把部分非核心業(yè)務(wù)從Oracle遷移到Cassandra,自然會(huì)先添加上一個(gè)新的DataService實(shí)現(xiàn):

@Repository
@Slf4j
public class CassandraDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by cassandra");
    }
}

當(dāng)完成支持多個(gè)數(shù)據(jù)庫(kù)的準(zhǔn)備工作時(shí),程序就已經(jīng)無(wú)法啟動(dòng)了,報(bào)錯(cuò)如下:

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

解析

當(dāng)一個(gè)Bean被構(gòu)建時(shí)的核心步驟:

  • 執(zhí)行AbstractAutowireCapableBeanFactory#createBeanInstance:通過(guò)構(gòu)造器反射出該Bean,如構(gòu)建StudentController實(shí)例
  • 執(zhí)行AbstractAutowireCapableBeanFactory#populate:填充設(shè)置該Bean,如設(shè)置StudentController實(shí)例中被 @Autowired 標(biāo)記的dataService屬性成員。

“填充”過(guò)程的關(guān)鍵就是執(zhí)行各種BeanPostProcessor處理器,關(guān)鍵代碼如下:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
      //省略非關(guān)鍵代碼
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
          //省略非關(guān)鍵代碼
         }
      }
   }   
}

因?yàn)镾tudentController含標(biāo)記為Autowired的成員屬性dataService,所以會(huì)使用到AutowiredAnnotationBeanPostProcessor完成“裝配”:找出合適的DataService bean,設(shè)置給StudentController#dataService。
裝配過(guò)程:

1.尋找所有需依賴注入的字段和方法:AutowiredAnnotationBeanPostProcessor#postProcessProperties

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

2.根據(jù)依賴信息尋找依賴并完成注入。比如字段注入,參考AutowiredFieldElement#inject方法:

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Field field = (Field) this.member;
   Object value;
   // ...
      try {
          DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
         // 尋找“依賴”,desc為"dataService"的DependencyDescriptor
         value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
      }
      
   }
   // ...
   if (value != null) {
      ReflectionUtils.makeAccessible(field);
      // 裝配“依賴”
      field.set(bean, value);
   }
}

案例中的錯(cuò)誤就發(fā)生在上述“尋找依賴”的過(guò)程中,DefaultListableBeanFactory#doResolveDependency

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

當(dāng)根據(jù)DataService類型找依賴時(shí),會(huì)找出2個(gè)依賴:

  • CassandraDataService
  • OracleDataService

在這樣的情況下,如果同時(shí)滿足以下兩個(gè)條件則會(huì)拋出本案例的錯(cuò)誤:

  • 調(diào)用determineAutowireCandidate方法來(lái)選出優(yōu)先級(jí)最高的依賴,但是發(fā)現(xiàn)并沒有優(yōu)先級(jí)可依據(jù)。具體選擇過(guò)程可參考
DefaultListableBeanFactory#determineAutowireCandidate:
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
   Class<?> requiredType = descriptor.getDependencyType();
   String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
   if (primaryCandidate != null) {
      return primaryCandidate;
   }
   String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
   if (priorityCandidate != null) {
      return priorityCandidate;
   }
   // Fallback
   for (Map.Entry<String, Object> entry : candidates.entrySet()) {
      String candidateName = entry.getKey();
      Object beanInstance = entry.getValue();
      if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
            matchesBeanName(candidateName, descriptor.getDependencyName())) {
         return candidateName;
      }
   }
   return null;
}

優(yōu)先級(jí)的決策是先根據(jù)@Primary,其次是@Priority,最后根據(jù)Bean名嚴(yán)格匹配。
如果這些幫助決策優(yōu)先級(jí)的注解都沒有被使用,名字也不精確匹配,則返回null,告知無(wú)法決策出哪種最合適。

@Autowired要求是必須注入的(required默認(rèn)值true),或注解的屬性類型并不是可以接受多個(gè)Bean的類型,例如數(shù)組、Map、集合。
這點(diǎn)可以參考DefaultListableBeanFactory#indicatesMultipleBeans:

private boolean indicatesMultipleBeans(Class<?> type) {
   return (type.isArray() || (type.isInterface() &&
         (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
}

案例程序能滿足這些條件,所以報(bào)錯(cuò)并不奇怪。而如果我們把這些條件想得簡(jiǎn)單點(diǎn),或許更容易幫助我們?nèi)ダ斫膺@個(gè)設(shè)計(jì)。就像我們?cè)庥龆鄠€(gè)無(wú)法比較優(yōu)劣的選擇,卻必須選擇其一時(shí),與其偷偷地隨便選擇一種,還不如直接報(bào)錯(cuò),起碼可以避免更嚴(yán)重的問(wèn)題發(fā)生。

修正

打破上述兩個(gè)條件中的任何一個(gè)即可,即讓候選項(xiàng)具有優(yōu)先級(jí)或根本不選擇。
但并非每種條件的打破都滿足實(shí)際需求:
如可以通過(guò)使用**@Primary**讓被標(biāo)記的候選者有更高優(yōu)先級(jí),但并不一定符合業(yè)務(wù)需求,好比我們本身需要兩種DB都能使用,而非不可兼得。

@Repository
@Primary
@Slf4j
public class OracleDataService implements DataService{
    //省略非關(guān)鍵代碼
}

要同時(shí)支持多種DataService,不同情景精確匹配不同的DataService,可這樣修改:

@Autowired
DataService oracleDataService;

將屬性名和Bean名精確匹配,就能實(shí)現(xiàn)完美的注入選擇:

  • 需要Oracle時(shí)指定屬性名為oracleDataService
  • 需要Cassandra時(shí)則指定屬性名為cassandraDataService

顯式引用Bean時(shí)首字母忽略大小寫

還有另外一種解決辦法,即采用@Qualifier顯式指定引用服務(wù),例如采用下面的方式:

@Autowired()
@Qualifier("cassandraDataService")
DataService dataService;

這樣能讓尋找出的Bean只有一個(gè)(即精確匹配),無(wú)需后續(xù)的決策過(guò)程:

DefaultListableBeanFactory#doResolveDependency

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
      //省略其他非關(guān)鍵代碼
      //尋找bean過(guò)程
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
      if (matchingBeans.isEmpty()) {
         if (isRequired(descriptor)) {
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         return null;
      }
      //省略其他非關(guān)鍵代碼
      if (matchingBeans.size() > 1) {
         //省略多個(gè)bean的決策過(guò)程,即案例1重點(diǎn)介紹內(nèi)容
      } 
     //省略其他非關(guān)鍵代碼
}

使用 @Qualifier 指定名稱匹配,最終只找到唯一一個(gè)。但使用時(shí),可能會(huì)忽略Bean名稱首字母大小寫。
如:

@Autowired
@Qualifier("CassandraDataService")
DataService dataService;

運(yùn)行報(bào)錯(cuò):

Exception encountered during context initialization - cancelling refresh
attempt: org.springframework.beans.factory.UnsatisfiedDependencyException:
 Error creating bean with name "studentController": Unsatisfied dependency
  expressed through field "dataService"; nested exception is
   org.springframework.beans.factory.NoSuchBeanDefinitionException: No
    qualifying bean of type "com.spring.puzzle.class2.example2.DataService"
     available: expected at least 1 bean which qualifies as autowire
      candidate. Dependency annotations:
       {@org.springframework.beans.factory.annotation.Autowired(required=true),
        @org.springframework.beans.factory.annotation.Qualifier(value=CassandraDataService)}

若未顯式指定 bean 名稱,默認(rèn)就是類名,不過(guò)首字母小寫!

假設(shè)要支持SQLServer,定義了一個(gè)名為SQLServerDataService的實(shí)現(xiàn):

@Autowired
@Qualifier("sQLServerDataService")
DataService dataService;

依然出現(xiàn)之前錯(cuò)誤,而若改成SQLServerDataService,則運(yùn)行通過(guò)。
這真是瘋了呀!

顯式引用Bean時(shí),首字母到底是大寫還是小寫?

答疑

raiseNoMatchingBeanFound(type, descriptor.getResolvableType(),
	descriptor);

當(dāng)因名稱問(wèn)題(例如引用Bean首字母搞錯(cuò)了)找不到Bean,會(huì)拋NoSuchBeanDefinitionException。

不顯式設(shè)置名字的Bean,其默認(rèn)名稱首字母到底是大寫還是小寫呢?
Spring Boot應(yīng)用會(huì)自動(dòng)掃包,找出直接或間接標(biāo)記了 @Component 的BeanDefinition。例如CassandraDataService、SQLServerDataService都被標(biāo)記了@Repository,而Repository本身被@Component標(biāo)記,所以都間接標(biāo)記了@Component。

一旦找出這些Bean信息,就可生成Bean名,然后組合成一個(gè)個(gè)BeanDefinitionHolder返回給上層:

ClassPathBeanDefinitionScanner#doScan

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

BeanNameGenerator#generateBeanName產(chǎn)生Bean名,有兩種實(shí)現(xiàn)方式:

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

因?yàn)镈ataService實(shí)現(xiàn)都是使用注解,所以Bean名稱的生成邏輯最終調(diào)用的其實(shí)是

AnnotationBeanNameGenerator#generateBeanName

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

看Bean有無(wú)顯式指明名稱,若:

用顯式名稱

  • 沒有

生成默認(rèn)名稱

案例沒有給Bean指名,所以生成默認(rèn)名稱,通過(guò)方法:

buildDefaultBeanName

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

首先,獲取一個(gè)簡(jiǎn)短的ClassName,然后調(diào)用Introspector#decapitalize方法,設(shè)置首字母大寫或小寫,具體參考下面的代碼實(shí)現(xiàn):

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

  • 一個(gè)類名是以兩個(gè)大寫字母開頭,則首字母不變
  • 其它情況下默認(rèn)首字母變成小寫

SQLServerDataService的Bean,其名稱應(yīng)該就是類名本身,而CassandraDataService的Bean名稱則變成了首字母小寫(cassandraDataService)。

修正

引用處修正

@Autowired
@Qualifier("cassandraDataService")
DataService dataService;

定義處顯式指定Bean名字,我們可以保持引用代碼不變,而通過(guò)顯式指明CassandraDataService 的Bean名稱為CassandraDataService來(lái)糾正這個(gè)問(wèn)題。

@Repository("CassandraDataService")
@Slf4j
public class CassandraDataService implements DataService {
  //省略實(shí)現(xiàn)
}

如果你不太了解源碼,不想糾結(jié)于首字母到底是大寫還是小寫,建議第二種方法

引用內(nèi)部類的Bean遺忘類名

這就能搞定所有Bean顯式引用不出 bug 嗎?
沿用上面案例,稍微再添加點(diǎn)別的需求,例如我們需要定義一個(gè)內(nèi)部類來(lái)實(shí)現(xiàn)一種新的DataService,代碼如下:

public class StudentController {
    @Repository
    public static class InnerClassDataService implements DataService{
        @Override
        public void deleteStudent(int id) {
          //空實(shí)現(xiàn)
        }
    }
    // ...
 }

這時(shí)一般都用下面的方式直接去顯式引用這個(gè)Bean:

@Autowired
@Qualifier("innerClassDataService")
DataService innerClassDataService;

那直接采用首字母小寫,這樣就萬(wàn)無(wú)一失了嗎?
仍報(bào)錯(cuò)“找不到Bean”,why?

答疑

現(xiàn)在問(wèn)題是“如何引用內(nèi)部類的Bean”。
在AnnotationBeanNameGenerator#buildDefaultBeanName,只關(guān)注了首字母是否小寫,而在最后變換首字母前,有這么一行處理 class 名稱的:

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

我們可以看下它的實(shí)現(xiàn):

ClassUtils#getShortName

關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)

假設(shè)是個(gè)內(nèi)部類,例如下面的類名:

com.javaedge.StudentController.InnerClassDataService

經(jīng)過(guò)該方法處理后,得到名稱:

StudentController.InnerClassDataService

最后經(jīng)Introspector.decapitalize首字母變換,得到Bean名稱:

studentController.InnerClassDataService

所以直接使用 innerClassDataService 找不到想要的Bean。

修正

@Autowired
@Qualifier("studentController.InnerClassDataService")
DataService innerClassDataService;

總結(jié)

像第一個(gè)案例,同種類型的實(shí)現(xiàn),可能不是同時(shí)出現(xiàn)在自己的項(xiàng)目代碼中,而是有部分實(shí)現(xiàn)出現(xiàn)在依賴的類庫(kù)。看來(lái)研究源碼的確能讓我們少寫幾個(gè) bug!

到此這篇關(guān)于關(guān)于Spring的@Autowired依賴注入常見錯(cuò)誤的總結(jié)的文章就介紹到這了,更多相關(guān)Spring @Autowired 依賴注入內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://blog.csdn.net/qq_33589510/article/details/120148275

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程20個(gè)非常實(shí)用的Java程序代碼片段

    20個(gè)非常實(shí)用的Java程序代碼片段

    這篇文章主要為大家分享了20個(gè)非常實(shí)用的Java程序片段,對(duì)java開發(fā)項(xiàng)目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程升級(jí)IDEA后Lombok不能使用的解決方法

    升級(jí)IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級(jí),尋思已經(jīng)有好久沒有升過(guò)級(jí)了。升級(jí)完畢重啟之后,突然發(fā)現(xiàn)好多錯(cuò)誤,本文就來(lái)介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關(guān)于小米推送Java代碼,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧...

    富貴穩(wěn)中求8032021-07-12
  • Java教程Java實(shí)現(xiàn)搶紅包功能

    Java實(shí)現(xiàn)搶紅包功能

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)搶紅包功能,采用多線程模擬多人同時(shí)搶紅包,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程xml與Java對(duì)象的轉(zhuǎn)換詳解

    xml與Java對(duì)象的轉(zhuǎn)換詳解

    這篇文章主要介紹了xml與Java對(duì)象的轉(zhuǎn)換詳解的相關(guān)資料,需要的朋友可以參考下...

    Java教程網(wǎng)2942020-09-17
  • Java教程Java8中Stream使用的一個(gè)注意事項(xiàng)

    Java8中Stream使用的一個(gè)注意事項(xiàng)

    最近在工作中發(fā)現(xiàn)了對(duì)于集合操作轉(zhuǎn)換的神器,java8新特性 stream,但在使用中遇到了一個(gè)非常重要的注意點(diǎn),所以這篇文章主要給大家介紹了關(guān)于Java8中S...

    阿杜7482021-02-04
1195
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 人人爱人人草 | 日韩性视频 | 青草视频网站 | 一区二区免费看 | 久久91精品| 国产精品久久久91 | 色999国产 | 青青草国产精品 | 九色在线 | 国产精品久久久久aaaa九色 | 久久男人| 国产精品美女久久久久久久久久久 | 最新日韩免费 | 国产精品99久久免费观看 | 欧美啪啪一区二区 | 99亚洲精品 | 久久久久久成人 | 久一久久| 狠狠干干 | 国产毛片av| 欧美精品在线看 | 久久久中文| 欧美在线高清 | 欧美性网| 日韩福利| 亚洲高清电影 | 国产精品极品美女在线观看免费 | 国产性×xxx盗摄xxxx | 香蕉久久夜色精品国产使用方法 | 欧美日韩中文 | 激情国产 | 久久久国产精品入口麻豆 | 国产探花在线精品一区二区 | 亚洲一区二区av | 精品亚洲一区二区 | 俺来也俺也啪www色 性色视频在线 | 国产精品毛片久久久久久久 | 依人在线免费视频 | 久久久av | 国产欧美日韩一区 | 色婷婷综合久色 |