關(guān)于ConditionalOnMissingBean失效問(wèn)題的追蹤
遇到一個(gè)@ConditionalOnMissingBean失效的問(wèn)題,今天花點(diǎn)時(shí)間來(lái)分析一下。
現(xiàn)場(chǎng)回放
services
首先介紹下代碼結(jié)構(gòu):有RunService,以及它的兩個(gè)實(shí)現(xiàn)類:TrainRunServiceImpl和CarRunServiceImpl

RunService
public interface RunService {
? ? void run();
}TrainRunServiceImpl
public class TrainRunServiceImpl implements RunService {
? ? @Override
? ? public void run() {
? ? ? ? System.out.println("開(kāi)火車(chē),wuwuwuwuwu");
? ? }
}CarRunServiceImpl
public class CarRunServiceImpl implements RunService {
? ? @Override
? ? public void run() {
? ? ? ? System.out.println("汽車(chē),dididi");
? ? }
}操作類
操作類MyInitBean中,注入了RunService – byType
@Component
public class MyInitBean implements InitializingBean {
@Autowired
private RunService runService;
@Override
public void afterPropertiesSet() throws Exception {
runService.run();
}
}
configuration
我們?cè)谂渲妙愔?,注入RunService的實(shí)現(xiàn)bean,并通過(guò)@ConditionalOnMissingBean來(lái)判斷是否注入。
@Configuration
public class MyConfiguration {
@Bean
@ConditionalOnMissingBean
public RunService carRunServiceImpl() {
return new CarRunServiceImpl();
}
@Bean
public RunService trainRunServiceImpl() {
return new TrainRunServiceImpl();
}
}
拋出異常
按照上述的代碼,執(zhí)行后,本以為會(huì)成功執(zhí)行,但是卻拋出了異常,異常信息如下:

在spring容器中存在了兩個(gè)RunService實(shí)現(xiàn)類。
這導(dǎo)致了MyInitBean無(wú)法決定它到底該使用這兩個(gè)中的哪一個(gè)。(默認(rèn)是byType注入的)
按照上述的異常信息,它給出了兩種解決方案:
@Qualifier
在注入bean時(shí),指定bean的名稱.
@Controller
public class MyInitBean implements InitializingBean {
? ? @Autowired
? ? @Qualifier("carRunServiceImpl")
? ? private RunService runService;
}通過(guò)@Configuration配置類注入的bean,默認(rèn)名稱為方法名稱
? @Bean // ?`trainRunServiceImpl `
? public RunService trainRunServiceImpl() {
? ? ?return new TrainRunServiceImpl();
?}直接在類頭部申明注入的bean,默認(rèn)名稱為類名稱
@Service ?// ?`trainRunServiceImpl`
public class TrainRunServiceImpl implements RunService {
}@Primary
@Primary的作用是,在bean存在多個(gè)候選者且無(wú)法決定使用哪一個(gè)時(shí),優(yōu)先使用帶有該注解的bean.
在配置類中Configuration添加
? @Bean
? @Primary
? public RunService trainRunServiceImpl() {
? ? ? return new TrainRunServiceImpl();
? }在類申明中添加
@Primary
public class TrainRunServiceImpl implements RunService {
}注意
在上述給出的兩種方法中,無(wú)論是使用@Primary還是這里容器中仍然存在多個(gè)實(shí)現(xiàn)類,
這并不是我們想要的結(jié)果。
這里為什么@ConditionalOnMissingBean會(huì)失效呢?
問(wèn)題定位
在進(jìn)行問(wèn)題定位前,我們先來(lái)回顧一下@ConditionalOnMissingBean的工作原理
工作原理
@ConditionalOnMissingBean
ConditionalOnMissingBean的注解定義如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
?? ?Class<?>[] value() default {};
?? ?String[] type() default {};
?? ?
?? ?//略....
}@ConditionalOnMissingBean通??梢杂腥缦氯N使用方式:
? ? @Bean
// ? ?@ConditionalOnMissingBean(type ="xxx.yyy.zzz.service")
// ? ?@ConditionalOnMissingBean(value = RunService.class)
? ? @ConditionalOnMissingBean //無(wú)參數(shù),表示按照返回值類型過(guò)濾
? ? public RunService carRunServiceImpl() {
? ? ? ? return new CarRunServiceImpl();
? ? }在注解上看到了一個(gè)OnBeanCondition類,在@ConditionalOnBean,ConditionalOnSingleCandidate和ConditionalOnMissingBean都看到了它的身影。
OnBeanCondition
@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
?? ?@Override
?? ?public ConditionOutcome getMatchOutcome(ConditionContext context,
?? ??? ??? ?AnnotatedTypeMetadata metadata) {
?? ??? ?//ConditionalOnBean ?略
?? ??? ?//ConditionalOnSingleCandidate 略
?? ??? ?
?? ??? ?if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
?? ??? ??? ?//尋找 @ConditionalOnMissingBean 匹配的 type;
?? ??? ??? ?BeanSearchSpec spec = new BeanSearchSpec(context, metadata,ConditionalOnMissingBean.class);
?? ??? ??? ?//從容器中尋找指定的type --- ?step1
?? ??? ??? ?MatchResult matchResult = getMatchingBeans(context, spec);
?? ??? ??? ?if (matchResult.isAnyMatched()) {
?? ??? ??? ??? ?//如果存在指定的type
?? ??? ??? ??? ?//reason: ?found beans of type 'service.Service' AServiceImpl
?? ??? ??? ??? ?String reason = createOnMissingBeanNoMatchReason(matchResult);
?? ??? ??? ??? ?//創(chuàng)建 ConditionOutcome.noMatch: return new ConditionOutcome(false, message);
?? ??? ??? ??? ?return ConditionOutcome.noMatch(ConditionMessage
?? ??? ??? ??? ??? ??? ?.forCondition(ConditionalOnMissingBean.class, spec)
?? ??? ??? ??? ??? ??? ?.because(reason));
?? ??? ??? ?}
?? ??? ??? ?
?? ??? ??? ?matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
?? ??? ??? ??? ??? ?.didNotFind("any beans").atAll();
?? ??? ?}
?? ??? ?//默認(rèn) 創(chuàng)建 ConditionOutcome.match : return new ConditionOutcome(true, message);
?? ??? ?return ConditionOutcome.match(matchMessage);
?? ?}
}ConditionOutcome 的用法:當(dāng)match= true時(shí),才注入容器.
若@ConditionalOnMissingBean找到了匹配項(xiàng),則返回ConditionOutcome.notMatch,則不注入容器。
問(wèn)題出在哪?
有了上面的一系列原理支撐,但是為什么沒(méi)有執(zhí)行到我們想要的結(jié)果呢?
debug執(zhí)行后,發(fā)現(xiàn)問(wèn)題出現(xiàn)在OnBeanCondition .getMatchingBeans(context, spec)這個(gè)方法中。
首先再次回顧下配置類:

