前言
熟悉 SpringBoot 的小伙伴们肯定不会对 @Conditional 注解感到陌生,它在 SpringBoot 的自动化配置特性中起到了非常重要的作用。许多配置类在加载 Bean 时都使用到了 @ConditionalOnClass、@ConditionalOnBean,@ConditionalOnProperty 等 @Conditional 的衍生注解。
那么,在单纯的 Spring 项目中,我们是否也可以使用 @Conditional 来实现一些自动化配置的特性呢?我们该怎么样去使用@Conditional? 它又是如何生效的?别着急,本篇文章会一一解答。
概述
@Conditional 在 Spring 4.0 中被引入,用于开发 “If-Then-Else” 类型的 bean 注册条件检查。在 @Conditional 之前,也有一个注解 @Porfile 起到类似的作用,它们两个的区别在于:
- @Profile 仅用于基于环境变量的条件检查,使用范围比较窄。
- @Conditional 更加通用,开发人员可以自定义条件检查策略。可用于 bean 注册时的条件检查。
- 4.3.8后,@Profile 也基于 @Conditional 来实现。
用法
首先来看一下源码中 @Conditional 的定义
package org.springframework.context.annotation;
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
根据定义, @Conditional 可以使用在类或方法上,具体的用法有:
- 作为类注解,标注在直接或间接使用了 @Component 的类上,包括 @Configuration 类
- 作为元注解,直接标注在其他的注解上面,用于编写自定义注解
- 作为任何 @Bean 方法的方法级注解
@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)
&