项目场景:
在SpringBoot中有一类Conditional的注解,用来控制Bean的注入,其中有一个ConditionalOnMissingBean注解比较特殊。
如果不明白这个注解的使用场景的话,很可能会遇到偶尔失效的情况。
看个案例体会一下
@ConfigurationpublicclassMyConfig{@Bean@ConditionalOnMissingBean(BClass.class)publicAClassaClass(){System.out.println("AClass is created");returnnewAClass();}@BeanpublicBClassbClass(){System.out.println("BClass is created");returnnewBClass();}}
输出:
AClass 也注入到Spring容器了。
按照@ConditionalOnMissingBean(BClass.class)的理解是,如果容器没有BClass Bean的话,才会注入AClass。但是我们看到BClass也在Spring容器中,那么AClass 不应该注入Spring容器中,才对符合我们的预期。这里就出现了@ConditionalOnMissingBean 失效的情况。
让我们把上面的代码调整一下,把BClass 的注入代码放到AClass的前面去。
@ConfigurationpublicclassMyConfig{@BeanpublicBClassbClass(){System.out.println("BClass is created");returnnewBClass();}@Bean![@ConditionalOnMissingBean](https://img-blog.csdnimg.cn/direct/6b89f26668d14bdcb7c7cfe50d7482aa.png)(BClass.class)publicAClassaClass(){System.out.println("AClass is created");returnnewAClass();}}
输出
这次的输出结果符合我们的预期了,@ConditionalOnMissingBean又生效了。
从这个现象我们基本可以推测出来,是SpringBean的注入顺序影响到了@ConditionalOnMissingBean的判断,但是SpringBean的顺序通常又是不可控的,那么@ConditionalOnMissingBean这个注解岂不是也会变得不可控。在同一个 Config 类中,我们可以通过控制代码顺序来解决问题,像上面这种情况。但是通常情况下,我们@ConditionalOnMissingBean注解中类都是来自其他class,那么这时候其他的Bean的注入顺序我们就没法控制了。我们的@ConditionalOnMissingBean方法也就失控了。
那么问题来了,我们应该怎么使用@ConditionalOnMissingBean注解呢?或者使用这个注解有什么条件限制吗?
我们还是先点进源码去看看这个注解的使用注释中都写了什么吧
看看官方是怎么说的
先看看@ConditionalOnMissingBean注解的注释上面都写了什么。通常这里会给我们很多关于这个类的注意事项和使用场景等
/**
* {@link Conditional @Conditional} that only matches when no beans meeting the specified
* requirements are already contained in the {@link BeanFactory}. None of the requirements
* must be met for the condition to match and the requirements do not have to be met by
* the same bean.
* <p>
* When placed on a {@code @Bean} method, the bean class defaults to the return type of
* the factory method:
*
* <pre class="code">
* @Configuration
* public class MyAutoConfiguration {
*
* @ConditionalOnMissingBean
* @Bean
* public MyService myService() {
* ...
* }
*
* }</pre>
* <p>
* In the sample above the condition will match if no bean of type {@code MyService} is
* already contained in the {@link BeanFactory}.
* <p>
* The condition can only match the bean definitions that have been processed by the
* application context so far and, as such, it is strongly recommended to use this
* condition on auto-configuration classes only. If a candidate bean may be created by
* another auto-configuration, make sure that the one using this condition runs after.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.0.0
*/
上面的注释中最后,我们看到这么一句话:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
翻译一下:
第一句:“这个condition 只会match 那些到目前为止已经被application处理的bean。”
这句话就充分验证我们上面那个案例的推断,ConditionalOnMissingBean 对Bean的判断是受到bean被spring容器处理顺序的影响的。意思就是说,在ConditionalOnMissingBean判断的时候,如果判断的bean还没有被spring容器解析到的时候,就会认为没有。这一点和我们上面的推断是一致。
第二句:“强烈建议,这个condition 只用在 auto-configuration class 上”
这句话就很明确了,告诉我们这个注解要在 auto-configuration class 上使用。什么是auto-configuration class 我们下面再细说。
第三句:如果 要判断的Bean 可能在另外一个 auto-configuration中,那么就要确保我们要判断的Bean要在那个之后。
这句话也就是说,如果确实用在了 auto-configuration class上,但是要判断的bean是在其他的 auto-configuration中的bean,那么就要保证在那个 auto-configuration 类之后去加载。至于怎么保证这个顺序,我们下面再详细讨论。
通过对注释的解读,了解到,@ConditionalOnMissingBean注解的使用是有前提限制的,否则就可能出现“失效”的情况。
分析原因以及我们应该怎么正确使用
通过前面的分析,我们基本可以确定了导致此问题的一个根本原因就是spring对bean的加载顺序导致的。提到spring对bean的加载顺序,我们需要了解一下spring对bean加载的顺序规则。
我们可以把spring的bean分为两大类型,一类是我们自定义的普通Bean,非自动装配类,另一类叫自动装配类。
非自动装配类的加载顺序是我们无法通过代码进行人为干预的,其中@Order 这类接口都是无效的,这些类加载顺序应该是按照文件目录结构加载的。
而自动装配类的加载顺序是我们可以控制的,控制的方式有很多,注解包括@Oder,@AutoConfigureBefore,@AutoConfigureAfter,AutoConfigureOrder,以及Order接口,这些都是用来控制自动装配类的。
知道了这些,那么接下来我就思考一下,@ConditionalOnMissingBean应该怎么使用?
首先,我们使用@ConditionalOnMissingBean注解的目的一般都是为了做一个兜底操作,就是我们不清楚,当spring容器启动的时候,是否有其他使用者添加了对应Bean,所以这个判断一定是要在把用户自定义的Bean都加载完成,再做判断。
根据我们上面说的bean的加载顺序,我们应该得出一个结论,那就是应该把@ConditionalOnMissingBean注解使用在自动装配的类中,也就是说我们要用@ConditionalOnMissingBean注解,就得把这个配置类改为自动装配类,而不能是一个普通的@Configuration注解的类。也就是这个类注解里提到的
it is strongly recommended to use this condition on auto-configuration classes only.
什么是自动装配类?
这个就是另外一个话题了。
springBoot 2.7 之前的版本,是通过扫描/META-INF/spring.factories文件,将里面配置的类进行自动装配。
最新的版本已经调整为/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。
所以如果要使用@ConditionalOnMissingBean注解,就要把所在的配置类,通过自动装配的形式加入spring容器中,而不是仅仅通过一个简单的@Configuration注解,而是还要加入自动装配的配置文件中去。
其实你看SpringBoot的源码就能看到,@ConditionalOnMissingBean注解使用的场景都是在自动配置的类中。
看下面SpringBoot对 MongoDB的自动装配的配置。
总结
小小的一个注解,背后却有这么深的知识体系。多研究一下SpringBoot的源码,其实是最好的教材案例。
版权归原作者 Jianyang.liu 所有, 如有侵权,请联系我们删除。