最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關于Spring的@Autowired依賴注入常見錯誤的總結

 更新時間:2021年09月15日 10:19:07   作者:JavaEdge.  
有時我們會使用@Autowired自動注入,同時也存在注入到集合、數(shù)組等復雜類型的場景。這都是方便寫 bug 的場景,本篇文章帶你了解Spring @Autowired依賴注入的坑

做不到雨露均沾

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

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

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

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

當完成支持多個數(shù)據(jù)庫的準備工作時,程序就已經(jīng)無法啟動了,報錯如下:

解析

當一個Bean被構建時的核心步驟:

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

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

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

因為StudentController含標記為Autowired的成員屬性dataService,所以會使用到AutowiredAnnotationBeanPostProcessor完成“裝配”:找出合適的DataService bean,設置給StudentController#dataService。
裝配過程:

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

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);
   }
}

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

當根據(jù)DataService類型找依賴時,會找出2個依賴:

  • CassandraDataService
  • OracleDataService

在這樣的情況下,如果同時滿足以下兩個條件則會拋出本案例的錯誤:

  • 調用determineAutowireCandidate方法來選出優(yōu)先級最高的依賴,但是發(fā)現(xiàn)并沒有優(yōu)先級可依據(jù)。具體選擇過程可參考
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ù)@Primary,其次是@Priority,最后根據(jù)Bean名嚴格匹配。
如果這些幫助決策優(yōu)先級的注解都沒有被使用,名字也不精確匹配,則返回null,告知無法決策出哪種最合適。

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

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

案例程序能滿足這些條件,所以報錯并不奇怪。而如果我們把這些條件想得簡單點,或許更容易幫助我們去理解這個設計。就像我們遭遇多個無法比較優(yōu)劣的選擇,卻必須選擇其一時,與其偷偷地隨便選擇一種,還不如直接報錯,起碼可以避免更嚴重的問題發(fā)生。

修正

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

@Repository
@Primary
@Slf4j
public class OracleDataService implements DataService{
    //省略非關鍵代碼
}

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

@Autowired
DataService oracleDataService;

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

  • 需要Oracle時指定屬性名為oracleDataService
  • 需要Cassandra時則指定屬性名為cassandraDataService

顯式引用Bean時首字母忽略大小寫

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

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

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

DefaultListableBeanFactory#doResolveDependency

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

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

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

運行報錯:

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 名稱,默認就是類名,不過首字母小寫!

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

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

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

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

答疑

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

當因名稱問題(例如引用Bean首字母搞錯了)找不到Bean,會拋NoSuchBeanDefinitionException。

不顯式設置名字的Bean,其默認名稱首字母到底是大寫還是小寫呢?
Spring Boot應用會自動掃包,找出直接或間接標記了 @Component 的BeanDefinition。例如CassandraDataService、SQLServerDataService都被標記了@Repository,而Repository本身被@Component標記,所以都間接標記了@Component。

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

ClassPathBeanDefinitionScanner#doScan

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

因為DataService實現(xiàn)都是使用注解,所以Bean名稱的生成邏輯最終調用的其實是

AnnotationBeanNameGenerator#generateBeanName

看Bean有無顯式指明名稱,若:

用顯式名稱

  • 沒有

生成默認名稱

案例沒有給Bean指名,所以生成默認名稱,通過方法:

buildDefaultBeanName

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

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

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

修正

引用處修正

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

定義處顯式指定Bean名字,我們可以保持引用代碼不變,而通過顯式指明CassandraDataService 的Bean名稱為CassandraDataService來糾正這個問題。

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

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

引用內部類的Bean遺忘類名

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

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

這時一般都用下面的方式直接去顯式引用這個Bean:

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

那直接采用首字母小寫,這樣就萬無一失了嗎?
仍報錯“找不到Bean”,why?

答疑

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

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

ClassUtils#getShortName

假設是個內部類,例如下面的類名:

com.javaedge.StudentController.InnerClassDataService

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

