SpringBoot中@ConditionalOnBean实现原理

本文详细解析了SpringBoot中条件注解的工作原理,特别是@ConditionalOnBean的执行时机和影响,以及如何避免无效类的扫描,确保配置类按预期加载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 在SpringBoot1.5.X时,判断条件是OR,SpringBoot2.x变为了AND

在spring 启动过程中,会调用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry后置处理器 调用ComponentScanAnnotationParser的parse(..)方法解析配置类,实现类路径下的类文件扫描,

遍历被扫描到的类,并通过@ComponentScan注解上指定的规则进行匹配,默认情况下,SpringBoot扫描@Component标记的类,( 但是在spring.factories中通过key org.springframework.boot.autoconfigure.EnableAutoConfiguration指定配置不会被扫描. 这些配置类会由@EnableAutoConfiguration导入的AutoConfigurationImportSelector来处理);

如果出现匹配的类还会通过ConditionEvaluator.shouldSkip(...)来判断是否忽略这个类,这时候会过滤掉小部分无效类,但是不会在这个期间应用@ConditionalOnBean去验证,

因为其验证实现类OnBeanCondition实现了接口ConfigurationCondition的getConfigurationPhase()方法,该方法返回值是一个枚举值,用于判断什么时期应用这个条件验证, shouldSkip()方法会判断这个接口返回值,OnBeanCondition实现的getConfigurationPhase()返回的是ConfigurationPhase.REGISTER_BEAN,而类扫描期间只会执行没有实现ConfigurationCondition接口,或返回值为null,或为ConfigurationPhase.PARSE_CONFIGURATION的验证器.

所以这个期间不会应用@ConditionalOnBean去验证. 这也可以理解因为这个时候有很多类没有被遍历到,现在执行OnBeanCondition验证肯定多数是false. 这也导致很多最终不会被实例化为Spring bean的类被包装成BeanDefinition(实现类ScannedGenericBeanDefinition ),并注册在DefaultListableBeanFactory 的成员beanDefinitionMap中,key为Bean的名称,同时Bean的名称会保存在List集合beanDefinitionNames中, 对于一些手动注册的Bean的名称会保存在List集合manualSingletonNames中,

然后对于parse(..)返回的BeanDefinition集合会遍历调用ConfigurationClassParser的parse(...)递归调用, 当解析到配置中有@Bean定义的Bean,并有条件注解时,会调用ConditionEvaluator.shouldSkip(...)方法,最终调用到的是 Condition实现类的matches(...)方法. 而@ConditionalOnBean注解中的验证实现类OnBeanCondition,最终判断是否有指定的bean就是通过DefaultListableBeanFactory 中的beanDefinitionMap和manualSingletonNames是否包含指定的Bean的名称来验证的.

当然对于扫描到的无效类,在ConfigurationClassPostProcessor的processConfigBeanDefinitions(...)调用parse()方法后会调用this.reader.loadBeanDefinitions(configClasses),来判断最终有效的BeanDefinition,这时候就会执行OnBeanCondition, 移除beanDefinitionMap中无效的BeanDefinition. 

  •  鉴于类路径扫描规则和OnBeanCondition验证实现原理,就可能导致无效问题

例如如下代码: C希望不存在A Bean时创建,B希望在C bean存在时创建,示例中A配置了Bean,这时B C都不应该创建,但执行结果是C确实没有创建,而B却被创建了. 原因就在于类扫描时,C被扫描了,导致beanDefinitionMap存在C 的BeanDefinition.但是C在this.reader.loadBeanDefinitions(configClasses),中由于条件验证不通过没有创建被移除.(当然和类扫描顺序也有关系,如果B能够在C之后被扫描到,就会执行正常,所以Spring官网建议通过spring.factories来配置,指定执行顺序或者在能够确定执行顺序的情况下使用@ConditionalOnBean,但是一定不能通过文件名来控制顺序)

