• 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);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

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

    那么会调到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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    上述方法中首先判断如果存在@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;
    		}
    
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    上述代码上一篇有分析,现在来分析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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    核心代码就是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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

    总结

    对于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 {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    测试

    @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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    sprinboot:
      aaaa: jiaza
    
    • 1
    • 2

    @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-------");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试

    @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();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    sprinboot:
      aaaa: jiaza222
    
    • 1
    • 2

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

  • 相关阅读:
    Nature文章|博士后对就业更有信心 但仍是学术界的苦力
    [管理与领导-122]:IT人看清职场中的隐性规则 - 18- 一半佛一半魔,一半君子一半小人,阴阳互转,生生不息,儒、释、道、法,一个不能少
    废品回收小程序:高效便捷回收,推动市场发展
    softlock_up以及时钟中断问题记录
    源码编译安装与yum和rpm软件安装详解
    星巴克推出Web3平台;天啦噜,AI绘画能007了;『决策算法』电子书;合成人脸数据集;面向数据的版本控制;前沿论文 | ShowMeAI资讯日报
    【UniApp】-uni-app-路由
    Git 学习(二)---- 分支及协作开发
    Vector源码分析
    python 第二次作业
  • 原文地址:https://blog.csdn.net/admin522043032/article/details/126642140