springBoot 源码二:各种条件注解解析

本文详细解析了SpringBoot中的条件注解,如@ConditionalOnBean、@ConditionalOnMissingBean等,介绍了它们在自动配置过程中的作用及判断逻辑。通过实例展示了如何根据配置文件的值来决定组件是否加载,并提供了条件注解的使用示例。

springboot各种条件注解解析

上一篇分析了springboot的自动配置过程。springboot拿到了需要自动配置的全类名,去加载那些自动配置类。就以springboot自动配置的tomcat举例。会根据不同的条件注解来判断是否加载配置类
在这里插入图片描述
那么springboot的条件注解有哪些呢?
条件注解
SpringBoot中的条件注解有:

  1. ConditionalOnBean:是否存在某个某类或某个名字的Bean
  2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
  3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
  4. ConditionalOnClass:是否存在某个类
  5. ConditionalOnMissingClass:是否缺失某个类
  6. ConditionalOnExpression:指定的表达式返回的是true还是false
  7. ConditionalOnJava:判断Java版本
  8. ConditionalOnJndi:JNDI指定的资源是否存在
  9. ConditionalOnWebApplication:当前应用是一个Web应用
  10. ConditionalOnNotWebApplication:当前应用不是一个Web应用
  11. ConditionalOnProperty:Environment中是否存在某个属性
  12. ConditionalOnResource:指定的资源是否存在
  13. ConditionalOnWarDeployment:当前项目是不是以War包部署的方式运行
  14. ConditionalOnCloudPlatform:是不是在某个云平台上

所有的条件注解都是基于@Conditional来实现的。关于@Conditional属于spring的知识 这里不再赘述。在spring的生命周期中分析过
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所有的条件注解都是通过@Conditional加上对应的条件判断类来实现的。通过几个常用的条件注解类进行分析。
@ConditionalOnBean
@ConditionalOnBean对应的条件判断类是OnBeanCondition。
在这里插入图片描述
首先会调用Condition接口的matches方法。Condition是接口,那么就会调到SpringBootCondition的matches方法

@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// 针对每个条件注解进行条件判断
		// 条件注解写在了哪个类上,或哪个方法上
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			// 条件的判断结果
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			// 如果log的日志级别为trace,那就直接记录当前条件的判断结果
			logOutcome(classOrMethodName, outcome);
			// 将判断结果记录到ConditionEvaluationReport中
			//ConditionEvaluationReportLoggingListener会在收到ContextRefreshedEvent事件后把判断结果用日志的方式打印出来
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
					+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
					+ "that class. This can also happen if you are "
					+ "@ComponentScanning a springframework package (e.g. if you "
					+ "put a @ComponentScan in the default package by mistake)", ex);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
		}
	}

判断的方法在于ConditionOutcome outcome = getMatchOutcome(context, metadata);它是一个抽象方法

	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

那么会调到OnBeanCondition的getMatchOutcome方法

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ConditionMessage matchMessage = ConditionMessage.empty();
		MergedAnnotations annotations = metadata.getAnnotations();

		// 如果存在ConditionalOnBean注解
		if (annotations.isPresent(ConditionalOnBean.class)) {
			//封装
			Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
			//底层通过获取bean工厂,从bean工厂获取对应的bean,把结果封装到MatchResult 
			MatchResult matchResult = getMatchingBeans(context, spec);

			// 如果某个Bean不存在
			if (!matchResult.isAllMatched()) {
				String reason = createOnBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(spec.message().because(reason));
			}

			// 所有Bean都存在
			matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
					matchResult.getNamesOfAllMatches());
		}

		// 如果存在ConditionalOnSingleCandidate注解
		if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
			Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
			MatchResult matchResult = getMatchingBeans(context, spec);

			// Bean不存在
			if (!matchResult.isAllMatched()) {
				return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
			}

			// Bean存在
			Set<String> allBeans = matchResult.getNamesOfAllMatches();

			// 如果只有一个
			if (allBeans.size() == 1) {
				matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
			}
			else {
				// 如果有多个
				List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
						spec.getStrategy() == SearchStrategy.ALL);

				// 没有主Bean,那就不匹配
				if (primaryBeans.isEmpty()) {
					return ConditionOutcome.noMatch(
							spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
				}
				// 有多个主Bean,那就不匹配
				if (primaryBeans.size() > 1) {
					return ConditionOutcome
							.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
				}

				// 只有一个主Bean
				matchMessage = spec.message(matchMessage)
						.found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
						.items(Style.QUOTE, allBeans);
			}
		}

		// 存在ConditionalOnMissingBean注解
		if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
			Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
					ConditionalOnMissingBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);

			//有任意一个Bean存在,那就条件不匹配
			if (matchResult.isAnyMatched()) {
				String reason = createOnMissingBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(spec.message().because(reason));
			}

			// 都不存在在,则匹配
			matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
		}
		return ConditionOutcome.match(matchMessage);
	}

