概述

  • 由上篇文章: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;
}