为什么要对API进行版本管理

因为软件有一个不断升级迭代的过程,而在升级中我们业务需求可能不断在更新,所以我们需要对以前的API接口不断的更新以满足变化的需求,但是在更新升级的过程中我们势必又要保证原来功能的可用性,所以我们就必须要对同一个API接口进行多版本的管理。

常用的API管理方式

1、不对API进行版本管理

每次版本升级都强制用户更新升级,这种方式非每次用户打开APP都要更新,非常影响用户的体验。然后这种方式会在旧系统过渡到新系统过程中让旧系统无法使用。并且这也会与业务冲突,很多场景业务就有需要新老系统并行的要求。

2、不同版本用不同域名

针对域名转发到部署了不同版本应用的服务器。如:http://v1.com ,v2.com,http://v3.com 的域名分别转发到部署了对应版本的应用服务器。

这种方式只有在系统比较稳定的时候,产品初期版本更新迭代比较快则不太适合,而且如果是每个版本都单独部署的话维护比较麻烦也比较浪费资源。

3、维护多个版本的代码(推荐)

首先在代码里面维护多个版本的API几口,然后在请求的URL、或者请求header里面带上一个版本参数,然后我们根据这个版本参数来区分调用对应版本的代码。

使用URL区分版本:

这种方式通常在代码里面我们会保存对应版本的controller方法,根据不同版本的URL 来分别调用对应的方法 如:

http://localhost:8888/test/v1/hellohttp://localhost:8888/test/v2/hellohttp://localhost:8888/test/v3/hello

分别调用我们的后台对应controller的方法



@RequestMapping("/test")
@RestController
public class TestAPiVersionController {

 @RequestMapping("v1/hello")
 public String hello(){
 return "version 1";
    }

 @RequestMapping("v2/hello")
 public String hello2(){
 return "version 2";
    }

 @RequestMapping("v3/hello")
 public String hello5(){
 return "version 3";
    }
}



通过保存多个版本的接口,然后在URL标识对应的版本号来调用对应的接口,这种方式解决了多版本管理的问题,但使用这种方式对于前端工程师来说也比较麻烦,必须在每个请求的URL上标记一个版本号来使用对应的版本的API,看着眼花缭乱的URL显然不太优雅。

所以我们会对这种方式进行一下优化。

优化目标

不需要在URL上标记版本号,前端只需要传一个当前APP的版本号,后端根据前端传过来的版本号,自动匹配到对应的版本API接口。

比如:

http://localhost:8888/test/hello hader 参数 apiversion 值 为v1 那么请求转发到v1版本的接口

http://localhost:8888/test/hello hader 参数 apiversion 值 为v2 那么请求转发到v2版本的接口

思路:

1、通过注解来标识不同接口的版本。

2、通过URL和请求Header 里面的apiversion值来建立与Controller不同版本方法的关系。

3、当一个请求进来,我们可以根据URL+apiversion 来执行对应版本的Controller方法。

如果不用@RequestMapping 的URL来标记不API的版本,那么势必我们得换一个方式来区分不同版本的API接口,这里我们用注解来实现。



/**
 * 定义接口版本号
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ApiVersion {

 int value();
}



Controller里面使用注解标识接口的版本号



@RequestMapping("/test")
@RestController
public class TestAPiVersionController {

 @RequestMapping("hello")
 @ApiVersion(1)
 public Object hello(){
 return "version 1";
    }

 @RequestMapping("hello")
 @ApiVersion(2)
 public Object hello2(){
 return "version 2";
    }

 @RequestMapping("hello")
 @ApiVersion(5)
 public Object hello5(){
 return "version 5";
    }


}



重新建立URL+apiversion 与Controller方法之之间的关系需要扩展一下我们的SpringMVC的两个类RequestHandlerMapping,RequestCondition来实现。其实现大致逻辑如下图:




java api version 版本控制 java接口版本管理_版本号


实现代码:

自定义ApiRequestCondition 实现RequestCondition


public class ApiRequestCondition implements RequestCondition<ApiRequestCondition> {

 private int apiVersion;

 public ApiRequestCondition(int apiVersion) {
 this.apiVersion = apiVersion;
    }
 public int getApiVersion() {
 return apiVersion;
    }

 @Override
 public ApiRequestCondition combine(ApiRequestCondition other) {
 // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
 return new ApiRequestCondition(other.getApiVersion());
    }

 @Override
 public int compareTo(ApiRequestCondition other, HttpServletRequest request) {
 //对符合请求版本的版本号进行排序
 return other.getApiVersion() - this.apiVersion;
    }

 @Override
 public ApiRequestCondition getMatchingCondition(HttpServletRequest request) {
 //设置默认版本号,请求版本号错误时使用最新版本号的接口
 Integer version=10000;
 //得到请求版本号
 String apiversion=request.getHeader("apiversion");
 if(StringUtils.isNotEmpty(apiversion)){
            Matcher m = Pattern.compile("v(d+)").matcher(apiversion);
 if (m.find()) {
                version = Integer.valueOf(m.group(1));
            }
        }
 // 返回小于等于请求版本号的版本
 if (version >= this.apiVersion){
 return this;
        }
 return null;
    }
}


重写RequestMappingHandlerMapping


public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

 @Override
 protected RequestCondition<ApiRequestCondition> getCustomTypeCondition(Class<?> handlerType) {
 ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
 return createCondition(apiVersion);
    }

 @Override
 protected RequestCondition<ApiRequestCondition> getCustomMethodCondition(Method method) {
 ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
 return createCondition(apiVersion);
    }

 private RequestCondition<ApiRequestCondition> createCondition(ApiVersion apiVersion) {
 return apiVersion == null ? null : new ApiRequestCondition(apiVersion.value());
    }
}


配置MVC使用自定义的CustomRequestMappingHandlerMapping (因为重写RequestMappingHandlerMapping 后会影响swagger的使用,所以在这里配置上swagger的资源路径映射)


@EnableSwagger2
@Configuration
public class WebApiVesionConfig extends WebMvcConfigurationSupport{



 @Override
 protected void addInterceptors(InterceptorRegistry registry) {
 super.addInterceptors(registry);
    }

 @Override
    @Bean
 public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
        handlerMapping.setOrder(0);
        handlerMapping.setInterceptors(getInterceptors());
 return handlerMapping;
    }

 @Override
 public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
 super.addResourceHandlers(registry);
    }
 /**
     * 配置servlet处理
     */
 @Override
 public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }



}


测试结果无误:


java api version 版本控制 java接口版本管理_ide_02