注意点: 如果将示例中的类配置到spring.factories中即时不指定顺序,也能正常执行,不会创建B C,这是由于spring.factories配置文件中的类并不是全部包装成BeanDefinition然后判断是否有效的,而是排序后封装成ConfigurationClass通过遍历的方式,同样是在this.reader.loadBeanDefinitions(configClasses)方法中判断该类是否通过条件验证是否有效.当执行到B是,C还没有被处理,所以导致B也不会被创建,(这有点属于巧合导致的正确结果,所以为了安全还是要执行顺序,让B在C之后被处理)

  • 第二种情况, 在配置类中定义Bean,如果使用@ConditionalOnBean依赖的也是配置类中Bean,则执行结果不可控,和配置类加载顺序有关。示例代码如下:代码中是希望Bean3被创建,但是实际上并没有!!!

解决有两种方式:

1 可以考虑使用@DependsOn来解决,参看Spring中@DependsOn注解的作用及实现原理

2 如果一定要区分两个配置类的先后顺序,可以将这两个类交与EnableAutoConfiguration管理和触发。 也就是定义在META-INF\spring.factories中声明是配置类, 然后通过@AutoConfigureBefore、AutoConfigureAfter、AutoConfigureOrder控制先后顺序。 因为这三个注解只对自动配置类生效。

