在什么情况下,@ConditionalOnMissingBean条件注解会失效?

2026-04-11 03:512阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2556个文字,预计阅读时间需要11分钟。

在什么情况下,@ConditionalOnMissingBean条件注解会失效?

1+背景:为SpringBoot多模块项目创建鉴权拦截器(基于HandlerInterceptor实现)。基础模块中已创建鉴权拦截器。然而,一个子模块需要定制化实现鉴权,其鉴权流程与基础模块不一致。

1 背景

项目为SpringBoot多模块项目,基础模块中已经创建了鉴权拦截器(基于HandlerInterceptor实现)。
然而有一个子模块需要定制化实现鉴权,其鉴权流程与基础模块中的鉴权流程不相匹配,因此构思通过@ConditionalOnMissingBean实现定制化加载。
具体实现思路如下:

// Common模块 // 定义鉴权接口(基于`HandlerInterceptor`) public interface AuthInterceptor extends HandlerInterceptor { } // 定义默认的鉴权拦截器 @Slf4j @Configuration @ConditionalOnMissingBean(AuthInterceptor.class) public class DefaultAuthInterceptor implements AuthInterceptor { }

// 业务模块 @Slf4j @Configuration public class BusinessAuthInterceptor implements AuthInterceptor { }

此处,基本思路是:

  • 当业务模块中存在自定义的鉴权拦截器(实现AuthInterceptor),则默认的鉴权拦截器会因为@ConditionalOnMissingBean注解不再创建;
  • 当业务模块中不存在自定义鉴权拦截器,则默认的鉴权拦截器(DefaultAuthInterceptor)将创建并注入指IOC容器中。
2 BUG分析

提交完代码后,存在自定义鉴权拦截器的模块运行正常,然而期望使用默认鉴权拦截器的模块无法正常运行,具体表现为IOC容器未创建DefaultAuthInterceptor实例。因为代码中均是通过注解实现Bean的定义,因此重点关注ConfigurationClassPostProcessor的解析流程。

ConfigurationClassPostProcessor是一个BeanFactory的后置处理器,因此它的主要功能是参与BeanFactory的建造,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。

主要处理流程在public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) 函数中,此函数负责解析以及校验registry中的配置类。以下通过注释解析processConfigBeanDefinitions的具体执行流程。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); // 获取registry中存储的已经解析的beanNames String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { // 遍历beanNames,获取相应的BeanDefinition BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 校验BeanDefinition中的Attribute,确定此配置类是否已经被解析过了 if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { // 校验当前BeanDefinition是否满足配置类要求,如果是,则假如待处理配置类集合 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // 保存尚未解析的配置类 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); // 保存已经解析的配置类 Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); // 尝试对尚未解析的配置类集合进行解析(此处逻辑比较复杂,后续展开) parser.parse(candidates); // 此处的校验规则: // 如果配置类被@Configuration修饰,且proxyBeanMethods属性设置为true,则当前类不能被final修饰; // 并且被@Bean修饰的方法,必须允许被覆盖(@Override),因为proxyBeanMethods被设置为true,斯需要通过cglib进行代理 parser.validate(); // 需要注意,在未包含定制化鉴权拦截器的模块中,registry包含DefaultAuthInterceptor的BeanDefinition,beanName为defaultAuthInterceptor // 保存解析出来的配置类 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); // 去除已经被解析的配置类,避免重复解析 configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // 根据解析出来的配置类,加载相应的BeanDefinition // 需要注意的是,此处实际上是对解析出来的BeanDefinition进行再次校验,其中就包含针对@Conditional相关注解的校验 // 针对此函数,在下文会进行具体的解析 this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end(); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }

