如何用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
相关产品推荐
相关产品推荐

