文章要點

  • 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 的型別,常用於專案元件配置,如 DataSourceJedisPool 等,業務系統一般用不到。

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

以上方式可以混用,不拘泥於特定的形式。推薦建構函式注入而不是欄位注入,是因為

  1. 欄位注入完全依賴 Spring 的 IOC 功能,耦合性較高
  2. 防止不當使用時候,Spring bean 欄位為空,這個是重點
  3. 當以也應該以 final 型別修飾字段時候,避免空指標的同時,也保證了依賴的不可變,不可變即安全
  4. 保證 Spring bean 的呼叫方呼叫的時候 bean 是完全初始化狀態

至此,Spring bean 的注入已經 初入門徑了。

Field xxx in xxx.xxx.xxx.xx required a single bean, but 2 were found

@Autowired 是根據介面進行注入,上述問題是因為在 依賴查詢 中發現同一 Interface 的多個例項 bean

場景還原如下

  1. 宣告一個介面類
public interface MultiService {
}
  1. 新增兩個實現類
@Service
public class MultiServiceImpl implements MultiService{
} @Service
public class MMultiService implements MultiService{
}
  1. 注入 MultiService
@Service
public class TestService { //這裡進行注入,然而 Spring 發現了2個例項,預設規則沒有匹配上,不知道該注入哪個了
@Autowired
private MultiService multiService;
}

那麼,在一個介面有多個實現 bean 的情況下,如何注入自己想要的 bean ?

依賴查詢的部分規則

  1. 首先根據型別查詢(byType),如果查詢結果唯一,就返回給依賴賦值。
  2. 如果依賴不唯一,則根據名稱查詢(byName),bean 名稱不會重複。

byName的匹配規則:

  1. 如果有 @Qualifier 指定 依賴 bean 的名稱,則拿指定名稱與上述查詢到的 bean 的名稱進行匹配,匹配到則返回給依賴賦值
  2. 如果匹配不到,則拿 @Autowired 修飾的欄位名或者方法引數名與 上述查詢到的 bean 的名稱匹配,匹配到則返回給依賴賦值
  3. 匹配不到則報 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 定義了事