SpringBoot的Conditional机制源码解析
- SpringBoot的Conditional机制源码解析
- 案例
- @ConditionalOnProperty
- @ConditionalOnBean
- @ConditionalOnProperty的原理
- ConditionalOnProperty 的属性匹配逻辑
- getMatchOutcome的切入点
SpringBoot的Conditional机制源码解析
SpringBoot的Conditional机制,是提供给开发人员,根据条件取加载Bean的功能。Conditional不是一个
注解,他是SpringBoot提供的一类。通过控制Conditional注解指定的条件成立与否,我们可以控制被
Conditional类注解标注的Ben是否加载,而其实现则是通过是否加载该Bean的BeanDefinition来控制的。例如@ConditionalOnProperty则是根据配置文件是否有指定的配置项,并且该配置项是否是指定的值,来决定是否加载该注解标注的Bean的BeanDefinition。而@ConditionalOnBean则是在Spring容器中包含指定的Bean的BeanDefinition时,才会加载该Bean的BeanDefinition。而Spring容器对Bean的实例化和初始化,就是根据BeanDefinition来进行的。
案例
@ConditionalOnProperty
用于测试的Bean
package com.example.sourcestudy.bean;
public class TestConditionOnProperty {
}
在Controller中引用该Bean
@RestController
public class HelloController {
@Autowired
private TestConditionOnProperty testConditionOnProperty;
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
启动类中配置加载该Bean,添加@ConditionalOnProperty注解
@SpringBootApplication
public class SourceStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SourceStudyApplication.class, args);
}
@Bean
@ConditionalOnProperty(prefix = "some", name = "object", havingValue = "true")
public TestConditionOnProperty getObject() {
return new TestConditionOnProperty();
}
}
注解的条件是配置文件必须有some.object=true这样项配置,该Bean才会被加载。此时配置文件中并没有配置some.object=true,不会加载该Bean。而Controller中又引用了该Bean,所以启动会报错
***************************
APPLICATION FAILED TO START
***************************
Description:
Field testConditionOnProperty in com.example.sourcestudy.controller.HelloController required a bean of type 'com.example.sourcestudy.bean.TestConditionOnProperty' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
The following candidates were found but could not be injected:
- Bean method 'getObject' in 'SourceStudyApplication' not loaded because @ConditionalOnProperty (some.object=true) did not find property 'object'
在配置文件中添加指定配置项
some.object=true
然后重新启动,就不报错了
@ConditionalOnBean
用于测试的Bean
public class TestConditionalOnBean {
}
用于测试的Controller,先不添加任何注解
public class TestController {
}
启动类中添加配置
@Bean
@ConditionalOnBean(TestController.class)
public TestConditionalOnBean getObject1() {
return new TestConditionalOnBean();
}
在HelloController中添加该Bean的引用
@Autowired
private TestConditionalOnBean testConditionalOnBean;
启动会发现报错
***************************
APPLICATION FAILED TO START
***************************
Description:
Field testConditionalOnBean in com.example.sourcestudy.controller.HelloController required a bean of type 'com.example.sourcestudy.bean.TestConditionalOnBean' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
The following candidates were found but could not be injected:
- Bean method 'getObject1' in 'SourceStudyApplication' not loaded because @ConditionalOnBean (types: com.example.sourcestudy.controller.TestController; SearchStrategy: all) did not find any beans of type com.example.sourcestudy.controller.TestController
给TestController添加@Controller注解
@Controller
public class TestController {
}
重新启动,就不再报错了
@ConditionalOnProperty的原理
我们来分析@ConditionalOnProperty的实现原理,首先看一下@ConditionalOnProperty注解包含的内容
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
boolean matchIfMissing() default false;
}
ConditionalOnProperty 的属性匹配逻辑
我们可以看到ConditionalOnProperty 注解上又标注类另外一个注解@Conditional({OnPropertyCondition.class}),那么OnPropertyCondition肯定是实现@ConditionalOnProperty功能的关键。
OnPropertyCondition的重点方法是getMatchOutcome
org.springframework.boot.autoconfigure.condition.OnPropertyCondition#getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
/**
* 获取@ConditionalOnProperty注解上的注解信息,
* 也就是@ConditionalOnProperty(prefix = "some", name = "object", havingValue = "true")
* 当中的prefix,name,havingValue
* AnnotationAttributes继承了LinkedHashMap,所以他就是一个map
* 里面的key-value就是每一项注解信息
* 一个@ConditionalOnProperty注解对应一个AnnotationAttributes
* 如果Bean上配置了多个@ConditionalOnProperty,这里就有多个AnnotationAttributes
* 但是我们只配了一个@ConditionalOnProperty,所以这里的list大小为一
*/
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
/*存放不匹配的Conditional信息*/
List<ConditionMessage> noMatch = new ArrayList<>();
/*存放匹配的Conditional信息*/
List<ConditionMessage> match = new ArrayList<>();
/**
* 循环遍历List<AnnotationAttributes>,校验是否匹配
* 如果匹配,就在match中添加一条对应的ConditionMessage
* 如果不匹配,就在noMatch中添加一条对应的ConditionMessage
*/
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
/**
* 调用determineOutcome,查看当前的@ConditionalOnProperty的条件是否都满足
* 返回一个ConditionOutcome对象,里面有一个boolean类型的成员属性match代码是否满足
*
* 但是在determineOutcome方法执行前,会先调用context.getEnvironment()
* 其实就是从当前的ApplicationContext中获取PropertyResolver对象
* 而PropertyResolver中已经保存了配置文件中的所有信息
*/
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
/*把ConditionOutcome对象中的ConditionMessage,添加到match集合或noMatch集合*/
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
/*如果noMatch不为空,代表该Bean中至少有一个@ConditionalOnProperty的条件不满足*/
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
/*走到这里,代表全都满足*/
return ConditionOutcome.match(ConditionMessage.of(match));
}
这里贴上一张图,可以看到当前AnnotationAttributes的数据
还有ConditionOutcome 对象 outcome
接下来看一下determineOutcome,他会返回一个ConditionOutcome 对象,该对象有一个boolean类型的match属性,代表当前的@ConditionalOnProperty注解指定的条件是否都满足,也就是配置文件中是否有对应的配置项。
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
/*Spec是当前类的内部类,根据annotationAttributes进行封装*/
Spec spec = new Spec(annotationAttributes);
/*missingProperties 对应缺失的属性*/
List<String> missingProperties = new ArrayList<>();
/*missingProperties 存放不匹配的属性*/
List<String> nonMatchingProperties = new ArrayList<>();
/**
* 通过PropertyResolver去验证对应的属性是否匹配
* 存在属性确实的情况,则把属性名添加到missingProperties中
* 存在不匹配的情况,则把属性名添加到nonMatchingProperties中
*/
spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
/*组装ConditionOutcome对象返回*/
if (!missingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
}
if (!nonMatchingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.found("different value in property", "different value in properties")
.items(Style.QUOTE, nonMatchingProperties));
}
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
这与贴上一张关于Spec的图,看看其中的属性
然后进入spec.collectProperties看一下他的处理逻辑
private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
/*遍历当前Spec对象的所有属性名*/
for (String name : this.names) {
/*name前拼上前缀,那么key就是some.object了*/
String key = this.prefix + name;
/*查看当前的PropertyResolver对象,是否有包含指定的属性*/
/*这里PropertyResolver对象是已经包含配置文件中所有配置信息的对象*/
if (resolver.containsProperty(key)) {
/*若包含,则查看是否匹配*/
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
/*不匹配,则包属性名添加到nonMatching集合中,也就是上面的nonMatchingProperties*/
nonMatching.add(name);
}
}
else {
/*PropertyResolver对象不包含该属性*/
if (!this.matchIfMissing) {
/*则把属性名添加到上面的missingProperties中*/
missing.add(name);
}
}
}
}
上面就是ConditionalOnProperty的匹配逻辑,通过getMatchOutcome校验该Bean上所有的@ConditionalOnProperty注解指定的条件是否都满足。
那么就一定有一个地方会调用到这个方法。
getMatchOutcome的切入点
这里贴上一张从《Spring源码深度解析》当中摘取出来的图
可以看到切入的开始点在ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
这里的ConfigurationClassPostProcessor继承父类BeanDefinitionRegistryPostProcessor,父类BeanDefinitionRegistryPostProcessor又继承了BeanFactoryPostProcessor。所以ConfigurationClassPostProcessor其实是一个BeanFactory后置处理器。所以他在Spring的AbstractApplicationContext类的refresh方法中,会在invokeBeanFactoryPostProcessors方法的这一步执行。里面是执行Spring容器中所有的BeanFactory后置处理器的postProcessBeanFactory方法,但是在此之前,会先执行所有的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法。
org.springframework.context.support.AbstractApplicationContext#refresh
// 调用上下文中注册为bean的工厂处理器。
nvokeBeanFactoryPostProcessors(beanFactory);
org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
/**
* 调用容器中所有实现了BeanDefinitionRegistryPostProcessor的实现类的postProcessBeanDefinitionRegistry方法
* 而BeanDefinitionRegistryPostProcessor又继承了BeanFactoryPostProcessor
* 其中Spring-Mybatis就是利用这个方法进行Mapper的扫描和注册成BeanDefinition
*/
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)
/**
* 循环遍历所有的BeanFactoryPostProcessor,
* 发现是BeanDefinitionRegistryPostProcessor类型,
* 则调用他的postProcessBeanDefinitionRegistry.
* 把BeanDefinitionRegistry对象传入进去,
* 所以这里允许BeanDefinitionRegistryPostProcessor可以继续像容器注册一些额外的BeanDefinition
* 例如Spring-Mybatis中注册了所有扫描到的Mapper
*/
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}
上面是调用到postProcessBeanDefinitionRegistry方法的流程,里面有些Spring-Mybatis的注释可以忽略,是我分析Spring-Mybatis的时候写的。
那么上面的ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法,后面会到ConfigurationClassParser的parse方法,然后一步一步调用调用到processConfigurationClass
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 这里就是Conditional注解的生效点,验证不通过就直接忽略掉不会进入里面的处理了
if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
...处理逻辑省略
}
org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase)
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子类对象的集合,对应的是该Bean上各种@Conditionalxxx注解对应的Conditionalxxx类*/
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
/*获取Conditional类,例如ConditionalOnProperty*/
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
/*添加到conditions集合*/
conditions.add(condition);
}
}
/*排序*/
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
/**
* 遍历调用condition.matches方法,
* 不匹配则返回false,那么这里整个方法就返回true,
* 外面条件通过,就会跳过了
*/
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
/*条件都匹配,则返回false,外面就不跳过*/
return false;
}
但是这里调用的是Conditional的matches方法,不是getMatchOutcome,那我们看看ConditionalOnProperty的父类SpringBootCondition的matches,SpringBootCondition实现了Condition的接口,所以调用的matches方法走的就是SpringBootCondition的matches方法
org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.Condition)
protected final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) {
return condition instanceof SpringBootCondition ? ((SpringBootCondition)condition).getMatchOutcome(context, metadata).isMatch() : condition.matches(context, metadata);
}
可以看到里面进行了一个类型判断,如果是SpringBootCondition类型的Conditional,则调用子类实现的getMatchOutcome方法,而ConditionalOnProperty的getMatchOutcome重写了父类SpringBootCondition的抽象方法,所以就会调用到ConditionalOnProperty的getMatchOutcome方法了。