• 【springboot】各种条件注解与源码分析


    SpringBoot中的底层原理实现中,条件注解时非常重要的,因为条件注解直接决定了一个Bean在什么条件下才会生效。

    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:是不是在某个云平台上

    本节我们来看看非常核心的两个条件注解@ConditionalOnClass和@ConditionalOnBean的底层是如何实现的。

    自定义条件注解

    SpringBoot中众多的条件注解,都是基于Spring中的@Conditional来实现的,所以我们先来用一下@Conditional注解。

    先来看下@Conditional注解的定义:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Conditional {
    
    	Class<? extends Condition>[] value();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    根据定义我们在用@Conditional注解时,需要指定一个或多个Condition的实现类,所以我们先来提供一个实现类:

    package com.morris.spring.boot.condition;
    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    public class WindowsCondition implements Condition {
    
        /**
         * 根据条件判断是否注入对应的Bean
         * @param conditionContext 应用上下文
         * @param annotatedTypeMetadata 加了@Conditional注解的方法的元数据信息
         * @return
         */
        @Override
        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
    
            String osName = conditionContext.getEnvironment().getProperty("os.name");
    
            if(osName.contains("Windows")) {
                return true;
            }
    
            return false;
        }
    }
    
    • 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

    很明显,我们可以在matches方法中来定义条件逻辑:

    1. ConditionContext:表示条件上下文,可以通过ConditionContext获取到当前的类加载器、BeanFactory、Environment环境变量对象
    2. AnnotatedTypeMetadata:表示当前正在进行条件判断的Bean所对应的类信息,或方法信息(比如@Bean定义的一个Bean),可以通过AnnotatedTypeMetadata获取到当前类或方法相关的信息,从而就可以拿到条件注解的信息,当然如果一个Bean上使用了多个条件注解,那么在解析过程中都可以获取到,同时也能获取Bean上定义的其他注解信息

    了解这些,我们就来看看@ConditionalOnClass是如何工作的。

    @ConditionalOnClass的底层工作原理

    先来看一个案例:

    package com.morris.spring.boot.config;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    @ConditionalOnMissingClass("io.undertow.Undertow")
    public class ConditionalOnClassConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们在ConditionalOnClassConfig这个类上使用了两个条件注解:

    1. @ConditionalOnClass(name = “org.apache.catalina.startup.Tomcat”):条件是项目依赖中存在"org.apache.catalina.startup.Tomcat"这个类,则表示符合条件
    2. @ConditionalOnMissingClass(value = “io.undertow.Undertow”):条件是项目依赖中不存在"io.undertow.Undertow"这个类,则表示符合条件

    这两个注解的功能都是通过@Conditional(OnClassCondition.class)来实现的,那么在OnClassCondition类中是如何对这两个注解进行区分的呢?

    Spring在解析到ConditionalOnClassConfig这个配置时,发现该类上用到了条件注解就会进行条件解析,相关源码如下:

    org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase)

    for (Condition condition : conditions) {
    	ConfigurationPhase requiredPhase = null;
    	if (condition instanceof ConfigurationCondition) {
    		requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
    	}
    	if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    		return true;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    conditions中保存了两个OnClassCondition对象,这段代码会依次调用OnClassCondition对象的matches方法进行条件匹配,一旦某一个条件不匹配就不会进行下一个条件的判断了,如果这里return的是true,但是这段代码所在的方法叫做shouldSkip,所以true表示忽略。

    我们继续看OnClassCondition的matches()方法的实现。

    OnClassCondition类继承了FilteringSpringBootCondition,FilteringSpringBootCondition类又继承了SpringBootCondition,而SpringBootCondition实现了Condition接口,matches()方法也是在SpringBootCondition这个类中实现的:

    org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)

    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    	// 获取当前解析的类名或方法名
    	String classOrMethodName = getClassOrMethodName(metadata);
    	try {
    		// 进行具体的条件匹配,ConditionOutcome表示匹配结果
    		ConditionOutcome outcome = getMatchOutcome(context, metadata);
    		// 日志记录匹配结果
    		logOutcome(classOrMethodName, outcome);
    		// 将匹配结果放入ConditionEvaluationReport中,后面会统一打印
    		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

    所以具体的条件匹配逻辑在getMatchOutcome方法中,而SpringBootCondition类中的getMatchOutcome方法是一个抽象方法,具体的实现逻辑就在子类OnClassCondition中:

    org.springframework.boot.autoconfigure.condition.OnClassCondition#getMatchOutcome

    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    	ClassLoader classLoader = context.getClassLoader();
    	ConditionMessage matchMessage = ConditionMessage.empty();
    	// 拿到ConditionalOnClass注解中的value值,也就是要判断是否存在的类名
    	List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
    	if (onClasses != null) {
    		// 判断onClasses中不存在的类
    		List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
    		if (!missing.isEmpty()) {
    			// 如果有缺失的类,那就表示不匹配
    			return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
    											.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
    		}
    		// 否则就表示匹配
    		matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
    			.found("required class", "required classes")
    			.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
    	}
    	// 和上面类似,只不过是判断onMissingClasses是不是全部缺失,如果是则表示匹配
    	List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
    	if (onMissingClasses != null) {
    		List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
    		if (!present.isEmpty()) {
    			return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
    											.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
    		}
    		matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
    			.didNotFind("unwanted class", "unwanted classes")
    			.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
    	}
    	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

    在getMatchOutcome方法中的逻辑为:

    1. 如果类或方法上有@ConditionalOnClass注解,则获取@ConditionalOnClass注解中的value属性,也就是要判断是否存在的类名
    2. 利用ClassNameFilter.MISSING来判断这些类是否缺失,把缺失的类的类名存入missing集合
    3. 如果missing不为空,则表示有类缺失,则表示不匹配,并利用ConditionMessage记录哪些类是缺失的,直接return,表示条件不匹配
    4. 否则,则表示条件匹配,继续执行代码
    5. 如果类或方法上有ConditionalOnMissingClass注解,则获取ConditionalOnMissingClass注解中的value属性,也就是要判断是否缺失的类名
    6. 利用ClassNameFilter.PRESENT来判断这些类是否存在,把存在的类的类名存入present集合
    7. 如果present不为空,则表示有类存在,则表示不匹配,并利用ConditionMessage记录哪些类是存在的,直接return,表示条件不匹配
    8. 否则,则表示条件匹配,继续执行代码
    9. return,表示条件匹配

    因为ConditionalOnClass注解和ConditionalOnMissingClass注解的逻辑是比较类似的,所以在源码中都是在OnClassCondition这个类中实现的,假如一个类上即有@ConditionalOnClass,也有@ConditionalOnMissingClass,比如以下代码:

    @Configuration
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    @ConditionalOnMissingClass("io.undertow.Undertow")
    public class ConditionalOnClassConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 如果@ConditionalOnClass条件匹配、@ConditionalOnMissingClass条件也匹配,那么getMatchOutcome方法会执行两次
    2. 如果@ConditionalOnClass条件不匹配,那么getMatchOutcome方法会执行一次
    3. 如果@ConditionalOnClass条件匹配、@ConditionalOnMissingClass条件不匹配,那么getMatchOutcome方法也只会执行一次,因为在getMatchOutcome方法处理了这种情况

    怎么判断一个类是否存在?
    org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#resolve

    protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
    	// 反射调用,没抛异常就是存在,抛出异常就是不存在
    	if (classLoader != null) {
    		return Class.forName(className, false, classLoader);
    	}
    	return Class.forName(className);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @ConditionalOnBean的底层工作原理

    @ConditionalOnBean和@ConditionalOnClass的底层实现应该是差不多的,一个是判断Bean存不存在,一个是判断类存不存在,事实上也确实差不多。

    首先@ConditionalOnBean和@ConditionalOnMissingBean注解的实现对应的都是OnBeanCondition类,OnBeanCondition类也是继承了SpringBootCondition,所以OnBeanCondition类中的getMatchOutcome方法才是匹配逻辑:

    org.springframework.boot.autoconfigure.condition.OnBeanCondition#getMatchOutcome

    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);
    		MatchResult matchResult = getMatchingBeans(context, spec);
    		if (!matchResult.isAllMatched()) {
    			// 如果某个Bean不存在
    			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) {
    			// 如果只有一个Bean
    			matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
    		}
    		else {
    			// 如果有多个
    			List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
    														spec.getStrategy() == SearchStrategy.ALL);
    
    			if (primaryBeans.isEmpty()) {
    				// 没有主Bean,那就不匹配
    				return ConditionOutcome.noMatch(
    					spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
    			}
    			if (primaryBeans.size() > 1) {
    				// 有多个主Bean,那就不匹配
    				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

    逻辑流程为:

    1. 当前在解析的类或方法上,是否有@ConditionalOnBean注解,如果有则生成对应的Spec对象,该对象中包含了用户指定的,要判断的是否存在的Bean的类型
    2. 调用getMatchingBeans方法进行条件判断,MatchResult为条件判断结果
    3. 只要判断出来某一个Bean不存在,则return,表示条件不匹配
    4. 只要所有Bean都存在,则继续执行下面代码
    5. 当前在解析的类或方法上,是否有@ConditionalOnSingleCandidate注解,如果有则生成对应的SingleCandidateSpec对象,该对象中包含了用户指定的,要判断的是否存在的Bean的类型(只能指定一个类型),并且该类型的Bean只能有一个
    6. 调用getMatchingBeans方法进行条件判断,MatchResult为条件判断结果
    7. 指定类型的Bean如果不存在,则return,表示条件不匹配
    8. 如果指定类型的Bean存在,但是存在多个,那就看是否存在主Bean(加了@primary注解的Bean),并且只能有一个主Bean,如果没有,则return,表示条件不匹配
    9. 如果只有一个主Bean,则表示条件匹配,继续执行下面代码
    10. 当前在解析的类或方法上,是否有@ConditionalOnMissingBean注解,如果有则生成对应的Spec对象,该对象中包含了用户指定的,要判断的是否缺失的Bean的类型
    11. 调用getMatchingBeans方法进行条件判断,MatchResult为条件判断结果
    12. 只要有任意一个Bean存在,则return,表示条件不匹配
    13. 都存在,则表示条件匹配
    14. 结束

    怎么知道bean是否存在呢?

    protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
    	ClassLoader classLoader = context.getClassLoader();
    	// 拿到beanFactory
    	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    	boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
    	Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
    	if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
    		BeanFactory parent = beanFactory.getParentBeanFactory();
    		Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
    							"Unable to use SearchStrategy.ANCESTORS");
    		beanFactory = (ConfigurableListableBeanFactory) parent;
    	}
    	MatchResult result = new MatchResult();
    	Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
    																  spec.getIgnoredTypes(), parameterizedContainers);
    	// 根据type从beanFactory取
    	for (String type : spec.getTypes()) {
    		Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
    															 parameterizedContainers);
    		Iterator<String> iterator = typeMatches.iterator();
    		while (iterator.hasNext()) {
    			String match = iterator.next();
    			if (beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match)) {
    				iterator.remove();
    			}
    		}
    		if (typeMatches.isEmpty()) {
    			result.recordUnmatchedType(type);
    		}
    		else {
    			result.recordMatchedType(type, typeMatches);
    		}
    	}
    	// 根据annotation从beanFactory取
    	for (String annotation : spec.getAnnotations()) {
    		Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
    																  considerHierarchy);
    		annotationMatches.removeAll(beansIgnoredByType);
    		if (annotationMatches.isEmpty()) {
    			result.recordUnmatchedAnnotation(annotation);
    		}
    		else {
    			result.recordMatchedAnnotation(annotation, annotationMatches);
    		}
    	}
    	// 根据beanName从beanFactory取
    	for (String beanName : spec.getNames()) {
    		if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
    			result.recordMatchedName(beanName);
    		}
    		else {
    			result.recordUnmatchedName(beanName);
    		}
    	}
    	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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    getMatchingBeans方法中会利用BeanFactory去获取指定类型的Bean,如果没有指定类型的Bean,则会将该类型记录在MatchResult对象的unmatchedTypes集合中,如果有该类型的Bean,则会把该Bean的beanName记录在MatchResult对象的matchedNames集合中,所以MatchResult对象中记录了,哪些类没有对应的Bean,哪些类有对应的Bean。

  • 相关阅读:
    c++课后作业
    Redis 数据一致性方案的分析与研究
    东北大学工程训练CNC加工中心(坤图)
    如何避免预读失效和缓存污染的问题?
    【C#】FileInfo类 对文件进行操作
    【C++】之多态最最最详细讲
    怎么选择好的游戏平台开发商?
    【算法与数据结构】--算法和数据结构的进阶主题--算法的优化和性能调优
    c++ I/O流
    C# 图解教程 第5版 —— 第11章 结构
  • 原文地址:https://blog.csdn.net/u022812849/article/details/126750109