Spring Boot下自定义注解实现RequestMapping的API版本控制方案求教
实现Spring Boot自定义注解的API版本控制(URL路径前缀方式)
嘿,这个需求我之前在项目里也碰到过——Java确实不允许直接扩展注解,但咱们可以借助Spring的核心扩展机制来实现你的目标,下面给你几个可行的思路:
方案一:自定义注解 + 扩展RequestMappingHandlerMapping(推荐)
这是最贴合你需求的方案,直接在Spring处理请求映射的核心环节注入版本逻辑,稳定且灵活。
步骤1:创建自定义的@MyRequestMapping注解
复制@RequestMapping的核心属性,再添加version字段来指定API版本:
import org.springframework.web.bind.annotation.RequestMethod; import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyRequestMapping { // 复用@RequestMapping的核心属性 String[] value() default {}; String[] path() default {}; RequestMethod[] method() default {}; String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {}; // 新增版本控制属性,默认版本为1 int version() default 1; }
步骤2:扩展RequestMappingHandlerMapping重写映射逻辑
Spring的RequestMappingHandlerMapping负责解析所有请求映射,我们可以重写它的getMappingForMethod方法,自动将版本前缀拼接到路径前:
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; @Component public class VersionedRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { // 先获取原生@RequestMapping的映射信息 RequestMappingInfo originalInfo = super.getMappingForMethod(method, handlerType); if (originalInfo == null) { return null; } // 优先读取方法上的@MyRequestMapping,再读取类上的 MyRequestMapping myRequestMapping = AnnotationUtils.findAnnotation(method, MyRequestMapping.class); if (myRequestMapping == null) { myRequestMapping = AnnotationUtils.findAnnotation(handlerType, MyRequestMapping.class); if (myRequestMapping == null) { return originalInfo; } } int version = myRequestMapping.version(); String versionPrefix = "/v" + version; // 给所有映射路径添加版本前缀(跳过已手动添加版本的路径) Set<String> newPatterns = new HashSet<>(); for (String pattern : originalInfo.getPatternsCondition().getPatterns()) { if (!pattern.startsWith("/v")) { newPatterns.add(versionPrefix + pattern); } else { newPatterns.add(pattern); } } // 构建新的映射信息并返回 return originalInfo.mutate() .patterns(newPatterns) .build(); } }
步骤3:使用自定义注解
在Controller类或方法上直接使用@MyRequestMapping,指定版本即可:
@RestController // 类级别指定默认版本1 @MyRequestMapping(version = 1) public class UserController { // 方法级别覆盖版本为2,最终映射路径:/v2/users @MyRequestMapping(version = 2, value = "/users", method = RequestMethod.GET) public List<User> getUsers() { // 业务逻辑 } // 继承类级别版本,最终映射路径:/v1/user/{id} @MyRequestMapping(value = "/user/{id}", method = RequestMethod.GET) public User getUserById(@PathVariable Long id) { // 业务逻辑 } }
方案二:自定义注解 + AOP(备选,适合简单场景)
如果你的项目不需要复杂的映射逻辑,可以用AOP拦截带@MyRequestMapping的方法,动态注册版本化的请求映射。不过这个方案需要手动处理Spring的映射注册表,复杂度较高,不如方案一直接,这里就不展开细节了。
方案三:路径变量+全局配置(快速替代方案)
如果不想自定义注解,也可以用Spring的路径变量实现版本控制,比如:
@RequestMapping("/v{version}/users") public List<User> getUsers(@PathVariable int version) { // 根据版本处理逻辑 }
再通过全局配置设置默认版本,但这种方式无法自动注入版本前缀,灵活性不如自定义注解。
注意事项
- 优先级:方法上的
@MyRequestMapping版本会覆盖类上的版本 - 兼容原生:如果Controller中混合使用
@RequestMapping和@MyRequestMapping,原生注解的映射不会被修改 - 避免重复:代码中已经处理了手动添加版本前缀的路径,不会重复拼接
内容的提问来源于stack exchange,提问作者Kumar Gaurav
相关产品推荐
相关产品推荐

