更多Spring文章,歡迎點擊 一灰灰Blog-Spring專題java
當經過接口的方式注入Bean時,若是有多個子類的bean存在時,具體哪一個bean會被注入呢?系統中可否存在兩個重名的bean呢?若是能夠,那麼怎麼選擇引入呢?若是不行的話又該怎麼避免上面的問題呢?git
<!-- more -->github
這個場景能夠說是比較常見的,如今提倡面向接口編程嘛,當一個接口有多個實例時,怎麼注入和引用就須要咱們額外關注下了spring
首先定義一個接口和兩個簡單的實現類,並演示一下咱們一般的用法編程
一個輸出的接口定義以下ide
public interface IPrint { void print(String msg); }
對應給兩個實現spring-boot
@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); } }
下面就是咱們通常的引用方式post
@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學習
@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]
根據前面的執行,所以能夠知曉,選擇bean的方式以下
存在@Primary
註解時
@Resource
註解指定name時,根據name來查找對應的bean@Autowired
註解,所有都用@Primary
標識的註解@Primary
註解要求惟一(非廣義的惟一性,並非指只能用一個@Primary,具體看前面)不存在@Primary
註解時
@Resource
註解指定name時,根據name來查找對應的bean@Autowired
註解時,根據屬性名去查對應的Bean,若是查不到則拋異常;若是查到,那便是它了在咱們實際的業務開發中,有多個bean名爲xxx的異常應該算是比較常見的,也就是說應該不能有兩個bean叫同一個name;但考慮下下面這個場景
A的服務,依賴B和C的服務;而B和C是兩個徹底獨立的第三方服務,他們各自都提供了一個beanName=xxxService
的bean,對於A而言,Spring容器中就會有BeanName衝突的問題了,並且這種場景,對A而言,也是不可控的啊,這種狀況下改怎麼辦?
先來個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
若是真的出現了上面這個問題,該怎麼解決呢?若是這些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了
基礎篇
應用篇
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
一灰灰blog
知識星球