<think>嗯,用户提出了一个关于Spring Boot中ErrorMvcAutoConfiguration的问题,具体涉及DefaultErrorViewResolverConfiguration和DispatcherServlet的条件关联问题。从用户的问题描述看,ta可能在启动应用时遇到了与错误视图解析相关的配置异常,比如ConventionErrorViewResolver未被正确创建,或者与DispatcherServlet的条件依赖出现了冲突。 用户可能正在调试一个Spring Boot应用,突然在启动日志里看到"No qualifying bean of type 'org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver'"之类的错误。这种问题通常发生在DispatcherServlet未被正确初始化时,而用户可能已经检查了@ConditionalOnBean(DispatcherServlet.class)这个条件,但依然不明白为什么自己的配置会失效。 让我拆解一下这个问题。首先ErrorMvcAutoConfiguration是Spring Boot错误处理的核心自动配置类,其中DefaultErrorViewResolverConfiguration内部类负责注册ConventionErrorViewResolver。关键点在于这个内部类标注了@ConditionalOnBean(DispatcherServlet.class),意味着只有在DispatcherServlet存在时才会生效。但用户似乎遇到了这个条件不满足的情况。 根据引用[2]的内容,DispatcherServlet的自动配置依赖于三个前提:项目类型必须是SERVLET,classpath中存在Servlet相关类,且没有手动注册WebMvcConfigurationSupport。用户的问题很可能出在第三个条件——如果ta手动声明了WebMvcConfigurationSupport(比如通过@EnableWebMvc),就会完全禁用Spring Boot的WebMVC自动配置,自然也包括DispatcherServletAutoConfiguration。 另一个排查方向是启动顺序。引用[2]提到WebMvcAutoConfiguration要在DispatcherServletAutoConfiguration之后加载。如果用户自定义的配置类意外提前加载了WebMvc相关组件,可能会破坏这个依赖链条。此时建议用@AutoConfigureOrder调整顺序。 还有种可能是用户把应用错误配置成了REACTIVE类型(WebApplicationType.REACTIVE),但实际使用的是Servlet技术栈。这时所有Servlet相关的自动配置包括DispatcherServlet都会被跳过。 解决方案上,首先应该让用户检查是否误用了@EnableWebMvc——这个注解会接管整个MVC配置。如果不需要完全自定义MVC,就应该移除它。其次检查主配置类上的@SpringBootApplication是否完整覆盖了包路径。最后查看是否有自定义的WebMvcConfigurer或ViewResolver干扰了默认配置。 考虑到用户可能还需要进一步验证,我在回复里提供了两个实用建议:在日志中搜索"DispatcherServlet : initialization completed"确认其是否加载(引用[4]有类似日志模板),以及用Arthas工具检查DispatcherServlet实例是否存在(引用[5]展示了如何用Arthas追踪视图解析过程)。这些实操性建议应该能帮助用户快速定位问题根源。 最后生成的相关问题覆盖了典型延伸场景:条件注解的工作原理(用户显然需要深入理解@Conditional机制)、手动注册DispatcherServlet的后果(常见于从传统Spring迁移的项目)、错误页面的自定义方式(这是ErrorMvcAutoConfiguration的核心功能),以及日志分析技巧(用户当前最需要的调试手段)。这样既解决了当前报错,又预判了用户后续可能的需求。</think>以下是针对 Spring Boot 中 `ErrorMvcAutoConfiguration` 的 `DefaultErrorViewResolverConfiguration` 与 `DispatcherServlet` 条件依赖问题的解析: --- ### 问题原因 `ErrorMvcAutoConfiguration.DefaultErrorViewResolverConfiguration` 内部类负责注册 `ConventionErrorViewResolver`,但其生效依赖以下条件: ```java @ConditionalOnBean(DispatcherServlet.class) // 关键条件 @ConditionalOnWebApplication(type = Type.SERVLET) ``` 若应用启动时未满足 `@ConditionalOnBean(DispatcherServlet.class)` 条件,`ConventionErrorViewResolver` 将不会被创建,导致错误视图解析失败。 --- ### 常见触发场景 1. **手动禁用自动配置** 在 `@SpringBootApplication` 中排除 `DispatcherServletAutoConfiguration`: ```java @SpringBootApplication(exclude = DispatcherServletAutoConfiguration.class) ``` 导致 `DispatcherServlet` 未被创建[^2]。 2. **自定义 `WebMvcConfigurationSupport`** 若手动注册了 `WebMvcConfigurationSupport` 类型的 Bean(如通过 `@EnableWebMvc`),会 **完全禁用** `WebMvcAutoConfiguration`,连带导致 `DispatcherServletAutoConfiguration` 失效[^2]。 3. **项目类型不符** 应用类型非 `SERVLET`(如设置为 `REACTIVE`),导致 `DispatcherServlet` 相关配置被跳过。 4. **依赖缺失** Classpath 中缺少 Servlet API 或 `DispatcherServlet` 类(如未引入 `spring-boot-starter-web`)。 --- ### 解决方案 #### 1. 检查依赖配置 确保 `pom.xml` 包含必要依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` #### 2. 检查自动配置排除项 移除对 `DispatcherServletAutoConfiguration` 的排除: ```java // 错误示例(移除 exclude 项) @SpringBootApplication public class MyApp { ... } ``` #### 3. 避免误用 `@EnableWebMvc` 除非需要完全自定义 MVC 配置,否则**不要添加** `@EnableWebMvc` 注解。该注解会注册 `WebMvcConfigurationSupport`,禁用默认自动配置[^2]。 #### 4. 验证应用类型 在 `application.properties` 中显式指定类型: ```properties spring.main.web-application-type=servlet ``` #### 5. 调试条件注解 启用自动配置报告,查看 `DispatcherServlet` 条件状态: ```properties # application.properties debug=true ``` 启动日志中将输出类似信息: ```log DispatcherServletAutoConfiguration matched: - @ConditionalOnClass found required class 'javax.servlet.Servlet' (OnClassCondition) - Found 'session' scope (OnWebApplicationCondition) ``` --- ### 核心机制总结 | 组件 | 生效条件 | 依赖关系 | |------|----------|----------| | `DispatcherServletAutoConfiguration` | Classpath 存在 `Servlet` 和 `DispatcherServlet` | 基础 Servlet 容器配置 | | `ErrorMvcAutoConfiguration` | `DispatcherServlet` 存在且项目类型为 `SERVLET` | 依赖 `DispatcherServlet` 完成初始化[^2] | | `ConventionErrorViewResolver` | `DispatcherServlet` 已创建 | 通过 `DefaultErrorViewResolverConfiguration` 注册 | > **关键点**:Spring Boot 的自动配置通过 `@Conditional` 系列注解实现条件装配,各配置类之间存在严格依赖链。`DispatcherServlet` 是 Servlet 技术栈的核心枢纽,其缺失会导致后续相关配置(如错误处理)被跳过[^2][^5]。 --- ### 相关问题 1. 如何自定义 Spring Boot 的错误页面(如 404、500 页面)? 2. `@ConditionalOnBean` 和 `@ConditionalOnMissingBean` 在自动配置中的工作原理是什么? 3. 手动注册 `DispatcherServlet` 会导致哪些自动配置失效? 4. 如何通过日志分析 Spring Boot 自动配置的加载过程? [^1]: 涉及自动配置类列表 [^2]: `WebMvcAutoConfiguration` 的触发条件和依赖顺序 [^4]: DispatcherServlet 初始化日志 [^5]: DispatcherServlet 处理请求的调用链分析
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值