在注入carRunServiceImpl時(shí),執(zhí)行OnBeanCondition .getMatchingBeans(context, spec)并沒(méi)有找到下面定義的trainRunServiceImpl.
真相只有一個(gè):
@Configuration 在初始化bean的時(shí)候,順序出現(xiàn)了問(wèn)題,那么如何控制初始化bean的順序呢?
解決問(wèn)題
一頓分析之后,我們發(fā)現(xiàn)只要控制了bean的加載順序之后,上述的問(wèn)題就可以解決了。
接下來(lái)我們來(lái)嘗試控制bean初始化順序:
Configuration中bean使用@Order ----------------- failure
@Configuration
public class MyConfiguration {
@Order(2)
@Bean
@ConditionalOnMissingBean
public RunService carRunServiceImpl() {
return new CarRunServiceImpl();
}
@Order(1)
@Bean
public RunService trainRunServiceImpl() {
return new TrainRunServiceImpl();
}
}
Configuration 調(diào)整bean申明順序----------------- success
將帶有@ConditionalOnMissingBean注解的bean,申明在代碼的末尾位置,操作成功:
@Configuration
public class MyConfiguration {
@Bean
public RunService trainRunServiceImpl() {
return new TrainRunServiceImpl();
}
@Bean
@ConditionalOnMissingBean
public RunService carRunServiceImpl() {
return new CarRunServiceImpl();
}
}
配置多個(gè)Configuration類,并通過(guò)@Order指定順序---------------- failure
@Configuration
@Order(Ordered.LOWEST_PRECEDENCE) //最低優(yōu)先級(jí)
public class MyConfiguration {
@Bean
@ConditionalOnMissingBean
public RunService carRunServiceImpl() {
return new CarRunServiceImpl();
}
}
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE) //最高優(yōu)先級(jí)
public class MyConfiguration2 {
@Bean
public RunService trainRunServiceImpl() {
return new TrainRunServiceImpl();
}
}
@Configuration并不能通過(guò)@Order指定順序。
大膽猜測(cè)下: @Configuration通過(guò)配置類名的自然順序來(lái)加載的。
@Configuration配置類加載順序通過(guò)類名順序來(lái)加載 ------- 驗(yàn)證success
將MyConfiguration2重命名為Configuration2,而它的加載順序在MyConfiguration之前,執(zhí)行程序成功。

