概述
- 由上篇文章:SpringBoot学习(五):自动配置的源码实现(二)Spring容器对自动配置的加载的分析可知,通过在应用主类中添加@SpringBootApplication或者@EnableAutoConfiguration注解,可以激活SpringBoot的自动配置机制,为应用提供一系列默认的功能组件,在应用中可以直接使用如@Autowired注解注入即可,而不需要在应用中显式配置。
- 在SpringBoot内部实现中,每个自动配置的功能组件都对应一个使用@Configuration注解的配置类,Spring容器在启动处理@EnableAutoConfiguration注解时,会自动加载这些配置类;然后基于@Contional注解提供的条件化加载机制,决定是否将在该配置类内部通过@Bean注解定义的功能组件加载到Spring容器。
@Conditional注解体系结构
- @Conditional是在Spring4.0引入的,主要用在需要条件化加载的类或方法上,即只有@Conditional注解中指定条件均满足时,才加载该类或方法对应的bean到Spring容器,其中条件化为基于Spring4.0提供的Condition接口实现类来实现。
- @Conditional注解的定义如下:在value中指定一个或多个需要满足的条件,即Conditon接口的实现类。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
- Condition接口的定义如下:每个Condition接口实现类表示某个特定的条件,实现matches方法,基于当前类的注解元数据metadata来定义该特定条件的判断逻辑(或方法的注解元数据,以下类似,不再赘述)。
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
作用域
- 类级别:与@Component及其子注解,@Configuration注解一起使用,基于@Conditonal注解指定的条件,判断是否需要加载该类到Spring容器;
- 注解级别:附加到其他注解定义中,构成复合注解。@SpringBootApplication就是一个复合注解,包含@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解。
- 方法级别:与@Bean注解一起使用,判断是否需要加载方法对应的bean到Spring容器。
与其他注解的关系
- 与@Configuration注解一起使用:当@Conditional注解指定条件无法满足时,则该配置类上的其他注解都不会生效,包括@ComponentScan,@Import,@PropertySource等,以及也不会注册配置类内部的@Bean注解的方法对应的bean到Spring容器。所以在类级别进行控制。
- 与@Bean注解一起使用:当@Conditional注解指定的条件无法满足时,则@Bean注解的方法对应的bean不会注册到Spring容器。所以在方法级别进行更加细粒度的控制。
案例分析:RedisAutoConfiguration
- 以RedisAutoConfiguration这个SpringBoot针对Redis提供的配置类为例,RedisAutoConfiguration配置类定义如下:
**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis support.
*
*/
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
- 与@Configuration一起使用: @ConditionalOnClass(RedisOperations.class): 当类路径下存在RedisOperations的实现类,即应用中存在存在Redis相关操作时,激活该配置类;
- 与@Bean一起使用:在redisTemplate方法中使用@ConditionalOnMissingBean(name = “redisTemplate”),即Spring容器中不存在beanName为redisTemplate的Bean时,将该方法的返回值template,以redisTemplate作为beanName,注册到Spring容器。
SpringBoot对@Conditional的拓展
- SpringBoot提供了自动配置功能。针对某个功能组件,以应用代码自身提供的为准,即如果应用代码提供了该功能组件,则以应用代码的为准,没有则自动加载一个默认的到Spring容器。或者为某个功能组件,自动配置一个依赖组件。
- SpringBoot通过拓展@Conditional注解,派生更多语义明确的条件注解,以及定义对应的Condition接口实现类来处理判断逻辑。
类级别
- @ConditionalOnClass:判断类路径是否存在指定类、类的子类,接口实现类等,存在则返回true,继续执行;如RedisAutoConfiguration配置类的@ConditionalOnClass(RedisOperations.class);
- @ConditionalOnMissingClass:与@ConditionalOnClass语义相反,不存在时返回true;
- 对应的Condition接口实现类为OnClassCondition。
Bean级别(基于BeanFactory包含的BeanDefinition)
- @ConditionalOnBean:判断当前Spring容器存在指定类对应的BeanDefinition,存在则返回true;
- @ConditionalOnMissingBean:与ConditionalOnBean语义相反;
- 对应的Condition接口实现类为OnBeanCondition。
其他
- @ConditionalOnProperty(属性级别)、@ConditionalOnResource(资源级别)等。
@Conditional注解处理与条件化加载
- 由@Conditional注解体系的分析可知,@Condtional注解通常是与@Configuration,@Bean等注解一起使用的。
- 由上一篇文章:SpringBoot学习(五):自动配置的源码实现(二)Spring容器对自动配置的加载分析可知:AutoConfigurationImportSelector从META-INF/spring.factories文件获取EnableAutoConfiguration作为key对应的自动配置类列表后,针对每个配置类,加载该配置类并创建ConfigurationClass类的configurationClass来封装该配置类,并以configurationClass作为参数,调用ConfigurationClassParser的processConfigurationClass方法来对该配置类的注解和类内部方法进行处理:processConfigurationClass方法的定义如下:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 处理@Conditional注解
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
// 处理其他注解:@ComponentScan,@Import,内部方法的@Bean等
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
由代码分析可知:最先对@Conditional注解进行处理,即调用conditionEvaluator的shouldSkip来处理,如果@Conditional注解对应的条件不满足,则直接返回,不再继续往下执行。@ComponentScan,@Import,内部方法的@Bean等注解的处理是在下面的doProcessConfigurationClass定义的。
ConditionEvaluator:@Conditional注解处理器
- 由以上分析可知,在ConditionEvaluator的shouldSkip方法中定义@Conditional注解的处理。
- shouldSkip方法的定义如下:基于需要条件化加载的类的注解元数据metadata来执行,具体为看metadata中是否存在@Conditional注解,有则取出@Conditional注解对应的Condition条件列表,遍历该列表,判断所有条件是否都满足。
/**
* Determine if an item should be skipped based on {@code @Conditional} annotations.
* @param metadata the meta data
* @param phase the phase of the call
* @return if the item should be skipped
*/
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
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);
}
}
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;
}