SpringBoot学习(五):自动配置的源码实现(三)@Conditional条件化加载机制
概述由上篇文章:SpringBoot学习(五):自动配置的源码实现(二)Spring容器对自动配置的加载的分析可知,通过在应用主类中添加@SpringBootApplication或者@EnableAutoConfiguration注解,可以激活SpringBoot的自动配置机制,为应用提供一系列默认的功能组件,在应用中可以直接使用如@Autowired注解注入即可,而不需要在应用中显式配置...
概述
- 由上篇文章: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; }
更多推荐
所有评论(0)