@Conditional条件注解源码分析

概念和使用规则

过滤-配置类生效的方式

@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
@ConditionalOnMissingClass : classpath中不存在该类时起效
@ConditionalOnBean : DI容器中存在该类型Bean时起效
@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
@ConditionalOnExpression : SpEL表达式结果为true时
@ConditionalOnProperty : 参数设置或者值一致时起效
@ConditionalOnResource : 指定的文件存在时起效
@ConditionalOnJndi : 指定的JNDI存在时起效
@ConditionalOnJava : 指定的Java版本存在时起效
@ConditionalOnWebApplication : Web应用环境下起效
@ConditionalOnNotWebApplication : 非Web应用环境下起效
=======
@Conditional :自定义实现条件注入

uml类图

在这里插入图片描述

时序图说明

此图说明了 @ConditionalOnClass、@ConditionalOnBean等待在整个spring容器中校验的时机

在这里插入图片描述

如何自定义注入

如何基于@Conditional实现自定义注入呢

@Component
public class ConditionalTest {

    @Component
    @ConditionalOnClass(DataSource.class)
    @Conditional(ConfigurationTest.EmbeddedDatabaseCondition.class)
    protected static class EmbeddedDatabaseConfiguration {
    }
    static class EmbeddedDatabaseCondition extends SpringBootCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
           return new ConditionOutcome(true, (String) null);
        }
    }
}

源码分析

如上面自定义注入案例,在11行断点

堆栈直接定位到这里

shouldSkip

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

public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
		return shouldSkip(metadata, null);
	}
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    	// 判断是否包含@Conditional注解,像spring自带的注解@ConditionalOnClass等,都是@Conditional注解
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}

        // 阶段给个默认值
		if (phase == null) {
            // 判断当前元数据是否包含 @Bean\@Compnent\@ComponentScan\@Import\@ImportResource  注解
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                // 给phase一个默认值PARSE_CONFIGURATION,再递归一下
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}

		List<Condition> conditions = new ArrayList<>();
   		// 解析当前bean上 所有的@Conditional注解的class(包含: @ConditionalOnClass、@ConditionalOnBean)
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
                // 根据class实例化
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

     	// 排序
		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
            // 根据上面类图可得只有 @OnBeanCondition 实现了 ConfigurationCondition
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
            /**
            条件1、
            	这里经过反复debug发现当前bean的元数据包含@ComponentScan注解时,
                phase才会=REGISTER_BEAN,
                其他情况时 phase都是null,然后在上面11行代码里面初始化 PARSE_CONFIGURATION 执行
                所以这里的 requiredPhase ,差不多都是PARSE_CONFIGURATION
                
                当然也有 REGISTER_BEAN 的情况,具体堆栈:ConfigurationClassParser#doProcessConfigurationClass:286行
            条件2、
                condition.matches 会默认走到 SpringBootCondition.matches 仅有SpringBootCondition实现此方法
            */
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

shouldSkip时机(思考)

可能不会有人思考这俩枚举值有啥不一样的作用?

  • ConfigurationPhase.PARSE_CONFIGURATION
  • ConfigurationPhase.REGISTER_BEAN

spring 虽然搞了很多条件注解,但是总的分为俩类

  • 基于Bean(spring容器的bean)的条件:@ConditionalOnBean
  • 基于其他的匹配:@ConditionalOnProperty、ConditionalOnResource、@ConditionalOnClass

因为在注册beanDefinition之前无法校验spring的bean,
所以要根据注册beanDefinition之前,和注册beanDefinition之后进行分开校验。

注册BeanDefinition之前

org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
注意这里默认是启动类,因为@ComponentScan 注解默认在启动类上面,当然如果再加一个@ComponentScan 注解也能走到这里
但是这里是 registerBeanDefinition 之前的校验

doScan

直接定位到doScan这里
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents

isCandidateComponent

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.core.type.classreading.MetadataReader)

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
                // 执行这里
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

	private boolean isConditionMatch(MetadataReader metadataReader) {
		if (this.conditionEvaluator == null) {
			this.conditionEvaluator =
					new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
		}
		return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
	}