此处重点解析ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(Set<ConfigurationClass> configurationModel)函数执行流程的解析:

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { // 创建TrackedConditionEvaluator实例,将由其判断是否 TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } 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) { // 当前配置类没有被校验,进入校验流程 // Return whether this configuration class was registered via @Import or automatically registered due to being nested within another configuration class. (此处未研究过,掠过) 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) { // 此处根据配置类是否包含@Conditional注解,进行校验 // 此处在后续展开讲解 skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN); } this.skipped.put(configClass, skip); } return skip; } } // 针对包含@Conditional注解的配置类,进行校验,确定是否需要将其从registry注册的beanDefinitions中进行移除 public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } if (phase == null) { if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } 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(); } // 当前函数前序都是进行铺垫,此处进行真正的匹配工作 // 如果匹配失败,则当前类需要从registry中进行移除,无法注册至最终的IOC容器中 // matches的实现在SpringBootCondition类中,后续文章将对其进行解析 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }

探究SpringBootConditionmatches函数执行流程。

@Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { // 根据Condition上下文,判断是否满足匹配要求,输出匹配结果 ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); 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); } } // getMatchOutcome的具体实现详见OnBeanCondition类 // class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty(); MergedAnnotations annotations = metadata.getAnnotations(); if (annotations.isPresent(ConditionalOnBean.class)) { 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()); } Set<String> allBeans = matchResult.getNamesOfAllMatches(); if (allBeans.size() == 1) { 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()) { return ConditionOutcome.noMatch( spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans)); } if (primaryBeans.size() > 1) { return ConditionOutcome .noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans)); } matchMessage = spec.message(matchMessage) .found("a single primary bean '" + primaryBeans.get(0) + "' from beans") .items(Style.QUOTE, allBeans); } } // 处理逻辑在此处 if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnMissingBean.class); // BUG的原因根由在此处,前序解析BeanDefinition时将DefaultAuthInterceptor的BeanDefinition添加至registry中 // 此处在匹配的时候,发现已经存在DefaultAuthInterceptor的BeanDefinition,因此不满足@ConditionalOnMissingBean的条件 // 因此匹配的记过为false,执行完此流程之后,上层函数中将从registry中删除DefaultAuthInterceptor的BeanDefinition // 这就相应的导致在未实现自定义AuthInterceptor的模块中,报出AuthInterceptor相应Bean缺失的错误 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); }

关于BUG出现的具体缘由,在上文的代码注释中已经给出。

3 复盘

出现此BUG,是因为对@ConditionalOnMissingBean的使用不清晰,在其对应的类型注释中已经给出了推荐的使用方式:

/** * {@link Conditional @Conditional} that only matches when no beans meeting the specified * requirements are already contained in the {@link BeanFactory}. None of the requirements * must be met for the condition to match and the requirements do not have to be met by * the same bean. * <p> * When placed on a {@code @Bean} method, the bean class defaults to the return type of * the factory method: * * <pre class="code"> * &#064;Configuration * public class MyAutoConfiguration { * * &#064;ConditionalOnMissingBean * &#064;Bean * public MyService myService() { * ... * } * * }</pre> * <p> * In the sample above the condition will match if no bean of type {@code MyService} is * already contained in the {@link BeanFactory}. * <p> * The condition can only match the bean definitions that have been processed by the * application context so far and, as such, it is strongly recommended to use this * condition on auto-configuration classes only. If a candidate bean may be created by * another auto-configuration, make sure that the one using this condition runs after. * * @author Phillip Webb * @author Andy Wilkinson * @since 1.0.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnMissingBean

此处推荐的是在自动化配置类内部通过@Bean创建对应的bean实例,在@Bean修饰的方法上添加@ConditionalOnMissingBean注解。

在什么情况下,@ConditionalOnMissingBean条件注解会失效?

4 解决

@Configuration public class CommonAuthAutoConfiguration { @Bean @ConditionalOnMissingBean(AuthInterceptor.class) public AuthInterceptor defaultAuthInterceptor() { return new DefaultAuthInterceptor(); } @Bean public WebMvcConfigurer commonAuthMvcConfigurer(AuthInterceptor authInterceptor) { return new CommonAuthMvcConfigurer(authInterceptor); } }

按照@ConditionalOnMissingBean注释推荐的方式,使用自动化配置类即可解决此问题。

5 拓展

按照@ConditionalOnMissingBean的注释,当有多个自动化配置类,均会创建某一类型的Bean时,如何按照顺序创建就是一个复杂的问题。

关于此问题,现在的思路是结合spring.factories指定Bean之间创建的顺序关系。

本文共计2556个文字,预计阅读时间需要11分钟。

在什么情况下,@ConditionalOnMissingBean条件注解会失效?

1+背景:为SpringBoot多模块项目创建鉴权拦截器(基于HandlerInterceptor实现)。基础模块中已创建鉴权拦截器。然而,一个子模块需要定制化实现鉴权,其鉴权流程与基础模块不一致。

1 背景

项目为SpringBoot多模块项目,基础模块中已经创建了鉴权拦截器(基于HandlerInterceptor实现)。
然而有一个子模块需要定制化实现鉴权,其鉴权流程与基础模块中的鉴权流程不相匹配,因此构思通过@ConditionalOnMissingBean实现定制化加载。
具体实现思路如下:

// Common模块 // 定义鉴权接口(基于`HandlerInterceptor`) public interface AuthInterceptor extends HandlerInterceptor { } // 定义默认的鉴权拦截器 @Slf4j @Configuration @ConditionalOnMissingBean(AuthInterceptor.class) public class DefaultAuthInterceptor implements AuthInterceptor { }

// 业务模块 @Slf4j @Configuration public class BusinessAuthInterceptor implements AuthInterceptor { }

此处,基本思路是:

  • 当业务模块中存在自定义的鉴权拦截器(实现AuthInterceptor),则默认的鉴权拦截器会因为@ConditionalOnMissingBean注解不再创建;
  • 当业务模块中不存在自定义鉴权拦截器,则默认的鉴权拦截器(DefaultAuthInterceptor)将创建并注入指IOC容器中。
2 BUG分析

提交完代码后,存在自定义鉴权拦截器的模块运行正常,然而期望使用默认鉴权拦截器的模块无法正常运行,具体表现为IOC容器未创建DefaultAuthInterceptor实例。因为代码中均是通过注解实现Bean的定义,因此重点关注ConfigurationClassPostProcessor的解析流程。

ConfigurationClassPostProcessor是一个BeanFactory的后置处理器,因此它的主要功能是参与BeanFactory的建造,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。

主要处理流程在public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) 函数中,此函数负责解析以及校验registry中的配置类。以下通过注释解析processConfigBeanDefinitions的具体执行流程。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); // 获取registry中存储的已经解析的beanNames String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { // 遍历beanNames,获取相应的BeanDefinition BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 校验BeanDefinition中的Attribute,确定此配置类是否已经被解析过了 if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { // 校验当前BeanDefinition是否满足配置类要求,如果是,则假如待处理配置类集合 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // 保存尚未解析的配置类 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); // 保存已经解析的配置类 Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); // 尝试对尚未解析的配置类集合进行解析(此处逻辑比较复杂,后续展开) parser.parse(candidates); // 此处的校验规则: // 如果配置类被@Configuration修饰,且proxyBeanMethods属性设置为true,则当前类不能被final修饰; // 并且被@Bean修饰的方法,必须允许被覆盖(@Override),因为proxyBeanMethods被设置为true,斯需要通过cglib进行代理 parser.validate(); // 需要注意,在未包含定制化鉴权拦截器的模块中,registry包含DefaultAuthInterceptor的BeanDefinition,beanName为defaultAuthInterceptor // 保存解析出来的配置类 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); // 去除已经被解析的配置类,避免重复解析 configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // 根据解析出来的配置类,加载相应的BeanDefinition // 需要注意的是,此处实际上是对解析出来的BeanDefinition进行再次校验,其中就包含针对@Conditional相关注解的校验 // 针对此函数,在下文会进行具体的解析 this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end(); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }

此处重点解析ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(Set<ConfigurationClass> configurationModel)函数执行流程的解析:

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { // 创建TrackedConditionEvaluator实例,将由其判断是否 TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } 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) { // 当前配置类没有被校验,进入校验流程 // Return whether this configuration class was registered via @Import or automatically registered due to being nested within another configuration class. (此处未研究过,掠过) 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) { // 此处根据配置类是否包含@Conditional注解,进行校验 // 此处在后续展开讲解 skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN); } this.skipped.put(configClass, skip); } return skip; } } // 针对包含@Conditional注解的配置类,进行校验,确定是否需要将其从registry注册的beanDefinitions中进行移除 public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } if (phase == null) { if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } 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(); } // 当前函数前序都是进行铺垫,此处进行真正的匹配工作 // 如果匹配失败,则当前类需要从registry中进行移除,无法注册至最终的IOC容器中 // matches的实现在SpringBootCondition类中,后续文章将对其进行解析 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }

探究SpringBootConditionmatches函数执行流程。

@Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { // 根据Condition上下文,判断是否满足匹配要求,输出匹配结果 ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); 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); } } // getMatchOutcome的具体实现详见OnBeanCondition类 // class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty(); MergedAnnotations annotations = metadata.getAnnotations(); if (annotations.isPresent(ConditionalOnBean.class)) { 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()); } Set<String> allBeans = matchResult.getNamesOfAllMatches(); if (allBeans.size() == 1) { 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()) { return ConditionOutcome.noMatch( spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans)); } if (primaryBeans.size() > 1) { return ConditionOutcome .noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans)); } matchMessage = spec.message(matchMessage) .found("a single primary bean '" + primaryBeans.get(0) + "' from beans") .items(Style.QUOTE, allBeans); } } // 处理逻辑在此处 if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnMissingBean.class); // BUG的原因根由在此处,前序解析BeanDefinition时将DefaultAuthInterceptor的BeanDefinition添加至registry中 // 此处在匹配的时候,发现已经存在DefaultAuthInterceptor的BeanDefinition,因此不满足@ConditionalOnMissingBean的条件 // 因此匹配的记过为false,执行完此流程之后,上层函数中将从registry中删除DefaultAuthInterceptor的BeanDefinition // 这就相应的导致在未实现自定义AuthInterceptor的模块中,报出AuthInterceptor相应Bean缺失的错误 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); }

关于BUG出现的具体缘由,在上文的代码注释中已经给出。

3 复盘

出现此BUG,是因为对@ConditionalOnMissingBean的使用不清晰,在其对应的类型注释中已经给出了推荐的使用方式:

/** * {@link Conditional @Conditional} that only matches when no beans meeting the specified * requirements are already contained in the {@link BeanFactory}. None of the requirements * must be met for the condition to match and the requirements do not have to be met by * the same bean. * <p> * When placed on a {@code @Bean} method, the bean class defaults to the return type of * the factory method: * * <pre class="code"> * &#064;Configuration * public class MyAutoConfiguration { * * &#064;ConditionalOnMissingBean * &#064;Bean * public MyService myService() { * ... * } * * }</pre> * <p> * In the sample above the condition will match if no bean of type {@code MyService} is * already contained in the {@link BeanFactory}. * <p> * The condition can only match the bean definitions that have been processed by the * application context so far and, as such, it is strongly recommended to use this * condition on auto-configuration classes only. If a candidate bean may be created by * another auto-configuration, make sure that the one using this condition runs after. * * @author Phillip Webb * @author Andy Wilkinson * @since 1.0.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnMissingBean

此处推荐的是在自动化配置类内部通过@Bean创建对应的bean实例,在@Bean修饰的方法上添加@ConditionalOnMissingBean注解。

在什么情况下,@ConditionalOnMissingBean条件注解会失效?

4 解决

@Configuration public class CommonAuthAutoConfiguration { @Bean @ConditionalOnMissingBean(AuthInterceptor.class) public AuthInterceptor defaultAuthInterceptor() { return new DefaultAuthInterceptor(); } @Bean public WebMvcConfigurer commonAuthMvcConfigurer(AuthInterceptor authInterceptor) { return new CommonAuthMvcConfigurer(authInterceptor); } }

按照@ConditionalOnMissingBean注释推荐的方式,使用自动化配置类即可解决此问题。

5 拓展

按照@ConditionalOnMissingBean的注释,当有多个自动化配置类,均会创建某一类型的Bean时,如何按照顺序创建就是一个复杂的问题。

关于此问题,现在的思路是结合spring.factories指定Bean之间创建的顺序关系。