说明
在Spring MVC项目中,如果要进行restful接口的版本控制一般有以下几个方向:
- 基于参数的版本控制
- 基于header的版本控制
- 基于path的版本控制
在spring MVC下,url映射到哪个method是由RequestMappingHandlerMapping来控制的,那么我们可以通过对RequestMappingHandlerMapping
定义条件来做版本控制
环境搭建
ApiVersion
表示请求的版本
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
String value() default "";
}
WebMvcRegistrationsConfig.java
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
private static final String VERSION_PARAM_NAME = "version";
private static final String HEADER_VERSION = "X-VERSION";
/**
* RequestMappingHandlerMapping 被 VersionRequestMappingHandlerMappingHandlerMapping 替换
* 按照VersionRequestMappingHandlerMappingHandlerMapping映射逻辑进行映射
* @return
*/
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new VersionRequestMappingHandlerMapping();
}
private class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
if (apiVersion == null) {
return null;
}
return new RequestCondition() {
@Override
public Object combine(Object o) {
return null;
}
@Override
public Object getMatchingCondition(HttpServletRequest request) {
String version = resolveVersion(method, request);
if (Objects.equals(version, apiVersion.value())) {
return this;
}
return null;
}
@Override
public int compareTo(Object o, HttpServletRequest request) {
return 0;
}
};
}
private String resolveVersion(Method method, HttpServletRequest request) {
String version = request.getParameter(VERSION_PARAM_NAME);
if (StringUtils.hasText(version)) {
return version;
}
version = request.getHeader(HEADER_VERSION);
if (StringUtils.hasText(version)) {
return version;
}
return resolvePathVersion(method, request);
}
private String resolvePathVersion(Method method, HttpServletRequest request) {
String bastPath = null;
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == RequestMapping.class) {
bastPath = ((RequestMapping)annotation).value()[0];
break;
} else if (annotation.annotationType() == GetMapping.class) {
bastPath = ((GetMapping)annotation).value()[0];
break;
} else if (annotation.annotationType() == PostMapping.class) {
bastPath = ((PostMapping)annotation).value()[0];
break;
} else if (annotation.annotationType() == DeleteMapping.class) {
bastPath = ((DeleteMapping)annotation).value()[0];
break;
} else if (annotation.annotationType() == PutMapping.class) {
bastPath = ((PutMapping)annotation).value()[0];
break;
}
}
Map<String, String> uriVariables = getPathMatcher().extractUriTemplateVariables(bastPath.startsWith("/") ? bastPath : "/" + bastPath,
request.getServletPath());
return uriVariables.get(VERSION_PARAM_NAME);
}
}
}
测试
@GetMapping("{version}/test")
public String v1(@PathVariable String version) {
return "default";
}
@GetMapping("{version}/test")
@ApiVersion("v2")
public String v2() {
return "v2";
}
@GetMapping("{version}/test")
@ApiVersion("v3")
public String v3() {
return "v3";
}
/**
* version在不一定非要在第一个位置
* @return
*/
@GetMapping("/test/{version}")
@ApiVersion("v4")
public String v4() {
return"v4";
}
@GetMapping("/test/{version}")
public String v5() {
return "default";
}
curl -X GET \
'http://localhost:8080/v1/test?version=v3' \
-H 'X-VERSION: v2'
优先级依次是:request参数 > header > path参数
path参数,变量不一定写在第一个,只要变量名称是${version}就可以
GitHub:https://github.com/jkxyx205/spring-boot-learn/tree/master/spring-mvc-mapping