springboot多數(shù)據(jù)源使用@Qualifier自動注入無效的解決
@Qualifier自動注入無效的解決
問題
使用springboot進行多數(shù)據(jù)源時,發(fā)生了單例DataSource對應多個DataSourceBean的問題。
具體錯誤如下:XXXXX required a single bean, but 3 were found。通過@Qualifier來區(qū)分,或是在@Bean中添加name屬性來區(qū)分,都沒有作用。
問題的根本原因
主要在于SpringBoot的DataSourceInitializer,該類在autoConfigure包中,用來自動初始化一個內(nèi)置的DataSource實例,在創(chuàng)建該實例的時候發(fā)生了注入的問題。
創(chuàng)建實例的流程(記錄一下方便以后再次調(diào)試):
1. 調(diào)用DataSourceInitializer的構造方法
2. 調(diào)用AbstractAutowireCapableBeanFactory的applyMergedBeanDefinitionPostProcessors方法
3. 調(diào)用AbstractAutowireCapableBeanFactory的initializeBean方法
4. AbstractAutowireCapableBeanFactory的applyBeanPostProcessorsBeforeInitialization方法,其中有一個循環(huán)是用多種Bean處理器來處理DataSourceInitializer對象
5. 之后使用反射的方式跳轉(zhuǎn)到DataSourceInitializer的init方法
6.里面通過this.applicationContext.getBean(DataSource.class)來獲取所有DataSource的實現(xiàn)類對象實例。
7. DefaultListableBeanFactory的resolveNamedBean方法中來選取實例對象,通過里面的getBeanNamesForType方法獲取到所有的符合requireType(也就是DataSource.class)的對象。
8. 如果對象實例的實例數(shù)量大于1,則會進入以下的兩個判斷:
判斷是否有Primary的實例,或者是優(yōu)先級高的實例對象,如果有,則將候選對象名賦值給candidateName。沒有則置為空,最后拋出多個實例的異常。
其中問題出在
因為項目中只使用了@Qualifier,而且springboot的DataSourceInitializer沒有對@Qualifier的處理,所以沒有對實例進行匹配。
造成多個數(shù)據(jù)源實例。對于存在多數(shù)據(jù)源的情況,他們做的補救措施是在代碼中添加了是否是Primary和是否是HighestPriority的判斷,
來處理采用哪個數(shù)據(jù)源。所以解決的方式也因此出來了。就是將某個實例標記為Primary或者HighestPriority。
解決問題的方法
在某個@Bean上添加@Primary注解,就可以做到唯一區(qū)分。
這里其實應該還可以通過優(yōu)先級來區(qū)分,但使用@Order發(fā)現(xiàn)并不是這個優(yōu)先級,也沒找到相關的資料,所以之后再研究一下。
@Qualifier的作用和應用
@Qualifier的作用
這是官方的介紹
This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring. It may also be used to annotate other custom annotations that can then in turn be used as qualifiers.
簡單的理解就是:
- 在使用@Autowire自動注入的時候,加上@Qualifier(“test”)可以指定注入哪個對象;
- 可以作為篩選的限定符,我們在做自定義注解時可以在其定義上增加@Qualifier,用來篩選需要的對象。這個理解看下面的代碼吧,不好解釋。
功能介紹
首先是對(1)的理解。
//我們定義了兩個TestClass對象,分別是testClass1和testClass2
//我們?nèi)绻诹硗庖粋€對象中直接使用@Autowire去注入的話,spring肯定不知道使用哪個對象
//會排除異常 required a single bean, but 2 were found
@Configuration
public class TestConfiguration {
@Bean("testClass1")
TestClass testClass1(){
return new TestClass("TestClass1");
}
@Bean("testClass2")
TestClass testClass2(){
return new TestClass("TestClass2");
}
}
下面是正常的引用
@RestController
public class TestController {
//此時這兩個注解的連用就類似 @Resource(name="testClass1")
@Autowired
@Qualifier("testClass1")
private TestClass testClass;
@GetMapping("/test")
public Object test(){
return testClassList;
}
}
@Autowired和@Qualifier這兩個注解的連用在這個位置就類似 @Resource(name=“testClass1”)
對(2)的理解
@Configuration
public class TestConfiguration {
//我們調(diào)整下在testClass1上增加@Qualifier注解
@Qualifier
@Bean("testClass1")
TestClass testClass1(){
return new TestClass("TestClass1");
}
@Bean("testClass2")
TestClass testClass2(){
return new TestClass("TestClass2");
}
}
@RestController
public class TestController {
//我們這里使用一個list去接收testClass的對象
@Autowired
List<TestClass> testClassList= Collections.emptyList();
@GetMapping("/test")
public Object test(){
return testClassList;
}
}
我們調(diào)用得到的結(jié)果是
[
{
"name": "TestClass1"
},
{
"name": "TestClass2"
}
]
我們可以看到所有的testclass都獲取到了。接下來我們修改下代碼
@RestController
public class TestController {
@Qualifier //我們在這增加注解
@Autowired
List<TestClass> testClassList= Collections.emptyList();
@GetMapping("/test")
public Object test(){
return testClassList;
}
}
和上面代碼對比就是在接收參數(shù)上增加了@Qualifier注解,這樣看是有什么區(qū)別,我們調(diào)用下,結(jié)果如下:
[
{
"name": "TestClass1"
}
]
返回結(jié)果只剩下增加了@Qualifier注解的TestClass對象,這樣我們就可以理解官方說的標記篩選是什么意思了。
另外,@Qualifier注解是可以指定value的,這樣我們可以通過values來分類篩選想要的對象了,這里不列舉代碼了,感興趣的同學自己試試。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
一文詳解Java?Condition的await和signal等待通知機制
這篇文章主要為大家詳細介紹了Java?Condition的await和signal等待通知機制的相關知識,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2025-02-02
Java代碼實現(xiàn)Map和Object互轉(zhuǎn)及Map和Json互轉(zhuǎn)
這篇文章主要介紹了Java代碼實現(xiàn)map和Object互轉(zhuǎn)及Map和json互轉(zhuǎn)的相關資料,需要的朋友可以參考下2016-05-05
SpringBoot2 參數(shù)管理實踐之入?yún)⒊鰠⑴c校驗的方式
這篇文章主要介紹了SpringBoot2 參數(shù)管理實踐,入?yún)⒊鰠⑴c校驗,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-06-06
startJVM錯誤Unable to load native library: libjvm.so解決方法
這篇文章主要介紹了startJVM錯誤Unable to load native library: libjvm.so解決方法,需要的朋友可以參考下2014-07-07
Springboot項目消費Kafka數(shù)據(jù)的方法
本文詳細介紹了如何在Spring Boot項目中配置和實現(xiàn)Kafka消費者和生產(chǎn)者,結(jié)合實例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧2025-01-01