StudentController.InnerClassDataService

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

studentController.InnerClassDataService

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

修正

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

總結

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

到此這篇關于關于Spring的@Autowired依賴注入常見錯誤的總結的文章就介紹到這了,更多相關Spring @Autowired 依賴注入內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • ShardingSphere-Proxy5搭建使用過程分析

    ShardingSphere-Proxy5搭建使用過程分析

    ShardingSphere-Proxy是跨語言的數(shù)據(jù)庫代理服務端,主要用來處理:分表、分庫、讀寫分離 等,這篇文章主要介紹了ShardingSphere-Proxy5搭建使用過程,需要的朋友可以參考下
    2022-10-10
  • MyBatis5中Spring集成MyBatis事物管理

    MyBatis5中Spring集成MyBatis事物管理

    這篇文章主要介紹了MyBatis5中MyBatis集成Spring事物管理的相關資料,需要的朋友可以參考下
    2016-05-05
  • springbooot整合dynamic?datasource數(shù)據(jù)庫密碼加密方式

    springbooot整合dynamic?datasource數(shù)據(jù)庫密碼加密方式

    這篇文章主要介紹了springbooot整合dynamic?datasource?數(shù)據(jù)庫密碼加密方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Intellij IDEA全局替換快捷鍵整理

    Intellij IDEA全局替換快捷鍵整理

    這篇文章主要給大家介紹了關于Intellij IDEA全局替換快捷鍵整理的相關資料,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-02-02
  • Java實現(xiàn)RSA加密工具類

    Java實現(xiàn)RSA加密工具類

    這篇文章主要介紹了Java如何實現(xiàn)RSA加密工具類,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-09-09
  • springcloud項目里application.yml不加載的坑及解決

    springcloud項目里application.yml不加載的坑及解決

    這篇文章主要介紹了springcloud項目里application.yml不加載的坑及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • macOS中搭建Java8開發(fā)環(huán)境(基于Intel?x86?64-bit)

    macOS中搭建Java8開發(fā)環(huán)境(基于Intel?x86?64-bit)

    這篇文章主要介紹了macOS中搭建Java8開發(fā)環(huán)境(基于Intel?x86?64-bit)?的相關資料,需要的朋友可以參考下
    2022-12-12
  • 使用Maven搭建SpringMVC項目的步驟(圖文教程)

    使用Maven搭建SpringMVC項目的步驟(圖文教程)

    本篇文章主要介紹了使用Maven搭建SpringMVC項目的步驟(圖文教程),非常具有實用價值,需要的朋友可以參考下
    2017-09-09
  • 微服務如何通過feign.RequestInterceptor傳遞參數(shù)

    微服務如何通過feign.RequestInterceptor傳遞參數(shù)

    這篇文章主要介紹了微服務如何通過feign.RequestInterceptor傳遞參數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Mybatis圖文并茂講解分頁插件

    Mybatis圖文并茂講解分頁插件

    使用過mybatis的人都知道,mybatis本身就很小且簡單,sql寫在xml里,統(tǒng)一管理和優(yōu)化。缺點當然也有,比如我們使用過程中,要使用到分頁,如果用最原始的方式的話,1.查詢分頁數(shù)據(jù),2.獲取分頁長度,也就是說要使用到兩個方法才能完成分頁
    2022-07-07

最新評論

德令哈市| 丹阳市| 泸水县| 深水埗区| 肇庆市| 正定县| 西丰县| 沁水县| 元朗区| 柘城县| 五指山市| 重庆市| 康乐县| 西平县| 克东县| 长岭县| 定结县| 香港 | 沧州市| 固始县| 水富县| 深州市| 宣城市| 仲巴县| 同心县| 定西市| 张家港市| 富平县| 拉萨市| 永仁县| 大连市| 林西县| 昌平区| 灯塔市| 衡水市| 兴海县| 稻城县| 应城市| 苏尼特右旗| 平定县| 巴塘县|