上述方法中首先判断如果存在@ConditionalOnBean注解,就会从bean工厂中获取bean,如果能找到,那么就匹配成功,如果找不到就匹配失败,由于@ConditionalOnBean的属性值可能是多个,那么就必须满足所有bean都能找到才算成功。然后不论成功失败,都把结果封装到MatchResult 当中。在判断MatchResult 的结果,如果失败了 就直接返回,如果成功了 接着判断。接着往下判断是否存在@ConditionalOnSingleCandidate直接,如果存在接着判断bean是否存在,是否只有一个bean,等等。判断完这层就会接着判断是否存在@ConditionalOnMissingBean注解。同理判断有没有这个bean。
也就是说OnBeanCondition同时判断了@ConditionalOnBean,@ConditionalOnSingleCandidate,@ConditionalOnMissingBean。
在这里插入图片描述
在这里插入图片描述
可以看到这两个注解用的也正是OnBeanCondition这个条件类。
条件注解的执行过程就是这样其他注解也同理。
上一篇分析过在从spring.factories获取到所有自动配置类和过滤器filter。首先会通过filter进行过滤,过滤完才会交给springboot加载。接下来就分析如何过滤。
获取到的过滤器filter有
在这里插入图片描述

List<String> filter(List<String> configurations) {
			long startTime = System.nanoTime();
			String[] candidates = StringUtils.toStringArray(configurations);
			boolean skipped = false;
			// 逐个利用AutoConfigurationImportFilter来判断所有的自动配置类的条件是否匹配,匹配结果存在match数组中
			// 先利用OnBeanCondition进行过滤
			// 再利用OnClassCondition进行过滤
			// 再利用OnWebApplicationCondition进行过滤
			for (AutoConfigurationImportFilter filter : this.filters) {
				boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);

				for (int i = 0; i < match.length; i++) {
					if (!match[i]) {
						candidates[i] = null;
						skipped = true;
					}
				}
			}
			// 全部都匹配
			if (!skipped) {
				return configurations;
			}
			// 把匹配的记录在result集合中,最后返回
			List<String> result = new ArrayList<>(candidates.length);
			for (String candidate : candidates) {
				if (candidate != null) {
					result.add(candidate);
				}
			}
			if (logger.isTraceEnabled()) {
				int numberFiltered = configurations.size() - result.size();
				logger.trace("Filtered " + numberFiltered + " auto configuration class in "
						+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
			}
			return result;
		}

	}

上述代码上一篇有分析,现在来分析filter.match(candidates, this.autoConfigurationMetadata)这部分。还是通过OnBeanCondition举例。由于OnBeanCondition没有实现match方法,调的就是父类FilteringSpringBootConditionmatch方法。

@Override
	public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
		ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);

		// autoConfigurationMetadata就是spring-autoconfigure-metadata.properties中的内容
		ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);

		boolean[] match = new boolean[outcomes.length];
		for (int i = 0; i < outcomes.length; i++) {
			match[i] = (outcomes[i] == null || outcomes[i].isMatch());

			// 首轮不匹配的,进行日志打印,以及记录到ConditionEvaluationReport中去
			if (!match[i] && outcomes[i] != null) {
				logOutcome(autoConfigurationClasses[i], outcomes[i]);
				if (report != null) {
					report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
				}
			}
		}
		return match;
	}

