Spring测试因请求缺少末尾斜杠返回404失败问题
末尾斜杠兼容问题排查与解决(Spring 2.7.5)
问题背景
通过主包下的TrailingSlashRedirectingFilter实现请求末尾斜杠兼容,生产环境运行正常,但在UserControllerTest中:
- 已通过
excludeFilters排除原过滤器并导入TestConfig重新注册,请求不带末尾斜杠仍返回404 - 改用实现
WebMvcConfigurer的WebConfiguration设置setUseTrailingSlashMatch(true),问题依旧
代码片段
原过滤器实现
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class TrailingSlashRedirectingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String fullURL = ServletUriComponentsBuilder .fromRequest(request) .build() .toString(); String uri = request.getRequestURI(); if (fullURL.endsWith("/") && !uri.equals("/")) { fullURL = fullURL.substring(0, fullURL.length() - 1); response.setStatus(HttpStatus.MOVED_PERMANENTLY.value()); response.setHeader(HttpHeaders.LOCATION, fullURL); } else if(uri.equals("")){ response.setStatus(HttpStatus.MOVED_PERMANENTLY.value()); response.setHeader(HttpHeaders.LOCATION, fullURL + "/"); } else { filterChain.doFilter(request, response); } } }
测试配置类
@Configuration public class TestConfig { @Bean public FilterRegistrationBean<TrailingSlashRedirectingFilter> trailingSlashRedirectingFilter() { FilterRegistrationBean<TrailingSlashRedirectingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new TrailingSlashRedirectingFilter()); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); registrationBean.addUrlPatterns("/api/*"); return registrationBean; } }
测试类
@WebMvcTest(controllers = UserController.class, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = TrailingSlashRedirectingFilter.class)) @Import(TestConfig.class) @ExtendWith(MockitoExtension.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class UserControllerTest extends BaseTestConfig { UUID userId; User userRequest; User userResponse; String jsonRequest; String jsonResponse; @MockBean UserService userService; @Test void testCreateUser() throws Exception { User user = userResponse; when(userService.createUser(any())).thenReturn(user); mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/ht/users").content(jsonRequest).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) .andExpect(content().json(jsonResponse)) .andDo(document("create-user", resource("Create a user"))) .andReturn(); } }
路径匹配配置类
@Configuration public class WebConfiguration implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseTrailingSlashMatch(true); } }
问题分析
- 过滤器逻辑缺陷:原过滤器仅处理带末尾斜杠的请求(重定向到无斜杠地址)和空URI场景,未覆盖无斜杠请求匹配带斜杠控制器映射的情况,完全依赖Spring MVC路径匹配兜底。
- 测试配置加载问题:
@WebMvcTest默认禁用全量组件扫描,若未显式导入WebConfiguration,setUseTrailingSlashMatch(true)配置不会生效。 - 过滤器注册冲突:
excludeFilters仅排除通过@Component注册的过滤器,但TestConfig中通过FilterRegistrationBean重新注册的过滤器,可能因测试环境Filter链顺序、URL匹配规则未正确生效。
解决方案
方案一:修复过滤器逻辑,实现双向兼容
修改过滤器,同时处理无斜杠请求的适配(改写请求URI而非重定向,避免POST请求丢失方法):
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class TrailingSlashRedirectingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String uri = request.getRequestURI(); // 带斜杠的非根路径:重定向到无斜杠地址 if (uri.endsWith("/") && !uri.equals("/")) { String newUri = uri.substring(0, uri.length() - 1); response.setStatus(HttpStatus.MOVED_PERMANENTLY.value()); response.setHeader(HttpHeaders.LOCATION, buildFullUrl(request, newUri)); } // 无斜杠的非根路径:改写请求URI为带斜杠,适配控制器映射 else if (!uri.equals("/") && !uri.isEmpty()) { HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) { @Override public String getRequestURI() { return uri + "/"; } }; filterChain.doFilter(wrappedRequest, response); } else { filterChain.doFilter(request, response); } } private String buildFullUrl(HttpServletRequest request, String newUri) { return ServletUriComponentsBuilder.fromRequest(request) .replacePath(newUri) .build() .toString(); } }
方案二:确保测试环境加载路径匹配配置
在测试类中显式导入WebConfiguration,让斜杠匹配规则生效:
@WebMvcTest(controllers = UserController.class, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = TrailingSlashRedirectingFilter.class)) @Import({TestConfig.class, WebConfiguration.class}) // 新增导入WebConfiguration @ExtendWith(MockitoExtension.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class UserControllerTest extends BaseTestConfig { // 原有测试代码不变 }
方案三:简化测试配置,直接启用路径匹配
若无需在测试中验证过滤器逻辑,可直接在测试配置中设置路径匹配规则:
@Configuration public class TestWebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseTrailingSlashMatch(true); } }
然后在测试类中导入该配置并排除原过滤器:
@WebMvcTest(controllers = UserController.class, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = TrailingSlashRedirectingFilter.class)) @Import(TestWebConfig.class) @ExtendWith(MockitoExtension.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class UserControllerTest extends BaseTestConfig { // 原有测试代码不变 }
关键注意事项
- Spring 2.7.x中
setUseTrailingSlashMatch(true)有效,但该属性在Spring Boot 3.x已被移除,后续升级需改用过滤器或路径改写方案。 @WebMvcTest环境下,过滤器注册优先级、URL匹配规则与生产环境可能存在差异,需确保测试配置与生产逻辑一致。- 若使用重定向处理斜杠,POST请求会被转为GET请求,可能引发业务异常,优先选择改写请求URI的方式。
内容的提问来源于stack exchange,提问作者Francislainy Campos
相关产品推荐
相关产品推荐

