SpringBoot学习(五):自动配置的源码实现(三)@Conditional条件化加载机制

概述

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

你可能感兴趣的:(SpringBoot)