一、背景描述
在项目中通过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本质是是一个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