IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> springBoot 源码二:各种条件注解解析 -> 正文阅读

[Java知识库]springBoot 源码二:各种条件注解解析

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

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 00:56:31  更:2022-09-04 00:57:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:59:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码