@Conditional条件注解的原理

spring中存在各种条件注解,用于按条件生成bean,可以结合应用动态创建bean,扩展性非常好,常见有:

1. 核心条件注解(Spring Framework)

  • @Conditional:最底层的条件注解,需要自定义实现 Condition 接口。

2. Spring Boot 条件注解

2.1 按 Bean 条件

  • @ConditionalOnBean:容器中存在指定 Bean 时生效
  • @ConditionalOnMissingBean:容器中不存在指定 Bean 时生效

2.2 按 Class 条件

  • @ConditionalOnClass:classpath 中存在指定类时生效
  • @ConditionalOnMissingClass:classpath 中不存在指定类时生效

2.3 按配置属性条件

  • @ConditionalOnProperty:当配置文件中存在指定属性,并且值符合要求时生效

2.4 按资源条件

  • @ConditionalOnResource:classpath 下存在指定资源文件时生效

2.5 按 Web 环境条件

  • @ConditionalOnWebApplication:当前是 Web 应用环境时生效
  • @ConditionalOnNotWebApplication:当前不是 Web 应用环境时生效

2.6 按表达式 / Java 版本条件

  • @ConditionalOnExpression:基于 SpEL 表达式的结果决定是否生效
  • @ConditionalOnJava:基于 JVM 版本判断是否生效

2.7 按候选 Bean 条件

  • @ConditionalOnSingleCandidate:指定类型只有一个候选 Bean 或标记了 @Primary 时生效

2.8 按云平台条件(Spring Boot 2.0+)

  • @ConditionalOnCloudPlatform:基于 Cloud 平台环境判断(如 Heroku, Cloud Foundry)

所有的的条件注解继承了@Conditional,每个条件注解都指定了Condition接口的实现,处理条件注解的方法是shouldSkip,参数metadata表示注解元数据,包含注解的属性,phase表示条件判断的阶段。处理的逻辑是

  1. 如果注解不存在,则不用判断是否跳过,直接进行处理。
  2. 没有告知哪个阶段,按解析配置阶段算
  3. 提取bean的class上所有的Condition接口
  4. 调用每个Condition接口的matches方法判断是否处理
java 复制代码
	public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
        //没有@Condition注解默认需要处理
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}

		if (phase == null) {
            //是spring的配置类,则默认解析配置阶段
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
            //否则按注册bean阶段处理
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}
        //获取配置的Conditional接口
		List<Condition> conditions = new ArrayList<>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
            //如果Condition没有声明作用的阶段或作用的阶段和实际调用shouldSkip方法判断时一样,通过Condition接口判断
            //作用的阶段和实际调用shouldSkip方法判断时不一样,返回false表示需要处理
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
            //调用实现类的getMatchOutcome方法根据条件判断
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
......
	}    

下面以@ConditionalOnClassConditionalOnBean为例看下。

java 复制代码
@ConditionalOnClass({ Servlet.class, ServerContainer.class })
public class WebSocketServletAutoConfiguration {
....
}

WebSocketServletAutoConfiguration上存在@ConditionalOnClass注解,指定当Servlet.class, ServerContainer.class存在时,才解析WebSocketServletAutoConfiguration配置类。首先根据@ConditionalOnClass({ Servlet.class, ServerContainer.class })获取条件生效的类Servlet.class, ServerContainer.class的类路径字符串,根据类路径加载,如果加载不到就返回无法加载到的类,再检查是否存在类无法加载就行,最后返回ConditionOutcome表示测试结果。

java 复制代码
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
        //获取@ConditionalOnClass注解指定的类路径字符串
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
            //加载类,返回加载不到的类路径字符串
			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));
		}
		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));
		}
        //返回加载为true的ConditionOutcome
		return ConditionOutcome.match(matchMessage);
	}

springmvc的自动装配类CacheAutoConfiguration上存在@ConditionalOnBean,即bean CacheAspectSupport 存在时,才会解析CacheAutoConfiguration

java 复制代码
@ConditionalOnBean(CacheAspectSupport.class)
public class CacheAutoConfiguration {}

使用Spec封装配置类的@ConditionalOnBean条件bean的class,然后到容器中根据类型CacheAspectSupport搜索bean,如果找到则返回为true的ConditionOutcome。

java 复制代码
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ConditionMessage matchMessage = ConditionMessage.empty();
		MergedAnnotations annotations = metadata.getAnnotations();
		if (annotations.isPresent(ConditionalOnBean.class)) {
            //提取@ConditionalOnBean中声明的bean类型
			Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (!matchResult.isAllMatched()) {
				String reason = createOnBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(spec.message().because(reason));
			}
			matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
					matchResult.getNamesOfAllMatches());
		}
		if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
			Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (!matchResult.isAllMatched()) {
				return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
			}
			else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
					spec.getStrategy() == SearchStrategy.ALL)) {
				return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
						.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
			}
			matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,
					matchResult.getNamesOfAllMatches());
		}
		if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
			Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
					ConditionalOnMissingBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
			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);
	}

	protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
		ClassLoader classLoader = context.getClassLoader();
		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();
        //搜索忽略的bean名字
		Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
				spec.getIgnoredTypes(), parameterizedContainers);
		for (String type : spec.getTypes()) {
            //按类型搜索bean的名字
			Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
					parameterizedContainers);
            //从满足类型要求的bean中去掉要忽略的bean
			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);
			}
		}
		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);
			}
		}
		for (String beanName : spec.getNames()) {
			if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
				result.recordMatchedName(beanName);
			}
			else {
				result.recordUnmatchedName(beanName);
			}
		}
		return result;
	}

总结下,spring通过条件注解声明进行条件判断的Condition接口,然后根据接口的matches方法判断条件是否满足。有不对的地方请大神指出,欢迎大家一起讨论交流,共同进步,更多请关注微信公众号 葡萄开源

相关推荐
现在就干24 分钟前
Spring事务基础:你在入门时踩过的所有坑
java·后端
该用户已不存在37 分钟前
Gradle vs. Maven,Java 构建工具该用哪个?
java·后端·maven
JohnYan1 小时前
Bun技术评估 - 23 Glob
javascript·后端·bun
二闹1 小时前
聊天怕被老板发现?摩斯密码来帮你
后端·python
用户298698530141 小时前
# C#:删除 Word 中的页眉或页脚
后端
David爱编程1 小时前
happens-before 规则详解:JMM 中的有序性保障
java·后端
小张学习之旅1 小时前
ConcurrentHashMap
java·后端
PetterHillWater2 小时前
阿里Qoder的Quest小试牛刀
后端·aigc
程序猿阿伟2 小时前
《支付回调状态异常的溯源与架构级修复》
后端·架构
dreams_dream2 小时前
django错误记录
后端·python·django