這里貌似所有的問(wèn)題似乎都解決了, 只需要我們自定義的配置類名稱保證最優(yōu)先加載就可以了。我們只需要注意配置類的命名規(guī)則即可.
但是,這種解決方案,似乎并不是那么令人信服。
@AutoConfigureBefore,@AutoConfigureAfter
經(jīng)查文檔,終于找到了需要的東西:我們可以通過(guò)@AutoConfigureBefore,@AutoConfigureAfter來(lái)控制配置類的加載順序。
@Configuration
public class MyConfiguration {
@Bean
@ConditionalOnMissingBean
public RunService carRunServiceImpl() {
return new CarRunServiceImpl();
}
}
@Configuration
@AutoConfigureBefore(MyConfiguration.class)
public class MyConfiguration2 {
@Bean
public RunService trainRunServiceImpl() {
return new TrainRunServiceImpl();
}
}
注意:
如果要開(kāi)啟@EnableAutoConfiguration需要在META-INF/spring.factories文件中添加如下內(nèi)容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ xxx.configuration.MyConfiguration2,\ xxx.configuration.MyConfiguration
結(jié)論
我們需要控制目標(biāo)bean的加載順序即可。
但是我們?cè)趯?shí)際的使用一些通用plugin過(guò)程中(如redis),并沒(méi)有刻意的指定bean的加載順序,這是為什么呢?
因?yàn)椋?/strong>在實(shí)際的應(yīng)用過(guò)程中,我們使用第三方插件,他們的默認(rèn)配置都會(huì)存在于插件的jar包中,而我們的個(gè)性化配置則存在于自身的應(yīng)用中。
而容器會(huì)優(yōu)先執(zhí)行classes/,然后才執(zhí)行jars/classes.
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java利用url實(shí)現(xiàn)網(wǎng)頁(yè)內(nèi)容的抓取
本文主要介紹了java利用url實(shí)現(xiàn)網(wǎng)頁(yè)內(nèi)容抓取的示例。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03
java web開(kāi)發(fā)中獲取tomcat上properties文件內(nèi)容的方法
java web開(kāi)發(fā)中如何獲取tomcat上properties文件內(nèi)容的方法,方便文件存儲(chǔ)位置的修改,解耦和,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
java實(shí)現(xiàn)斐波那契數(shù)列的3種方法
這篇文章主要介紹了java實(shí)現(xiàn)斐波那契數(shù)列的3種方法,有需要的朋友可以參考一下2014-01-01
Intellij無(wú)法創(chuàng)建java文件解決方案
這篇文章主要介紹了Intellij無(wú)法創(chuàng)建java文件解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
JDBC連接mysql亂碼異常問(wèn)題處理總結(jié)
這篇文章主要介紹了JDBC連接mysql亂碼異常問(wèn)題處理的辦法和思路,有需要的朋友參考學(xué)習(xí)下。2017-12-12
解決druid監(jiān)控頁(yè)面SQL不顯示的問(wèn)題
這篇文章主要介紹了解決druid監(jiān)控頁(yè)面SQL不顯示的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
http調(diào)用controller方法時(shí)openfeign執(zhí)行流程
這篇文章主要為大家介紹了http調(diào)用controller方法時(shí)openfeign執(zhí)行流程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
SpringCloud 微服務(wù)最佳開(kāi)發(fā)實(shí)踐
本文結(jié)合我們實(shí)際的開(kāi)發(fā)中遇到的一些問(wèn)題整理出了一份微服務(wù)開(kāi)發(fā)的實(shí)踐規(guī)范,對(duì)SpringCloud 微服務(wù)開(kāi)發(fā)實(shí)踐相關(guān)知識(shí)感興趣的朋友一起看看吧2021-07-07
淺談resultMap的用法及關(guān)聯(lián)結(jié)果集映射
這篇文章主要介紹了resultMap的用法及關(guān)聯(lián)結(jié)果集映射操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06

