文章要點
- Spring bean 的宣告方式
- Spring bean 的注入規則
- Spring bean 的依賴查詢規則
- Spring bean 的名稱定義方式和預設名稱規則
XXX required a bean of type 'XXX' that could not be found.
這種情況,一般是因為沒有在 Spring bean 容器中找到對應的 bean 。
bean 的宣告
使用一個 bean,首先要宣告一個 bean,使之交給 Spring 管理 宣告一個 bean , 常用有如下幾種方式
- xml 宣告
- 註解宣告
xml 宣告
這種適用於非 SpringBoot 框架,老舊 Spring MVC 的框架使用形式。
現有架構常用於 dubbo bean 的宣告,所有的 dubbo 引用和服務,都會宣告成為一個 Spring bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 宣告一個 Spring bean -->
<bean id="testService" class="com.example.demo.TestService"/>
</beans>
當你發現在 Spring bean 容器中無法找到一個 dubbo 服務的時候(這時候是引用 dubbo 服務),請從如下角度去思考問題:
- dubbo 服務的註冊/引用了嗎 ?
- dubbo 服務的配置屬性
interface
寫的對嗎 ? 介面包打了嗎? - dubbo 服務的引用配置屬性
id
和 使用 dubbo 服務的地方匹配嗎?
上述問題都解決後,就不會出現找不到 dubbo bean 的問題了
註解宣告
宣告成 Spring bean 有一個基礎的註解 @Component
,這些都用與類上。
@Component //這個類例項 已經交給 Spring 管理了
public class ComponentService {
}
其餘我們常用的 @Service
、@Repository
等均繼承這個基礎註解,都可以宣告一個類的例項為 Spring bean
其繼承關係如下
註解宣告-高階版
高階版註解宣告需要依賴 @Configuration
註解
上章節中的 ComponentService
也可以用如下的方式去宣告
@Configuration
public class TestConfiguration {
//宣告 ComponentService
@Bean
public ComponentService componentService() {
//返回一個自定義例項,交給 Spring bean 管理
return new ComponentService();
}
}
這種返回一個自定義 bean 的型別,常用於專案元件配置,如 DataSource
、JedisPool
等,業務系統一般用不到。
bean 的注入
我們可以使用 @Autowired
(Spring)、 @Resource
(JSR250規範) 、@Inject
(JSR330規範) 來進行注入
通常的,我們使用 @Autowired
足以滿足各種注入場景,這裡不再介紹另外兩種。
在一個被 Spring 管理的類例項 (畫重點) 當中 引用另外一個 Spring bean 的方式,即 bean 的注入方式,有如下三種:
- 建構函式注入(官方推薦)
- 欄位注入
- 方法注入
首先宣告一個 bean,用於被別的 bean 注入
@Repository
public class TestRepository {
}
建構函式注入
@Service
public class TestService {
private TestRepository testRepository;
//@Autowired
public TestService(TestRepository testRepository) {
this.testRepository = testRepository;
}
}
注意在建構函式上面,@Autowired
已經被我註釋掉,原因如下:
As of Spring Framework 4.3,
an @Autowired annotation on such a constructor is no longer necessary
if the target bean defines only one constructor to begin with.
欄位注入
@Service
public class TestService {
@Autowired
private TestRepository testRepository;
}
方法注入
@Service
public class TestService {
private TestRepository testRepository;
@Autowired
public void setTestRepository(TestRepository testRepository) {
this.testRepository = testRepository;
}
}
以上方式可以混用,不拘泥於特定的形式。推薦建構函式注入而不是欄位注入,是因為
- 欄位注入完全依賴 Spring 的 IOC 功能,耦合性較高
- 防止不當使用時候,Spring bean 欄位為空,這個是重點
- 當以也應該以
final
型別修飾字段時候,避免空指標的同時,也保證了依賴的不可變,不可變即安全 - 保證 Spring bean 的呼叫方呼叫的時候 bean 是完全初始化狀態
至此,Spring bean 的注入已經 初入門徑了。
Field xxx in xxx.xxx.xxx.xx required a single bean, but 2 were found
@Autowired
是根據介面進行注入,上述問題是因為在 依賴查詢
中發現同一 Interface
的多個例項 bean
場景還原如下
- 宣告一個介面類
public interface MultiService {
}
- 新增兩個實現類
@Service
public class MultiServiceImpl implements MultiService{
}
@Service
public class MMultiService implements MultiService{
}
- 注入
MultiService
@Service
public class TestService {
//這裡進行注入,然而 Spring 發現了2個例項,預設規則沒有匹配上,不知道該注入哪個了
@Autowired
private MultiService multiService;
}
那麼,在一個介面有多個實現 bean 的情況下,如何注入自己想要的 bean ?
依賴查詢的部分規則
- 首先根據型別查詢(byType),如果查詢結果唯一,就返回給依賴賦值。
- 如果依賴不唯一,則根據名稱查詢(byName),bean 名稱不會重複。
byName的匹配規則:
- 如果有
@Qualifier
指定 依賴 bean 的名稱,則拿指定名稱與上述查詢到的 bean 的名稱進行匹配,匹配到則返回給依賴賦值 - 如果匹配不到,則拿
@Autowired
修飾的欄位名或者方法引數名與 上述查詢到的 bean 的名稱匹配,匹配到則返回給依賴賦值 - 匹配不到則報
Field xxx in xxx.xxx.xxx.xx required a single bean, but 2 were found
錯誤
bean 名稱的指定
在宣告一個 bean 的時候,可以指定 bean 名稱
//這裡指定了bean name 為 componentService
@Component("componentService")
public class ComponentService {
}
//這裡指定了bean name 為 multiService
@Service("multiService")
public class MultiServiceImpl implements MultiService{
}
//這裡指定了bean name 為 mMultiService
@Service("mMultiService")
public class MMultiService implements MultiService{
}
如果沒有指定 bean 名稱,預設會以當前類名的首字母小寫為 bean 名稱
但是如果當前類的第二個字母仍然為大寫,那就會以當前類名 為 bean 名稱
如 MMultiService
的預設 bean 名稱為 MMultiService
MultiServiceImpl
的預設 bean 名稱為 multiServiceImpl
在使用一個 bean 的時候,也可以指定 bean 名稱
@Service
public class TestService {
@Autowired
@Qualifier("MMultiService") //指定 bean 名稱
private MultiService multiService;
}
org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'xxx' for bean class [xxx.xx.xx.xx] conflicts with existing, non-compatible bean definition of same name and class [xxx.xxx.xxx]
這種就是 bean 名稱定義重複了,知道異常棧中提示的錯誤 bean 名稱並修改即可
The bean 'xxx', defined in class path resource [xx/xx/xx.class], could not be registered. A bean with that name has already been defined in file [xx\xx\xx.class] and overriding is disabled.
這種是 bean 被聲明瞭多次,檢查是否配置類編寫錯誤,一般業務 bean 不會宣告多次
這裡可能會提示你通過設定 spring.main.allow-bean-definition-overriding=true
允許 bean 定義被覆蓋
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
請不要這麼做!請找到 bean 宣告衝突的地方,檢查 bean 是否有特殊設定,根據業務進行取捨。不要簡單覆蓋 bean 定義了事