核心代码就是ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);用来判断是否通过,判断能完成后把结果放到boolean[]数组中返回。而getOutcomes是抽象方法,就会调到子类也就是OnBeanCondition的getOutcomes方法

	@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];

		// 遍历处理每个自动配置类
		for (int i = 0; i < outcomes.length; i++) {
			String autoConfigurationClass = autoConfigurationClasses[i];
			if (autoConfigurationClass != null) {

				// 当前自动配置中@ConditionalOnBean所依赖的类
				Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");

				// 如果onBeanTypes都存在,则返回null
				outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);

				if (outcomes[i] == null) {

					// 继续判断@ConditionalOnSingleCandidate所依赖的类
					Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
							"ConditionalOnSingleCandidate");
					outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
				}
			}
		}
		return outcomes;
	}

到此条件注解的流程就结束了,其他两个条件类流程同理。

总结

对于springboot各种条件注解,首先获取的条件注解中@Conditional的值。调用其抽象父类SpringBootCondition的matches方法。该方法中通过getMatchOutcome(context, metadata)方法将结果封装成ConditionOutcome 。同时getMatchOutcome是抽象方法,具体实现在子类。如果matches方法返回true表示可以加载,返回false表示不需要加载。

在自动配置过程中,从MATE-INF/spring.factories获取到自动配置类和过滤器filter。在过滤的过程中调用了FilteringSpringBootConditionmatch方法。该方法中getOutcomes(autoConfigurationClasses, autoConfigurationMetadata)用于判断是否通过,是一个抽象方法在具体子类实现,也就是调用了OnBeanCondition、OnClassCondition、OnWebApplicationCondition的getOutcomes方法。

条件注解案例

使用几个常用的条件注解
@ConditionalOnMissingBean和@ConditionalOnBean

@Component
@ConditionalOnMissingBean(DemoBean.class)
public class AaMissBean {
	public void test(){
		System.out.println("AaMissBean------------test");
	}
}

@Component
@ConditionalOnBean(DemoBean.class)
public class AaOnBean {
	public void test(){
		System.out.println("AaBean------------test");
	}
}

@Component
public class DemoBean {

}

测试

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		ApplicationContext run = SpringApplication.run(Application.class, args);
		Object onBean = run.getBean("aaOnBean");
		System.out.println(onBean);
		Object missBean = run.getBean("aaMissBean");
		System.out.println(missBean);
	}
}

由于springboot中存在DemoBean ,那么AaOnBean 就会被加载,而AaMissBean 不会被加载
在这里插入图片描述
如果去掉DemoBean 的@Component注解,则相反 那么AaMissBean就会被加载,而AaOnBean 不会被加载
在这里插入图片描述
@ConditionalOnProperty
application.yml

sprinboot:
  aaaa: jiaza

@ConditionalOnProperty(prefix = “sprinboot”,name = “aaaa”,havingValue = “jiazai”,matchIfMissing = false)
通过配置文件的值来判断是否加载
prefix :前缀
name :名称
havingValue :对应的值
matchIfMissing :默认为false,必须匹配才能加载 如果为true 如果没找到sprinboot.aaaa的值也能加载。

@Component
//配置文件中的springboot.aaaa 必须等于 jiazai 才能加载
@ConditionalOnProperty(prefix = "sprinboot",name = "aaaa",havingValue = "jiazai",matchIfMissing = false)
public class AaProperBean {
	public void test(){
		System.out.println("----AaProperBean-------");
	}
}

测试

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		ApplicationContext run = SpringApplication.run(Application.class, args);
		AaProperBean aaProperBean = run.getBean("aaProperBean",AaProperBean.class);
		aaProperBean.test();
	}
}

在这里插入图片描述如果修改配置文件

sprinboot:
  aaaa: jiaza222

在这里插入图片描述
就不会加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值