一、为什么需要版本号
一般来说,api 接口是提供给其他系统或是其他公司使用,不能随意频繁的变更。然而,需求和业务不断变化,接口和参数也会发生相应的变化。如果直接对原来的接口进行修改,势必会影响其他系统的正常运行。这就必须对api 接口进行有效的版本控制。
例如,添加用户的接口,由于业务需求变化,接口的字段属性也发生了变化而且可能和之前的功能不兼容。为了保证原有的接口调用方不受影响,只能重新定义一个新的接口。
http://localhost:8080/api/v1/user
http://localhost:8080/api/v2/user
Api 版本控制的方式:
域名区分管理,即不同的版本使用不同的域名,v1.api.test.com,v2.api.test.com
请求url 路径区分,在同一个域名下使用不同的url路径,test.com/api/v1/,test.com/api/v2
请求参数区分,在同一url路径下,增加version=v1或v2 等,然后根据不同的版本,选择执行不同的方法。
二、自定义注解实现版本控制
大致实现方案:
首先,创建自定义的MyApiVersion注解和启动注解EnableMyApiVersion
然后,自定义url匹配逻辑MyApiVersionCondition,自定义匹配处理器CustomRequestMappingHandlerMapping
最后配置注册自定义的RequestMappingHandlerMapping
1.自定义版本控制注解和启动注解
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface MyApiVersion {
/**
* 标识版本号 默认走V1版本
* @return
*/
int value() default 1;
}复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ApiAutoConfiguration.class)
@Documented
public @interface EnableMyApiVersion {
}复制
创建 MyApiVersionCondition 类,并继承RequestCondition 接口,作用是:版本号筛选,将提取请求URL中版本号,与注解上定义的版本号进行比对,以此来判断某个请求应落在哪个controller方法上。
自定义匹配处理器,重写部分 RequestMappingHandlerMapping 的方法。
public class MyApiVersionCondition implements RequestCondition<MyApiVersionCondition> {
// 路径中版本的前缀, 这里用 /v[1-9]/的形式
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
private int apiVersion;
public MyApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
@Override
public MyApiVersionCondition combine(MyApiVersionCondition other) {
// 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
return new MyApiVersionCondition(other.getApiVersion());
}
@Override
@Nullable
public MyApiVersionCondition getMatchingCondition(HttpServletRequest request) {
Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
if (m.find()) {
Integer version = Integer.valueOf(m.group(1));
if (version >= this.apiVersion) {
return this;
}
}
return null;
}
@Override
public int compareTo(MyApiVersionCondition other, HttpServletRequest request) {
// 优先匹配最新的版本号
return other.getApiVersion() - this.apiVersion;
}
public int getApiVersion() {
return apiVersion;
}
}复制
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
MyApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, MyApiVersion.class);
return createCondition(apiVersion);
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
MyApiVersion apiVersion = AnnotationUtils.findAnnotation(method, MyApiVersion.class);
return createCondition(apiVersion);
}
private RequestCondition<MyApiVersionCondition> createCondition(MyApiVersion apiVersion) {
return apiVersion == null ? null : new MyApiVersionCondition(apiVersion.value());
}
}复制
3.配置注册自定义的RequestMappingHandlerMapping
重写请求过处理的方法,将之前创建的 CustomRequestMappingHandlerMapping 注册到系统中。
public class ApiAutoConfiguration implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new CustomRequestMappingHandlerMapping();
}
}复制
ApiAutoConfiguration没有使用Configuration自动注入,而是使用Import带入,目的是可以在程序中选择性启用或者不启用版本控制。
三、测试
1.测试Controller
@Slf4j
@RestController
@RequestMapping("/diku")
public class VersionController {
@MyApiVersion
@GetMapping("/{version}/test")
public String myVersion() {
log.info("版本控制测试!----V1");
return "V1测试成功";
}
@MyApiVersion(2)
@GetMapping("/{version}/test")
public String myVersionTwo() {
log.info("版本控制测试!----V2");
return "V2测试成功";
}
}复制
启动类加上启动版本控制注解
@EnableMyApiVersion
@SpringBootApplication
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}复制
2.使用postman进行测试
这两个截图说明,请求正确的版本地址,会自动匹配版本的对应接口。
这个截图说明,当请求的版本大于当前版本时,默认匹配当前版本。
以上,就把Spring Boot 如何优雅的设计 Restful API 接口版本号,实现 API 版本控制介绍完了!主要就是梳理Springmvc的执行流程中的handlerMapping。