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

如何用Spring自定义带参数前缀的请求映射注解?

问题描述

使用Spring Boot 2 + Java 11,希望自定义@APIv1注解,实现以下效果:

  • 注解传入的路径参数(如"/users/")能与固定前缀"/api/v1"自动拼接,作为控制器类的基础路径
  • 控制器方法上的@GetMapping等路径能与类级路径进一步拼接,最终形成完整API路由(比如示例中/api/v1/users/info)

示例期望的代码结构:

@APIv1("/users/")    // 拼接为"/api/v1/users/"
public class UserController {
    @GetMapping("/info")
    public String info() {return "This should be returned at /api/v1/users/info/";}

    /* 更多带映射的方法 */
}

之前参考写法存在问题:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
@RequestMapping("/api/v1")
@interface APIv1 {
    @AliasFor(annotation = RestController.class)
    String value() default "";
}

该写法错误地将value属性与RestController的组件名称属性关联,导致传入的路径参数不生效,所有方法都会路由到/api/v1/info这类路径,无法实现子路径拼接。

解决方案

要实现固定前缀+自定义子路径的自动拼接,需要通过自定义请求映射处理器扩展Spring的路由逻辑,具体步骤如下:

1. 定义@APIv1注解

调整注解的属性用途,让value用于接收子路径,同时保留@RestController的特性:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
public @interface APIv1 {
    // 用于传入类级别的子路径,比如"/users/"
    String value() default "";
}

2. 自定义请求映射处理器

创建APIv1RequestMappingHandlerMapping类,重写Spring的路径解析逻辑,将"/api/v1"与注解的value拼接作为类的基础路径:

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

public class APIv1RequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    private static final String API_V1_PREFIX = "/api/v1";

    @Override
    protected boolean isHandler(Class<?> beanType) {
        // 只处理标注了@APIv1的类
        return AnnotationUtils.findAnnotation(beanType, APIv1.class) != null;
    }

    @Override
    protected void registerHandlerMethod(Object handler, Method method, Object handlerMethod) {
        Class<?> beanType = handler.getClass();
        APIv1 apiV1Annotation = AnnotationUtils.findAnnotation(beanType, APIv1.class);
        if (apiV1Annotation != null) {
            // 拼接前缀与注解传入的子路径
            String classPath = API_V1_PREFIX + apiV1Annotation.value();
            // 为当前类设置拼接后的基础路径
            setRequestMappingPrefix(classPath);
        }
        super.registerHandlerMethod(handler, method, handlerMethod);
    }

    private void setRequestMappingPrefix(String prefix) {
        // 通过反射修改父类的prefix属性,实现路径前缀设置
        try {
            java.lang.reflect.Field prefixField = RequestMappingHandlerMapping.class.getDeclaredField("prefix");
            prefixField.setAccessible(true);
            prefixField.set(this, prefix);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("Failed to set API v1 prefix", e);
        }
    }
}

3. 注册自定义处理器到Spring容器

添加配置类,将自定义的APIv1RequestMappingHandlerMapping注册为Bean,让Spring使用它来处理@APIv1注解的控制器:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Configuration
public class WebConfig {

    @Bean
    public RequestMappingHandlerMapping apiV1RequestMappingHandlerMapping() {
        return new APIv1RequestMappingHandlerMapping();
    }
}

4. 使用验证

按照最初的示例代码使用@APIv1注解,此时控制器的基础路径会自动拼接为"/api/v1/users/",方法上的@GetMapping("/info")会最终路由到"/api/v1/users/info",完全符合预期。

如果不需要子路径(比如基础的/api/v1接口),直接使用@APIv1即可,默认子路径为空,基础路径就是"/api/v1"。

内容的提问来源于stack exchange,提问作者aPatchyDev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 08:59:14