image.png

注册BeanDefinition之后

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
image.png

loadBeanDefinitions

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions

	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        // 创建 requiredPhase 的条件执行器,默认只有 OnBeanCondition 会生效
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
		// requiredPhase 执行 OnBeanCondition类的校验
		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}

		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}


private class TrackedConditionEvaluator {

		private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();

		public boolean shouldSkip(ConfigurationClass configClass) {
			Boolean skip = this.skipped.get(configClass);
			if (skip == null) {
				if (configClass.isImported()) {
					boolean allSkipped = true;
					for (ConfigurationClass importedBy : configClass.getImportedBy()) {
						if (!shouldSkip(importedBy)) {
							allSkipped = false;
							break;
						}
					}
					if (allSkipped) {
						// The config classes that imported this one were all skipped, therefore we are skipped...
						skip = true;
					}
				}
				if (skip == null) {
					skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
				}
				this.skipped.put(configClass, skip);
			}
			return skip;
		}
	}

matches

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

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
            /*
            这里会根据this动态去执行:
            
            OnBeanCondition           ==》@ConditionalOnMissingBean、@ConditionalOnBean、@ConditionalOnSingleCandidate
            OnClassCondition          ==》@ConditionalOnClass、@ConditionalOnMissingClass
            OnWebApplicationCondition ==》@ConditionalOnWebApplication
            ||
            ==> getMatchOutcome
            */
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
            // 打印日志
			logOutcome(classOrMethodName, outcome);
            // 用来记录自动化配置过程中条件匹配的详细信息及日志信息
            // 在完成finishRefresh时会在ConditionEvaluationReportLoggingListener监听消息打印日志
			recordEvaluation(context, classOrMethodName, outcome);

            // 返回 OnBeanCondition、OnClassCondition、OnClassCondition 匹配后返回的结果
			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);
		}
	}

getMatchOutcome

OnBeanCondition

案例
image.png
直接定位到具体判断的地方
org.springframework.boot.autoconfigure.condition.OnBeanCondition#containsBean
org.springframework.beans.factory.support.DefaultListableBeanFactory#containsBeanDefinition
image.png

OnClassCondition

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

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 获取当前线程的类加载器是Launcher$AppClassLoader
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
        /*
        todo *****************************这一步没看懂具体是咋解析的,有空再研究一下 *****************************
        这一步是获取当前注解上的值,
        如: @ConditionalOnClass(DataSource.class) 解析出 javax.sql.DataSource
        */
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
           /*
            这一步是精髓了,用classLoader.loadClass(className);检测当前类加载器中是否存在指定class文件
            ClassNameFilter.MISSING 检测丢失的
            ClassNameFilter.PRESENT 检测存在的
           */
			List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
			if (!missing.isEmpty()) {
                // 如果不存在写入日志
                // 且标记 ConditionOutcome.match=true
				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));
		}
        // 对@ConditionalOnMissingClass 注解的解析与@ConditionalOnClass逻辑是相反的
		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));
		}

        // 走到这里标记 ConditionOutcome.match=true
		return ConditionOutcome.match(matchMessage);
	}

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#filter


abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
    protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
                                        ClassLoader classLoader) {
        if (CollectionUtils.isEmpty(classNames)) {
            return Collections.emptyList();
        }
        List<String> matches = new ArrayList<>(classNames.size());
        for (String candidate : classNames) {
            if (classNameFilter.matches(candidate, classLoader)) {
                matches.add(candidate);
            }
        }
        return matches;
    }

    protected enum ClassNameFilter {

        PRESENT {

            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return isPresent(className, classLoader);
            }

        },

        MISSING {

            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
            }

        };

        abstract boolean matches(String className, ClassLoader classLoader);

        static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }
            try {
                resolve(className, classLoader);
                return true;
            }
            catch (Throwable ex) {
                return false;
            }
        }
    }
    protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
        if (classLoader != null) {
            return classLoader.loadClass(className);
        }
        return Class.forName(className);
    }

}           


OnWebApplicationCondition
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值