更多Spring文章,歡迎點選 一灰灰Blog-Spring專題
當通過介面的方式注入Bean時,如果有多個子類的bean存在時,具體哪個bean會被注入呢?系統中能否存在兩個重名的bean呢?如果可以,那麼怎麼選擇引入呢?如果不行的話又該怎麼避免上面的問題呢?
I. 多例項Bean的選擇
這個場景可以說是比較常見的,現在提倡面向介面程式設計嘛,當一個介面有多個例項時,怎麼注入和引用就需要我們額外關注下了
1. 基本使用姿勢
首先定義一個介面和兩個簡單的實現類,並演示一下我們通常的用法
一個輸出的介面定義如下
public interface IPrint {
void print(String msg);
}
複製程式碼
對應給兩個實現
@Component
public class ConsolePrint implements IPrint {
@Override
public void print(String msg) {
System.out.println("console print: " + msg);
}
}
@Slf4j
@Component
public class LogPrint implements IPrint {
@Override
public void print(String msg) {
log.info("log print: {}", msg);
}
}
複製程式碼
下面就是我們一般的引用方式
@Autowired
註解時,屬性名即為預設的Bean名,如下面的logPrint
就是獲取beanName=logPrint
的bean@Resource(name=xxx)
直接指定Bean的name,來唯一選擇匹配的bean
@Component
public class NormalPrintDemo {
@Resource(name = "consolePrint")
private IPrint consolePrint;
@Autowired
private IPrint logPrint;
@PostConstruct
public void init() {
consolePrint.print(" console print!!!");
logPrint.print(" log print!!!");
}
}
複製程式碼
上面是兩種常見的使用姿勢,此外還可以藉助@Primary
註解來宣告預設的注入bean
2. @Primary
註解
這個註解就是為了解決當有多個bean滿足注入條件時,有這個註解的例項被選中
根據上面的作用說明,很明顯可以得知一點
@Primary
註解的使用有唯一性要求:即對應上面的case,一個介面的子類中,只能有一個實現上有這個註解
假設將這個註解放在LogPrint
上之後,如下
@Slf4j
@Component
@Primary
public class LogPrint implements IPrint {
@Override
public void print(String msg) {
log.info("log print: {}", msg);
}
}
複製程式碼
結合上面的常用姿勢,加上這個註解之後,我們的測試用例應該至少包含下面幾個
@Resource
指定beanName的是否會被@Primary
影響- 前面的
@Autowired
註解 + 屬性名的方式,是按照第一節的方式選擇呢,還是選擇被@Primary
標識的例項 @Autowired
+ 隨意的一個非beanName的屬性,驗證是否會選中@Primary
標識的註解
@Component
public class PrintDemoBean {
@Resource(name = "logPrint")
private IPrint print;
/**
* 下面的註解不指定name,則例項為logPrint
*/
@Autowired
private IPrint consolePrint;
// logPrint的選擇,由@Primary註解決定
@Autowired
private IPrint logPrint;
// logPrint的選擇,由@Primary註解決定
@Autowired(required = false)
private IPrint xxxPrint;
@PostConstruct
public void init() {
print.print("expect logPrint for [print]");
consolePrint.print(" expect logPrint for [consolePrint]");
logPrint.print("expect logPrint for [logPrint]");
xxxPrint.print("expect logPrint for [xxxPrint]");
}
}
複製程式碼
執行結果如下
2018-10-22 19:42:40.234 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect logPrint for [print]
2018-10-22 19:42:40.235 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect consolePrint for [consolePrint]
2018-10-22 19:42:40.235 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect logPrint for [logPrint]
2018-10-22 19:42:40.235 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect logPrint for [xxxPrint]
複製程式碼
3. 小結
根據前面的執行,因此可以知曉,選擇bean的方式如下
存在@Primary
註解時
@Resource
註解指定name時,根據name來查詢對應的bean@Autowired
註解,全部都用@Primary
標識的註解@Primary
註解要求唯一(非廣義的唯一性,並不是指只能用一個@Primary,具體看前面)
不存在@Primary
註解時
@Resource
註解指定name時,根據name來查詢對應的bean@Autowired
註解時,根據屬性名去查對應的Bean,如果查不到則拋異常;如果查到,那即是它了
II. 重名Bean的問題
在我們實際的業務開發中,有多個bean名為xxx的異常應該算是比較常見的,也就是說應該不能有兩個bean叫同一個name;但考慮下下面這個場景
A的服務,依賴B和C的服務;而B和C是兩個完全獨立的第三方服務,他們各自都提供了一個beanName=xxxService
的bean,對於A而言,Spring容器中就會有BeanName衝突的問題了,而且這種場景,對A而言,也是不可控的啊,這種情況下改怎麼辦?
1. 同名Bean
先來個case演示下同名bean的情況,如下定義兩個bean,除了包路徑不一樣外,類名相同,通過@Component
註解方式宣告bean,因此兩個bean的beanName都是SameA
package com.git.hui.boot.beanorder.choose.samename.a;
import org.springframework.stereotype.Component;
/**
* Created by @author yihui in 21:32 18/10/22.
*/
@Component
public class SameA {
private String text ;
public SameA() {
text = "a sameA!";
}
public void print() {
System.out.println(text);
}
}
package com.git.hui.boot.beanorder.choose.samename.b;
import org.springframework.stereotype.Component;
/**
* Created by @author yihui in 21:33 18/10/22.
*/
@Component
public class SameA {
private String text;
public SameA() {
text = "B SameA";
}
public void print() {
System.out.println(text);
}
}
複製程式碼
接下來測試下引用,是否有問題
package com.git.hui.boot.beanorder.choose.samename;
import com.git.hui.boot.beanorder.choose.samename.a.SameA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* Created by @author yihui in 21:32 18/10/22.
*/
@Component
public class SameDemo {
@Autowired
private SameA sameA;
@PostConstruct
public void init() {
sameA.print();
}
}
複製程式碼
執行之後,毫不意外的丟擲了異常,堆疊資訊如下
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.git.hui.boot.beanorder.Application]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'sameA' for bean class [com.git.hui.boot.beanorder.choose.samename.b.SameA] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.boot.beanorder.choose.samename.a.SameA]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:184) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at com.git.hui.boot.beanorder.Application.main(Application.java:15) [classes/:na]
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'sameA' for bean class [com.git.hui.boot.beanorder.choose.samename.b.SameA] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.boot.beanorder.choose.samename.a.SameA]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:288) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:202) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:170) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
... 12 common frames omitted
複製程式碼
2. 同名問題規避
如果真的出現了上面這個問題,該怎麼解決呢?如果這些bean是我們可控的,最簡單的方式就是不要同名,定義的時候指定beanName,如下
@Component("aSameA")
public class SameA {
private String text ;
public SameA() {
text = "a sameA!";
}
public void print() {
System.out.println(text);
}
}
複製程式碼
如果完全不可控呢?正如前面說的兩個第三方服務我都得依賴,但是他們有同名的bean,怎麼破?
一個解決方法就是排除掉其中一個同名的bean的自動載入,採用主動註冊的方式註冊這個bean
排除自動掃描的bean的方式如下,在啟動類新增註解@ComponentScan
並指定其中的excludeFilters
屬性
@SpringBootApplication
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SameA.class)})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
複製程式碼
然後自定義一個bean的配置類
package com.git.hui.boot.beanorder.choose.samename;
import com.git.hui.boot.beanorder.choose.samename.a.SameA;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by @author yihui in 22:14 18/10/22.
*/
@Configuration
public class AutoConfig {
@Bean
public SameA mySameA() {
return new SameA();
}
}
複製程式碼
其他的程式碼和之前沒有區別,再次執行,結果如下, 最後的輸出為 a sameA!
,根據型別來選擇了例項化的bean了
II. 其他
a. 更多博文
基礎篇
- 181009-SpringBoot基礎篇Bean之基本定義與使用
- 181012-SpringBoot基礎篇Bean之自動載入
- 181013-SpringBoot基礎篇Bean之動態註冊
- 181018-SpringBoot基礎篇Bean之條件注入@Condition使用姿勢
- 181019-SpringBoot基礎篇Bean之@ConditionalOnBean與@ConditionalOnClass
- 181019-SpringBoot基礎篇Bean之條件注入@ConditionalOnProperty
- 181019-SpringBoot基礎篇Bean之條件注入@ConditionalOnExpression
- 181022-SpringBoot基礎篇Bean之多例項選擇
應用篇
b. 專案原始碼
- 工程:spring-boot-demo
- module: 008-beanorder
1. 一灰灰Blog
- 一灰灰Blog個人部落格 blog.hhui.top
- 一灰灰Blog-Spring專題部落格 spring.hhui.top
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
3. 掃描關注
一灰灰blog