我们知道在SpringBoot中提供了很多的Conditionalxxx
的注解,
使用方法参考博客:Spring Boot的自动配置是啥?Auto-configuration?
首先想一下,为什么我们需要这种的注解存在呢?因为选择性问题。所以程序不仅仅是顺序执行,还有很多的if-else
,其实理解Conditionalxxx
就是if-else
,只是针对的场景就是适合某种条件才注册某个bean。尤其做公共组件,必须满足各种使用者不同的需求。
现在有如下这样一个类
package com.example.condition;
public class OrderService {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
尝试按照以下方式去注册一个bean,首先注册一个,然后按照条件进行注册,理论上应该可以的吧
package com.example.condition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RootConfig {
@Bean
public OrderService normalService() {
OrderService orderService = new OrderService();
orderService.setType("normal");
return orderService;
}
@ConditionalOnMissingBean
@Bean
public OrderService conditionService() {
OrderService orderService = new OrderService();
orderService.setType("condition");
return orderService;
}
}
测试结果确实OK。
现在我们稍微修改一下上面的配置类并进行测试
异常信息:Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.condition.OrderService' available: expected single matching bean but found 2: conditionService,normalService
.
说明此时这两个Bean都注册成功了!@ConditionalOnMissingBean
失效了?
其实这里引出了自动配置的顺序问题。那么怎么样才能保证这个conditionService
一定有效呢?
有可能你很快想到了进行排序,比如:
@Configuration
// @AutoConfigureAfter(RootConfig.class)
@AutoConfigureOrder(Integer.MAX_VALUE)
public class ConditionConfig {
@ConditionalOnMissingBean
@Bean
public OrderService conditionService() {
OrderService orderService = new OrderService();
orderService.setType("condition");
return orderService;
}
}
按照猜想,将AutoConfigureOrder
的优先级设置为最低,那么里面的@Bean
应该是后注册的吧?但是结果呢?@ConditionalOnMissingBean
仍然没有达到想要的效果。
其实上面这里误用了@AutoConfigureOrder
注解,这个注解只有通过自动配置的方式(也就是在spring.factories中定义,通过EnableAutoConfiguration
引入的才有效)。
可以,那么我们把这两个配置类移到另一个包(当前Spring Boot无法扫描到的路径),并在META-INF/spring.factories
文件中配置上。如下所示:
这样却是是可以的,但是有一点很不好,就是这两个类需要移到其他路径之下,总感觉很奇怪。那有没有更好的办法呢?当然有。
首先将这个带条件的类放到另一个类里面
package com.example.condition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
public class ConditionBeanHoder {
@ConditionalOnMissingBean
@Bean
public OrderService conditionService() {
OrderService orderService = new OrderService();
orderService.setType("condition");
return orderService;
}
}
然后创建一个DeferredImportSelector
类型的实现类
package com.example.condition;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class ConditionDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ConditionBeanHoder.class.getName()};
}
}
最后配置类如下所示(通过Import来导入这个DeferredImportSelector
类即可):
package com.example.condition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(ConditionDeferredImportSelector.class)
public class RootConfig {
@Bean
public OrderService normalService() {
OrderService orderService = new OrderService();
orderService.setType("normal");
return orderService;
}
}
最后主类如下所示
package com.example.condition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringStartMain {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringStartMain.class);
OrderService orderService = applicationContext.getBean(OrderService.class);
System.out.println(orderService.getType());
applicationContext.close();
}
}
结构如下
此时如果有注入OrderService
实例,启动程序:
执行结果为normal,说明当前起作用的是在RootConfig
中的类。
如果注释掉这个类呢?
可见达到了不移动类路径也不需要在spring.factories
中配置就实现了目标。那么这个DeferredImportSelector
为什么有这样的魔力呢?
我们首先要弄懂是Conditional
如何起作用的.
对于这类注解的定义,画风一般都是如下的格式
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnMissingClass {
/**
* The names of the classes that must not be present.
* @return the names of the classes that must not be present
*/
String[] value() default {};
}
会包含@Conditional(OnClassCondition.class)
这样的元注解。因为ConditionalOnxxx
是由Spring Boot提供的,而注解Conditional
在Spring 4.0的时代就有了。首先去看一下org.springframework.context.annotation.Conditional
的注释。
以上说明包含如下几个意思
- 该注释主要是用于干涉bean的注册的(暗示一个组件只有在符合某个条件的情况下才能注册),可以是各种条件,但是有一点要注意:这个条件必须是在bean注册之前就可以判断的。等注册到了bean容器之后,这个条件其实也就没啥用了。(时机很重要)
- 作用在包含
@Component
或@Configuration
注解的类上面或者包含@Bean
注解的方法上 - 如果使用在包含
@Configuration
注解的类上面,那么这个条件判断作用于这个类里面所有的@Bean
方法、@Import
注解、ComponentScan
注解。 - 最后一点特别要注意的就是,这个注解不支持继承性。(不包含元注解
@Inherited
),不会从父类中或被继承方法中继承这个注解,限定了作用范围的单一性。而且,建议所有其他添加了@Conditional
的元注解的所有的复合注解(composed annotation
)都不要声明为@Inherited
。
这个注解必须定义一个Condition
接口的实现类。
为了这个注解的功能能够实现,必须有另一个类的存在,那就是ConditionEvaluator
.毕竟@Conditional
只是一个注解,如果作用还是要有一套算法来支持,而ConditionEvaluator
就是这套算法。除了这两个角色,还不够,那就是作用的对象。在Spring中就是ConditionContext
,这样三者就齐全了。在ConditionContext
中,通过ConditionEvaluator
来判断,@Conditional
的结果。
创建ConditionEvaluator
实例化了ConditionContext
:
/**
* Create a new {@link ConditionEvaluator} instance.
*/
public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
this.context = new ConditionContextImpl(registry, environment, resourceLoader);
}
在判断shouldSkip
的时候
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 没有@Conditional注解,不需要过滤
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 默认phase
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// 获取Condition条件
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 进行排序
AnnotationAwareOrderComparator.sort(conditions);
// 真实比较
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
如果仅仅是针对@Conditional
注解来说,此处的ConfigurationPhase
其实是没用的。因为requiredPhase == null
一直成立,然后所有的结果就看condition.matches(this.context, metadata)
了。
比如使用上面的定义一个仅在类存在的情况下才起作用的配置类
package com.example.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class OnClassH2DriverCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
context.getClassLoader().loadClass("org.h2.Driver");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
package com.example.condition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
@Conditional(OnClassH2DriverCondition.class)
public class RootConfig {
@Bean
public OrderService normalService() {
OrderService orderService = new OrderService();
orderService.setType("normal");
return orderService;
}
}
其实还是蛮简单的。Spring仅仅是针对以上的这种实现做了一些封装而已。
比如OnClassCondition
的判断类是否存在的主要逻辑就是org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter
.
protected enum ClassNameFilter {
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return isPresent(className, classLoader);
}
},
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
public abstract boolean matches(String className, ClassLoader classLoader);
public static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
forName(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;
}
}
private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader != null) {
return classLoader.loadClass(className);
}
return Class.forName(className);
}
}
最终就是通过classLoader.loadClass(className)
来判断的。
对于OnBeanCondition
,最终也是去查看当前容器内是否存在注册的的Bean定义
private BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName,
boolean considerHierarchy) {
if (beanFactory.containsBeanDefinition(beanName)) {
return beanFactory.getBeanDefinition(beanName);
}
if (considerHierarchy && beanFactory.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) {
return findBeanDefinition(((ConfigurableListableBeanFactory) beanFactory.getParentBeanFactory()), beanName,
considerHierarchy);
}
return null;
}
判断条件本来不是难事,其实真正的难点在于顺序性,就像本文开头的案例那样。对于normalService
和conditionService
这两个Bean,必须保证normalService
先进行注册,conditionService
此时才会因为容器中已经存在了相同类型的bean不再注册,而如果是normalService
在后面进行注册,conditionService
注册时容器中确实不存在相同类型的bean,当然就进行了注册,最终导致容器中存在了两个该类型的Bean了。
按照时间先后顺序,
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
,此时是在解析一个ConfigurationClass
开始之时,此时会根据类上的OnConditional条件来判断这个类是不是需要注册,注意此处ConfigurationPhase
为PARSE_CONFIGURATION
.
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// ....
}
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
,如果上面的判断是不需要过滤的,此时遇到了ComponentScans
或者ComponentScan
注解的时候,也会根据条件进行判断。此时ConfigurationPhase
为REGISTER_BEAN
。(与当前关系不大)
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// ...
}
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
,此时是在ConfigurationClassParser
完成了classpath下所有类的初步判断并解析成ConfigurationClass
之后,针对ConfigurationClass
每个元素(比如importedBy
、beanMethods
、importBeanDefinitionRegistrars
等)进行注册。
第一处:
第二处:
从以上可以看出,在解析和注册时ConfigurationPhase
的值是不一样。而ConfigurationPhase
的最关键的作用就在于ConditionEvaluator
进行比较的时候首先排除掉不属于当前阶段的判断。
// 真实比较
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
像OnBeanCondition
就属于REGISTER_BEAN
这个阶段的。所以在ConfigurationClassParser
进行解析的过程当中其实是不会起作用的。只会在ConfigurationClassBeanDefinitionReader
解析时才作用。
如上图所示:
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
用于注册的configClasses
是一个LinkedHashSet的,是有顺序的,然后再查看ConfigurationClassParser
中的configurationClasses
属性。
private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();
这个属性是LinkedHashMap
类型的,也是保证顺序的。
因为只要在解析过程中保证了configurationClasses的顺序性,就保证了注册的顺序性。
最后我们看一下org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
方法:
很明显分为了两大块,最后一块针对deferredImportSelectorHandler
做处理,按照顺序,这一块的处理结果一定排在```LinkedHashMap````的后面。
那么这个DeferredImportSelectorHandler
如何作用的呢?
在进行@Import
注解解析的时候,如果遇到的是DeferredImportSelector
就首先保存在DeferredImportSelectorHandler
对象中的deferredImportSelectors
列表当中,然后在解析完了所有的ConfigurationClass
之后再来处理deferredImportSelectors
。
总结:Spring中注册的工作主要是在ConfigurationClassPostProcessor
来实现的,而注册之前首先必须进行各种类的解析,主要就是各种注解。而针对这些注解的解析操作,都是解析一个就放到一个LinkedHashMap
当中,但是唯一的一个例外就是DeferredImportSelector
,这种结果的实现类都是先存放到一个容器中,等到其他的类都处理完成之后,再进行处理的,最后再放到LinkedHashMap
中(说到底,其实就是通过LinkedHashMap
这种数据结构来保证注册的顺序性。)
想要对ConfigurationClassPostProcessor
有深入的了解,可以参考本人的博客:Spring中最重要的一个后置处理器