Spring Boot 通过自定义 HandlerMapping 按包目录自动拼接接口前缀
在维护多版本 API(如 v1、v2)时,传统做法需为每个 Controller 手动编写冗长的 @RequestMapping 路径前缀。本文介绍一种自动化方案:利用 Spring MVC 的扩展机制,重写 RequestMappingHandlerMapping,使系统能直接读取 Controller 所在的 Java 包结构(如 com.api.v1)并自动将其转换为 URL 前缀。
实现原理与目标
Spring 使用 RequestMappingHandlerMapping 来解析和处理 @RequestMapping 注解。其核心流程是在应用启动时调用 getMappingForMethod 方法来生成具体的路由映射规则。我们只需要拦截这个方法的返回值,提取当前的包名信息,并将其转化为前缀合并到原有的路由信息中,即可达到“根据目录结构生成接口前缀”的目的。
这种设计模式的优势在于解耦:业务层的 Controller 依然专注于自身的资源逻辑(如 /banner),而不需要关心所属的 API 版本层(如 /v1/)。当新增版本或调整包结构时,无需改动现有的控制器代码。
第一步:编写自定义 HandlerMapping
我们需要创建一个继承自 RequestMappingHandlerMapping 的新类。该类的主要职责是重写 getMappingForMethod 方法,在此处注入前缀逻辑。
- 获取原始映射信息:调用父类的
super.getMappingForMethod(method, handlerType),这一步已经拿到了当前 Controller 和 Method 定义的常规@RequestMapping内容。 - 提取包路径:通过
handlerType获取当前 Controller 的全限定包名,并根据配置根目录截取特定后缀(即版本号路径)。 - 格式化前缀:将 Java 包的点号分隔(如
.v1.)替换为 URL 斜线分隔(如/v1/)。 - 合并路径:使用
RequestMappingInfo.paths().combine()将生成的前缀与原有路径合并。
public class AutoPrefixUrlMapping extends RequestMappingHandlerMapping {
// 通过配置文件注入API的根包名,例如:com.example.api
@Value("${api.package.path}")
private String apiPackagePath;
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 1. 获取原始的RequestMappingInfo对象
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
// 如果没有原生的请求映射,则直接返回null或默认处理
if (mappingForMethod == null) {
return null;
}
// 2. 提取基于包结构的URL前缀
String prefix = getPrefix(handlerType);
// 3. 合并前缀:生成包含新前缀的新的 RequestMappingInfo 并与原信息合并
if (prefix != null && !prefix.isEmpty()) {
return RequestMappingInfo.paths(prefix).build().combine(mappingForMethod);
}
return mappingForMethod;
}
/**
* 从类所在的包中提取路径前缀。
* 例如:com.example.api.v1.controller.BannerController -> /v1/controller/
*/
private String getPrefix(Class<?> handlerType){
String packageName = handlerType.getPackage().getName();
// 移除配置的根路径部分
String doPath = packageName.replaceAll(this.apiPackagePath, "");
// 将包的分隔符'.'替换为路径分隔符'/'
return doPath.replace('.', '/');
}
}
第二步:注册配置类以生效
自定义的 Mapping 必须告诉 Spring 容器使用它来替代默认的处理器映射。这可以通过实现 WebMvcRegistrations 接口来完成,Spring 会自动检测此类并应用自定义逻辑。
- 实现接口:新建配置类实现
WebMvcRegistrations,重写getRequestMappingHandlerMapping方法。 - 实例化返回:在该方法中返回自定义的
AutoPrefixUrlMapping实例。 - 加入容器:添加
@Component注解使其被扫描注册。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.config.annotation.WebMvcRegistrations;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Component
public class AutoPrefixConfiguration implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
// 返回自定义的映射器
return new AutoPrefixUrlMapping();
}
}
第三步:测试与验证
配置完成后,我们可以在不同版本目录下创建 Controller 进行验证。此时,Controller 内部的 @RequestMapping 只需要声明其相对层级,无需包含版本号。
1. 建立 V1 版本的控制器:
假设包路径为 com.example.api.v1.banner,对应的请求会自动带上 /v1/banner 前缀。
@RestController
@RequestMapping("/test") // 最终路径将是:/v1/test/test(取决于具体包名组合逻辑)
// 如果放在 com.example.api.v1 下且上面有 @RequestMapping("/"),则可能是 /v1/
public class BannerV1Controller {
@GetMapping("/hello")
public String hello() {
return "Hello from v1";
}
}在浏览器或客户端发起请求:http://localhost:8080/v1/hello(此处路径构成依赖于实际包名深度与基础映射)。由于前面我们的示例是 getPrefix 会保留整个剩余包路径并转为斜杠,因此若包是 com.api.v1,则 v1 会变成 /v1;若有子包 v1.xxx,则为 /v1/xxx。
2. 建立 V2 版本的控制器:
新建一个 v2 目录下的 Controller,不修改任何代码结构,仅移动文件。
重启服务后,访问新版接口的地址,系统会根据其所在的 v2 包自动识别路径前缀:
成功访问证明无论包结构多么复杂,均能根据目录自动生成完整且正确的前缀路径。
注意事项与排错
- 配置项检查:确保 application.yml 或 properties 文件中定义了
api.package.path指向你的 API 根目录,且该值在正则匹配时能正确剥离。 - 冲突问题:如果多个不同的包路径经过计算后产生了完全相同的 URL 前缀,Spring 在启动时会抛出 Ambiguous Mapping 异常。请确保各版本下的路径唯一性。
- 静态资源:此方案仅影响带
@RequestMapping注解的 Controller 映射,不会影响spring.mvc.static-path-pattern配置的静态文件访问。
以上就是基于 WebMvcRegistrations 和 RequestMappingHandlerMapping 自动拼接 API 路径前缀的详细内容,更多关于 Spring Boot 高级路由配置的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









