从Spring(Vanilla)迁移至Spring Boot:如何保留带.s/.json后缀的URL映射
正在把原生Spring MVC的大型Web应用迁移到Spring Boot 3.2.5,原应用在web.xml里配置了DispatcherServlet只处理.s和.json后缀的请求:
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>3</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.s</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.json</url-pattern> </servlet-mapping>
Spring Boot里DispatcherServlet默认映射"/",但应用里大量URL引用(比如https://example.com/article.s?id={id})和控制器代码都依赖这些后缀。现在想不用修改所有URL和控制器代码,让/article.s能匹配@RequestMapping(value = "/article", params="id")的控制器,.json后缀的请求同理——原Spring MVC里不用在@RequestMapping加后缀,框架会自动忽略。
另外,旧版Spring用WebMvcConfigurer.configurePathMatch里的setUseSuffixPatternMatch(true)实现这个功能,但Spring 6里这方法已经废弃且无替代,只想实现把*.s/*.json重写成对应无后缀路径的效果。
方案1:自定义请求路径剥离Filter
写一个Filter,在请求到达DispatcherServlet之前,把.s或.json后缀去掉,修改请求URI:
import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class SuffixStripFilter implements Filter { private static final Pattern SUFFIX_PATTERN = Pattern.compile("(.+)(\\.s|\\.json)$"); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String requestURI = httpRequest.getRequestURI(); Matcher matcher = SUFFIX_PATTERN.matcher(requestURI); if (matcher.matches()) { String strippedUri = matcher.group(1); HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(httpRequest) { @Override public String getRequestURI() { return strippedUri; } @Override public String getServletPath() { return strippedUri; } }; chain.doFilter(wrappedRequest, response); } else { chain.doFilter(request, response); } } }
这个Filter会自动匹配带目标后缀的请求,剥离后再传给DispatcherServlet,控制器不需要做任何修改就能匹配。
方案2:用UrlRewriteFilter实现URL重写
引入成熟的URL重写工具,配置更灵活:
- 添加Maven依赖:
<dependency> <groupId>org.tuckey</groupId> <artifactId>urlrewritefilter</artifactId> <version>4.0.4</version> </dependency>
- 在
src/main/webapp/WEB-INF下创建urlrewrite.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 4.0//EN" "https://tuckey.org/res/dtds/urlrewrite4.0.dtd"> <urlrewrite> <rule> <from>^(.+)\.s$</from> <to type="forward">$1</to> </rule> <rule> <from>^(.+)\.json$</from> <to type="forward">$1</to> </rule> </urlrewrite>
- 在Spring Boot里注册这个Filter:
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter; import org.springframework.core.Ordered; @Configuration public class UrlRewriteConfig { @Bean public FilterRegistrationBean<UrlRewriteFilter> urlRewriteFilter() { FilterRegistrationBean<UrlRewriteFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new UrlRewriteFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); return registrationBean; } }
这种方式适合后续可能需要更多URL规则的场景,配置修改更方便。
方案3:内容协商配置(仅针对.json后缀)
如果.json后缀是用来做内容协商的,可以配置Spring MVC的内容协商管理器,让框架识别后缀并映射到对应媒体类型,同时不影响路径匹配:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(true) .mediaType("json", org.springframework.http.MediaType.APPLICATION_JSON) .ignoreAcceptHeader(false); } }
注意:这个方案只处理.json后缀,.s后缀还是需要结合前两种方案处理。
方案对比
- 自定义Filter:轻量无依赖,代码可控,适合仅需剥离特定后缀的简单场景。
- UrlRewriteFilter:功能强大,规则配置灵活,适合未来有扩展需求的场景。
- 内容协商配置:仅适配与媒体类型相关的后缀,局限性大。
内容的提问来源于stack exchange,提问作者Jakub Królikowski

