Spring Boot 2.7.18中@ConditionalOnMissingBean未生效,生成两个InternalResourceViewResolver
问题场景
基于Spring Boot 2.7.18进行自动配置,自定义以下配置类创建InternalResourceViewResolver:
@Configuration @ConditionalOnClass(javax.servlet.jsp.jstl.core.Config.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) public class ViewResolverConfiguration { @Bean public InternalResourceViewResolver internalResourceViewResolver( @Value("${spring.mvc.view.prefix:/WEB-INF/jsp/}") String prefix, @Value("${spring.mvc.view.suffix:.jsp}") String suffix) { final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix(prefix); viewResolver.setSuffix(suffix); viewResolver.setOrder(InternalResourceViewResolver.HIGHEST_PRECEDENCE); return viewResolver; } }
运行后通过/actuator/beans发现存在两个InternalResourceViewResolver实例:
"internalResourceViewResolver": { "aliases": [], "scope": "singleton", "type": "org.springframework.web.servlet.view.InternalResourceViewResolver", "resource": "class path resource [com/example/ViewResolverConfiguration.class]", "dependencies": [ "com.example.ViewResolverConfiguration" ] }, "defaultViewResolver": { "aliases": [], "scope": "singleton", "type": "org.springframework.web.servlet.view.InternalResourceViewResolver", "resource": "class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]", "dependencies": [ "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter" ] }
其中WebMvcAutoConfiguration中的defaultViewResolver带有@ConditionalOnMissingBean注解,但仍被创建:
@Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(this.mvcProperties.getView().getPrefix()); resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver; }
通过/actuator/conditions查看条件判断结果为:
"WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#defaultViewResolver": [ { "condition": "OnBeanCondition", "message": "@ConditionalOnMissingBean (types: org.springframework.web.servlet.view.InternalResourceViewResolver; SearchStrategy: all) did not find any beans" } ]
排查思路
1. 配置类加载顺序与条件触发时机
@ConditionalOnMissingBean的生效依赖于Bean的注册顺序。如果自定义配置类的加载时机晚于WebMvcAutoConfiguration,那么在自动配置类执行条件检查时,自定义的InternalResourceViewResolver还未被注册,条件判断会认为不存在该Bean,从而创建默认实例。
- 检查自定义配置类的条件注解:
@ConditionalOnClass(javax.servlet.jsp.jstl.core.Config.class)要求JSTL类存在才会加载配置类。如果JSTL依赖的加载时机晚于WebMvcAutoConfiguration的条件检查,会导致自定义配置类延迟加载。 - 对比自动配置类的条件:
WebMvcAutoConfiguration的触发条件是@ConditionalOnWebApplication(type = SERVLET)+@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }),这些类通常在启动初期就会被加载,所以自动配置类会优先触发。
2. 验证Bean注册顺序
可以通过以下方式确认两个Bean的注册顺序:
- 在自定义配置类的
internalResourceViewResolver()方法中添加日志,输出Bean创建的时间点; - 自定义
BeanPostProcessor,在Bean初始化前打印Bean名称和时间,对比两个实例的注册顺序。
3. 检查@ConditionalOnMissingBean的搜索范围
虽然条件信息显示搜索策略为all,但如果自定义Bean所在的配置类未被加载(条件未满足),搜索时自然无法找到该Bean。需要确认JSTL的Config.class是否确实存在于类路径中,可通过打印类路径或检查依赖树验证。
解决方法
方法一:强制自定义配置类优先加载
给自定义配置类添加@AutoConfigureBefore(WebMvcAutoConfiguration.class)注解,确保自定义配置类在自动配置类之前加载。这样当自动配置类执行@ConditionalOnMissingBean检查时,自定义的InternalResourceViewResolver已经存在,就不会创建默认实例:
@Configuration @ConditionalOnClass(javax.servlet.jsp.jstl.core.Config.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @AutoConfigureBefore(WebMvcAutoConfiguration.class) public class ViewResolverConfiguration { // 原有代码不变 }
方法二:设置自定义Bean为优先实例(仅解决优先级,不消除重复Bean)
给自定义的InternalResourceViewResolver添加@Primary注解,确保Spring在注入时优先选择自定义实例。但此方法不会阻止默认Bean的创建,仅解决注入优先级问题:
@Bean @Primary public InternalResourceViewResolver internalResourceViewResolver(...) { // 原有代码不变 }
方法三:排除自动配置类(不推荐)
通过spring.autoconfigure.exclude属性排除WebMvcAutoConfiguration,但这会影响其他MVC自动配置逻辑,仅在特殊场景下使用:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
内容的提问来源于stack exchange,提问作者Niels Bech Nielsen

