随着业务的发展,产品对外开放的API接口会随着业务的需要经常发生变动,考虑到快速响应新用户的需求,且不影响老用户当前使用的前提下,对开放API进行版本控制是非常有必要的。
API版本控制的优点如下:
1:降低代码冗余
2:兼容历史版本
3:新接口升级可进行增量迭代,且版本向下兼容,升级平滑,可在历史客户无感的情况下进行版本迭代更新。
实现步骤如下:
01:新增自定义注解
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
/**
* 版本号
* @return
*/
int value() default 1;
}
ElementType.TYPE 表示注解可作用于类
ElementType.METHOD 表示注解可作用于方法
02:定义匹配逻辑
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+).*");
private int apiversion ;
ApiVersionCondition(int apiversion){
this.apiversion=apiversion;
}
public int getApiversion() {
return apiversion;
}
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
return new ApiVersionCondition(apiVersionCondition.getApiversion());
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
if(m.find()){
Integer version = Integer.valueOf(m.group(1));
if(version>=this.apiversion){
return this;
}
}
return null;
}
1:通过ApiVersionCondition 重写 RequestCondition 的URL匹配逻辑,将提取请求URL中的版本号与注解上定义的版本号进行对比,以此来判断某个请求应落在哪个控制器上。
2:当类和方法上都包含 @ApiVersion注解时,通过ApiVersionRequestCondition.combine完成注解的合并,提取版本号,并与注解上的版本号进行比对,判断版本号是否符合要求。
03:自定义匹配处理
public class ApiRequestHandlerMapping extends RequestMappingHandlerMapping {
public final static String VERSION_FLAG = "{version}";
public static RequestCondition<ApiVersionCondition> createCondition(Class<?> clazz){
RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
if(classRequestMapping == null){
return null;
}
StringBuilder mappingUrlBuilder = new StringBuilder();
if(classRequestMapping.value().length>0){
mappingUrlBuilder.append(classRequestMapping.value()[0]);
}
String mappingUrl = mappingUrlBuilder.toString();
if(!mappingUrl.contains(VERSION_FLAG)){
return null;
}
ApiVersion apiVersion = clazz.getAnnotation(ApiVersion.class);
return apiVersion == null ? new ApiVersionCondition(1):new ApiVersionCondition(apiVersion.value());
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method){
return createCondition(method.getClass());
}
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType){
return createCondition(handlerType);
}
}
1:spring mvc中把web框架和spring ioc融合在一起,是通过ContextLoaderListener监听servlet上下文的创建后来加载父容器完成的,然后通过配置一个servlet对象DispatcherServlet,在初始化DispatcherServlet时来加载具体子容器。
2:RequestMappingHandlerMapping也是在DispatcherServlet的初始化过程中自动加载的。
3: 默认会自动加载所有实现HandlerMapping接口的bean。
4:我们可以通过setOrder来设置优先级。
5:系统默认会加载RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、 SimpleUrlHandlerMapping 并且按照顺序使用。
04:增加配置类WebMvcRegistrationsConfig
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiRequestHandlerMapping();
}
}
1:增加此类的目的是为了注册 ApiRequestHandlerMapping 到系统中。
04:增加两个版本的接口
接口【V1】【v2】版本 ,其中/search/{orderId} 接口【V1】【V2】 版本的接口均包含
@ApiVersion(value = 1)
@RestController
@RequestMapping("api/{version}/order")
public class ApiVersionTestV1Controller {
@GetMapping("/save/{orderId}")
public JSONResult saveOrderById(@PathVariable int orderId){
String message = "订单号 V1 保存成功" + orderId;
System.out.println(message);
return JSONResult.OK(message);
}
@GetMapping("/search/{orderId}")
public JSONResult searchOrderById(@PathVariable int orderId){
String message = "获取订单详情成功" + orderId;
System.out.println(message);
return JSONResult.OK(message);
}
}
@ApiVersion(value = 2)
@RestController
@RequestMapping("api/{version}/order")
public class ApiVersionTestV2Controller {
@GetMapping("/delete/{orderId}")
public JSONResult deleteOrderById(@PathVariable int orderId){
String message = "订单号 V2 删除成功" + orderId;
System.out.println(message);
return JSONResult.OK(message);
}
@GetMapping("/query")
public JSONResult queryOrder(){
String message = "获取订单列表信息成功V2!";
System.out.println(message);
return JSONResult.OK(message);
}
@GetMapping("/search/{orderId}")
public JSONResult searchOrderById(@PathVariable int orderId){
String message = "V2获取订单详情成功" + orderId;
System.out.println(message);
return JSONResult.OK(message);
}
【测试结果】
1:通过V2版本访问V1的接口
【结论】:高版本,可直接访问低版本接口。
2:通过V1版本访问V2的接口
【结论:低版本无法访问高版本接口】
3:访问V1,V2 版本均包含的接口
【结论】:同名接口均调用对应版本,互不影响。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。