You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:49:34