为什么@Conditional会失效?

2023年 8月 7日 58.3k 0

一、背景描述

在项目中通过cat上报java对redis相关操作,从而监控redis命令操作的监控指标,在基础组件中写了如下配置:

@Configuration
@ConditionalOnClass({Cat.class,RedisOperations.class})
@Slf4j
public class CatRedisAutoConfiguration {
    /**
     * redis拦截上报
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @ConditionalOnBean(RedisConnectionFactory.class)
    @ConditionalOnMissingBean
    public RedisAopService redisAopService(){
        log.info("CatAutoConfiguration init RedisAopService...");
        return new RedisAopService();
    }
}

业务项目添加该配置后,启动项目发现RedisAopService并没有注入进去,redis相关操作并没有上报,怀疑是条件注解失效导致的问题。

二、常见条件注解失效场景

springboot中常见的条件注解有:

  • @ConditionalOnClass:当类路径中存在指定的类时,条件才会成立。
  • @ConditionalOnMissingClass:当类路径中不存在指定的类时,条件才会成立。
  • @ConditionalOnBean:当容器中存在指定的 Bean 时,条件才会成立。
  • @ConditionalOnMissingBean:当容器中不存在指定的 Bean 时,条件才会成立。
  • @ConditionalOnProperty:当指定的属性在配置文件中被设置为特定的值时,条件才会成立。
  • @ConditionalOnExpression:通过 SpEL 表达式来判断条件是否成立。
  • @ConditionalOnWebApplication:当是一个 Web 应用程序时,条件才会成立。
  • @ConditionalOnNotWebApplication:当不是一个 Web 应用程序时,条件才会成立。

这些条件注解也都是基于@Conditional实现,@Conditional 注解用于根据特定的条件来决定是否启用或禁用某个组件或配置。它可以应用于类、方法或配置类上。当条件不满足时,被 @Conditional 注解标记的组件或配置将被忽略,不会被加载到 Spring 容器中。以下常见情况下,@Conditional注解可能会失效:

  • 条件表达式始终返回 false:如果条件表达式的逻辑判断始终返回 false,那么被 @Conditional 注解标记的组件或配置将不会生效,无论条件是否满足。
  • 条件依赖的Bean未被正确注入:在定义条件注解时,如果条件依赖某个 Bean 的存在或属性值,但这个 Bean 在运行时未被正确注入,那么条件判断可能会失效。
  • 条件依赖的class未被加载:在条件注解依赖的class,未被引入或者由于版本冲突未被正确加载,也会导致条件注解失效。
  • 条件不存在或配置错误:如果自定义的条件类或条件判断方法存在问题,或者配置了不存在的条件类,那么条件判断也可能失效。
  • 条件不在正确的上下文中生效:有些条件注解只在特定的上下文环境下才会生效,例如 @ConditionalOnWebApplication 只在 Web 应用上下文中生效。如果将这样的条件注解应用在非对应的上下文环境中,条件判断也会失效。
  • Bean注入顺序问题:条件注解依赖的bean在条件注解生效判断时,还没有被注册成BeanDefination,但是最终会被注册进来,导致条件注解失效。

三、聊一聊条件注解实现原理

从之前的两篇文章《ConfigurationClassPostProcessor原理详解》和《springboot自动装配原理》中可以了解到配置类的解析和加载成BeanDefination都是由ConfigurationClassPostProcessor完成。
我们选择@ConditionalOnBean为例,分析一下springboot条件注解的视线原理,看一下@ConditionalOnBean实现:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

	Class[] value() default {};

	String[] type() default {};

	Class[] parameterizedContainer() default {};
}

该注解依赖@Conditional注解,并且依赖OnBeanCondition.class,一般常用到的是value属性,也就是依赖的bean。
@ConditionalOnBean的生效依赖OnBeanCondition,看一下其继承关系
OnBeanCondition.png
OnBeanCondition本质是是一个Condition,并且继承了SpringBootCondition拥有一些条件注解的通用能力,并且拥有其他一些工具能力。它的核心方法是实现SpringBootCondition定义的getMatchOutcome�方法,看一下方法实现:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	ConditionMessage matchMessage = ConditionMessage.empty();
	MergedAnnotations annotations = metadata.getAnnotations();
	if (annotations.isPresent(ConditionalOnBean.class)) {
		Spec 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 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 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);
}

该方法分别支持@ConditionalOnBean、@ConditionalOnSingleCandidate和@ConditionalOnMissingBean三个条件注解的逻辑判定,继续分析@ConditionalOnBean,就是检查容器中是否有符合条件的bean。会继续调用getMatchingBeans方法实现:

protected final MatchResult getMatchingBeans(ConditionContext context, Spec spec) {
ClassLoader classLoader = context.getClassLoader();
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
Set

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论