拆分@ComponentScan与@EnableWebMvc至不同配置类引发请求映射失效问题
问题
我定义了两个Spring配置类:
@Configuration @EnableWebMvc public class WebConfig { } @Configuration @ComponentScan("io.abned.spring.websocket.controllers") public class AppConfig { }
同时在io.abned.spring.websocket.controllers包下有控制器类及对应的测试类:
@RestController @RequestMapping("api/v1") public class HelloController { @GetMapping("hello") public String hello() { return "Hello, world!"; } } @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextHierarchy({ @ContextConfiguration(classes = AppConfig.class), @ContextConfiguration(classes = WebConfig.class) }) class HelloControllerIntegrationTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } @Test void testHello() throws Exception { mockMvc.perform(get("/api/v1/hello")) .andExpect(status().isOk()) .andExpect(jsonPath("$").value("Hello, world!")); } }
使用Spring 6.1.10(未使用Spring Boot)时,上述测试执行失败,但将@ComponentScan移至WebConfig后测试成功。请问为何拆分@ComponentScan与@EnableWebMvc到不同配置类时会出现此问题?
原因分析
问题核心在于Spring上下文层级与请求映射处理器的扫描范围限制:
- 通过
@ContextHierarchy定义的两层上下文存在父子关系:AppConfig是父上下文,WebConfig是子Web上下文(因标注了@WebAppConfiguration)。 @EnableWebMvc会自动注册RequestMappingHandlerMapping组件,它的职责是扫描并注册控制器的请求映射规则。- 默认情况下,
RequestMappingHandlerMapping的detectHandlerMethodsInAncestorContexts属性为false,仅会扫描当前所在的子Web上下文中的控制器Bean。 - 你的
HelloController是由父上下文AppConfig的@ComponentScan扫描注册的,不在子Web上下文的扫描范围内,因此请求映射无法被识别,测试时会返回404导致失败。 - 若将
@ComponentScan移至WebConfig,控制器会被注册到子Web上下文中,自然能被RequestMappingHandlerMapping检测到,测试也就正常通过了。
解决方案
修改WebConfig,让它直接继承DelegatingWebMvcConfiguration并覆写createRequestMappingHandlerMapping方法,开启对祖先上下文的控制器扫描:
@Configuration public class WebConfig extends DelegatingWebMvcConfiguration { @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { RequestMappingHandlerMapping requestMapping = super.createRequestMappingHandlerMapping(); requestMapping.setDetectHandlerMethodsInAncestorContexts(true); return requestMapping; } }
配置完成后,RequestMappingHandlerMapping会扫描当前上下文及其所有父上下文中的控制器Bean,就能正确识别到父上下文里的HelloController了。
内容的提问来源于stack exchange,提问作者Abned
相关产品推荐
相关产品推荐

