Spring 从 3.0 开始支持条件装配,@Conditional 和 @Configuration 可以说构成了 Spring 注解驱动的核心,也是 Spring Boot 自动装配的基础。本文会以一道阿里的面试题[1]引出相关的话题:
- @Conditional 是做什么的?
- @Conditional 多个条件是什么逻辑关系?
- 条件判断在什么时候执行?
- ConfigurationCondition 和 Condition 有什么区别?什么时候使用 ConfigurationCondition?
- 多个 Condition 执行的顺序是什么样的?可以配置优先级么?
- 可以介绍一下 @Conditional 常见的一些用法么?
1. @Conditional 简介
@Conditional 是 Spring 提供的条件装配的核心 API,在 Spring Framework 中感受还并不明显,但在 Spring Boot 中大行其道。与 @Conditional 相关的 API 有:
- @Conditional:条件装配的核心注解。Spring Boot 基于这个标准标注派生出多个子注解,如 @ConditionalOnBean、@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnWebApplication 等。
- Condition:定义 @Conditional 注解的匹配规则。
- ConfigurationCondition:相比于 Condition 接口,多出 getConfigurationPhase() 方法,可以获取 @Conditional 的装配阶段,默认配置解析阶段和Bean注入阶段都会进行匹配。
- ConditionEvaluator:用于处理 @Conditional 注解。提供了 shouldSkip 方法,判断该 Bean 是否需要注入。
1.1 相关概念
@Conditional 注解会指定条件配置的规则 Condition。如果有多个匹配规则,则先排序后匹配。如果没有定义顺序,则按配置的顺序逐一匹配。@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Conditional {
Class<? extends Condition>[] value();
}
1.1.1 @Conditional 用法
@Conditional 大致有以下两种用法:
- 标注在类上:需要经历解析和注册两个阶段,通常配合 @Configuration 使用。因为标注在类上,需要被 @ComponentScan 扫描到后注册到容器中,因此类上一般至少标注有 @Componen 或其派生注解。配置类解析时,先判断是否解析这个配置类;配置类注册时,判断这个配置类是否需要注册。
- 标注在方法上:只会经历注册阶段,通常配合 @Bean 使用。
说明:如果 @Conditional 标注在配置类上,而这个 Condition 条件只有配置类解析后才能满足。为了让配置类被解析,需要让 @Conditional 跳过配置阶段,这就是 ConfigurationCondition 存在的意义。
@Configuration
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class }) // 类上使用,配合@Configuration
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcTemplateAutoConfiguration {
@Bean
@ConditionalOnMissingBean(JdbcOperations.class) // 方法上使用,配合@Bean
public JdbcTemplate jdbcTemplate() {
...
}
}
1.1.2 Spring 配置类
Spring 配置类有狭义和广义之分。@Configuration 是狭义配置类,其它的配置类则是广义配置类。具体见 ConfigurationClassUtils#isConfigurationCandidate。
- 类上有 @Configuration 注解
- 类上有 @Compontent 注解
- 类上有 @CompontentScan 注解
- 类上有 @Import 注解
- 类上有 @ImportResource 注解
- 类中有 @Bean 标注的方法
1.2 核心 API
1.2.1 Condition
说明:Condition 接口中 matches 方法有两个参数:public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
- ConditionContext:条件装配的上下文环境,封装了 BeanFactory 和 Environment 等上下文环境,用于 Condition 判断是否需要注入。
- AnnotatedTypeMetadata:这是 Spring Core 中处理注解的核心 API,封装了 @Conditional 标注的类或方法的全部注解信息,包括 @Conditional 注解和其它的所有注解。
比如,@Profile 就是使用 ProfileCondition 实现的。
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
1.2.2 ConfigurationCondition
ConfigurationCondition 的理解相对来说难一点,在 Spring Boot 中大量使用。
public interface ConfigurationCondition extends Condition {
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
PARSE_CONFIGURATION, // 1. 配置解析阶段。如果不匹配,不会解析配置文件,但配置类会注册到容器中
REGISTER_BEAN // 2. Bean注册阶段。如果汪匹配,不会注册到容器中
}
}
说明:Spring 将配置解析具体分为两个阶段:
- PARSE_CONFIGURATION:配置解析阶段。如果匹配失败,不会解析配置文件。
- REGISTER_BEAN:注册阶段。如果匹配失败,不会注册到容器中。
2. 源码分析
@Conditional 是 ConditionEvaluator 处理,提供了 shouldSkip 方法,来判断是否忽略该 Bean。@Conditional 通常是和 @Cofiguration 一起处理,分为解析和注册两个阶段,其中 ConfigurationClassPostProcessor 处理解析阶段,ConfigurationClassBeanDefinitionReader 处理注册阶段。
- ConditionEvaluator:(核心)封装了对 @Conditional 的处理逻辑。
- ConfigurationClassPostProcessor:(核心)解析阶段。配置类是否解析。
- ConfigurationClassBeanDefinitionReader:(核心)注册阶段。是否注册。
- AnnotatedBeanDefinitionReader:是否注册。处理 @Conditional 装配条件。
- ClassPathScanningCandidateComponentProvider:是否注册。处理 @Conditional 装配条件。
2.1 ConditionEvaluator
ConditionEvaluator 会读取 @Conditional 注解的装配条件 Condition,调用 shouldSkip 方法进行条件匹配。
- Condition:不管是解析还是注册阶段都会进行条件匹配,返回调用 Condition#matches 匹配的结果。
ConfigurationCondition:相比于 Condition,增加了配置阶段的匹配。当装配阶段不一致时,直接返回 false,也就是需要解析或注册该 Bean。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 1. 没有@Conditional注解,直接过
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 2. 如果直接调用shouldSkip(metadata)方法,则根据该类是否是配置类决定装配阶段
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// 3. 获取Conditon,并排序
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);
// 4. (核心)如果不是ConfigurationCondition,则无论是解析还是注册阶段都会进行匹配
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;
}
2.2 ConfigurationClassPostProcessor
ConfigurationClassPostProcessor 是 @Configuration 注解的处理入口,调用 processConfigBeanDefinitions 方法解析配置类。ConfigurationClassPostProcessor 解析配置类,ConfigurationClassBeanDefinitionReader 则用于注册。 说明:ConfigurationClassPostProcessor 中可以看到配置类都会经历解析和注册两个阶段:
解析阶段:ConfigurationClassParser 解析是 PARSE_CONFIGURATION 阶段。
- 注册阶段:ConfigurationClassBeanDefinitionReader 会将 ConfigurationClassParser 解析的配置信息注册到容器中,因此是 REGISTER_BEAN 阶段。另外,需要注意的是,如果配置类不能注册,则其解析的相关配置也不会注册到容器中。
// ConfigurationClassBeanDefinitionReader加载解析的配置信息
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 配置类不能注册,则直接返回。不会注册其解析的配置信息
if (trackedConditionEvaluator.shouldSkip(configClass)) {
return;
}
// 注册解析的配置信息
registerBeanDefinitionForImportedConfigurationClass(configClass);
loadBeanDefinitionsForBeanMethod(beanMethod);
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
3. 使用示例
```java @Configuration @Conditional(MyCondition1.class) public class BeanAutoConfiguration1 { @Bean public HelloService helloService() {
} }return new helloServiceImpl();
public class MyCondition1 implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //获取spring容器 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //判断容器中是否存在Service类型的bean boolean existsService = !beanFactory.getBeansOfType(HelloService.class).isEmpty(); return existsService; } } ```
总结时刻
推荐阅读
- 《@Conditional通过条件来控制bean的注册》:强烈推荐。
每天用心记录一点点。内容也许不重要,但习惯很重要!