SpringBoot的Conditional机制源码解析

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方法了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值