熟悉 SpringBoot 的小伙伴们肯定不会对 @Conditional 注解感到陌生,它在 SpringBoot 的自动化配置特性中起到了非常重要的作用。许多配置类在加载 Bean 时都使用到了 @ConditionalOnClass、@ConditionalOnBean,@ConditionalOnProperty 等 @Conditional 的衍生注解。
那么,在单纯的 Spring 项目中,我们是否也可以使用 @Conditional 来实现一些自动化配置的特性呢?我们该怎么样去使用@Conditional? 它又是如何生效的?别着急,本篇文章会一一解答。
@Conditional 在 Spring 4.0 中被引入,用于开发 “If-Then-Else” 类型的 bean 注册条件检查。在 @Conditional 之前,也有一个注解 @Porfile 起到类似的作用,它们两个的区别在于:
首先来看一下源码中 @Conditional 的定义
package org.springframework.context.annotation;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
根据定义, @Conditional 可以使用在类或方法上,具体的用法有:
@Conditional 有一个属性 value,其类型是 Condition 数组。组件必须匹配数组中所有的 Condition,才可以被注册。
package org.springframework.context.annotation;
@FunctionalInterface
public interface Condition {
/**
* 判断条件是否匹配
* @param context 上下文信息,可以从中获取 BeanDefinitionRegistry,BeanFactory,Environment,ResourceLoader,ClassLoader 等一些用于资源加载的信息
* @param metadata 注解的元信息,可以从中获取注解的属性
* @return {@code true} 条件匹配,组件可以注册
* or {@code false} 否决组件的注册
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Condition 是一个函数式接口,只有一个 matches 方法,返回 true 则表示条件匹配。matches 方法的两个参数分别是上下文信息和注解的元信息,从这两个参数中可以获取到 IOC 容器和当前组件的信息,从而判断条件是否匹配。
由于 ConditionContext 和 AnnotatedTypeMetadata 的方法都比较简单,这里就不贴出源码了,有兴趣的小伙伴可自行翻看源码。
Condition 必须遵循与 BeanFactoryPostProcessor 相同的限制,并注意永远不要与 bean 实例交互。如果要对与 @Configuration bean 交互的条件进行更细粒度的控制,可以考虑 ConfigurationCondition 接口。
public interface ConfigurationCondition extends Condition {
/**
* 返回条件生效的阶段
*/
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
/**
* 在 @Configuration 类解析时生效
*/
PARSE_CONFIGURATION,
/**
* 在 bean 注册时生效。此时所有的 @Configuration 都解析完成了。
*/
REGISTER_BEAN
}
}
接下来我们在 Spring 下实现一个简单的 ConditionalOnBean 注解,实现一个 bean 只有在另一个 bean 存在时,才进行注册。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// Conditional 作为元注解,主要的判断逻辑在 OnBeanCondition 类中
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
// bean 的名称
String[] name() default {};
}
// OnBeanCondition 主要的判断逻辑在 matches 方法中
class OnBeanCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(ConditionalOnShardingProps.class.getName());
if (attrs != null) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
for (Object beanName : attrs.get("name")) {
if(!beanFactory.containsBean((String) beanName)) {
return false;
}
}
return true;
}
}
return true;
}
}
// 使用 ConditionalOnBean 注解
@Configuration
@Conditional(ConditionalOnBean.class)
public static class OnBeanConfig {
@Bean
@ConditionalOnBean(name = "a")
public B b() {
return new B();
}
}
这样,一个自定义的 Conditional 注解就写好了,使用时只要把它加到类或方法上即可生效。
首先,通过调用链路的分析可知,Conditional 的调用方是 ConditionEvaluator,而 ConditionEvaluator 在 ConfigurationClassParser、ConfigurationClassBeanDefinitionReader 和 AnnotatedBeanDefinitionReader 中均有所使用。先来看下这三个类在 Spring 的流程中扮演什么角色。
ConfigurationClassParser
ConfigurationClassParser 在 ConfigurationClassPostProcessor 中被使用到,而 ConfigurationClassPostProcessor 是一个 BeanDefinitionRegistryPostProcessor,顾名思义,就是在 bean 扫描完成后,对 bean 的定义进行修改的一个后置处理器,主要的功能在于解析 bean 中的所有配置类。
// ConfigurationClassParser 的核心逻辑
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 调用 shouldSkip 方法,对应的阶段是 PARSE_CONFIGURATION
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
... 省略
}
ConfigurationClassBeanDefinitionReader
ConfigurationClassBeanDefinitionReader 也是在 ConfigurationClassPostProcessor 中被使用到。在配置类解析完成后,对其中包含的 bean 进行注册。
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 调用的还是 conditionEvaluator.shouldSkip,在其基础上做了个缓存。
// 对应的阶段是 REGISTER_BEAN
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
... 省略
}
AnnotatedBeanDefinitionReader
AnnotatedBeanDefinitionReader 主要在 AnnotationConfigApplicationContext 中被使用到。AnnotationConfigApplicationContext 是 Spring 中的一个高级容器,与 ClassPathXmlApplicationContext 不同的是,它主要通过解析 Java 配置文件中的配置,来进行 bean 的注册。
<T> void doRegisterBean(Class<T> beanClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 调用 shouldSkip 方法,对应的阶段为 null
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
... 省略
}
综上,我们已经了解了 ConditionEvaluator 在 Spring 的流程中是如何发挥作用的,接下来看看核心方法 shouldSkip 的具体实现逻辑。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 不存在 Conditional 注解,则不处理
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 阶段为空时的处理逻辑
if (phase == null) {
// 有 Configuration、Component、ComponentScan、Import、ImportResource 等注解,则任务是配置解析阶段
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// 获取所有 Conditional 注解,并提取出 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);
}
}
// 对 Condition 进行排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// 调用 Condition 的 matches 方法,不符合条件的则跳过
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
// 所有的 Condition 都符合,则不跳过,进行后续处理
return false;
}
看了源代码,相信小伙伴们对 Conditional 的理解又深入了一层。