第6章 构建 RESTful 服务
6.1 RESTful 简介
6.2 构建 RESTful 应用接口
6.3 使用 Swagger 生成 Web API 文档
6.4 实战:实现 Web API 版本控制
6.4 实战:实现 Web API 版本控制
如果业务需求变更,Web API 功能发生变化时应该如何处理呢?总不能通知所有的调用方修改吧?接下来好好研究一下 Web API 的版本控制。
6.4.1 为什么进行版本控制
一般来说,Web API 是提供给其他系统或其他公司使用的,不能随意频繁地变更。然而,由于需求和业务不断变化,Web API 也会随之不断修改。如果直接对原来的接口修改,势必会影响其他系统的正常运行。
例如,系统中用户添加的接口 /api/user 由于业务需求的变化,接口的字段属性也发横了变化,而且可能与之前的功能不兼容。为了保证原有的接口调用方不受影响,只能重新定义一个新的接口:/api/user2,这使得接口非常臃肿难看,而且极难维护。
那么如何做到在不影响现有调用方的情况下,优雅地更新接口的功能呢?最简单高效的办法就是对 Web API 进行有效的版本控制。通过增加版本号来区分对应的版本,来满足各个接口调用方的需求。版本号的使用有以下几种方式:
- 通过
域名
进行区分,即不同的版本使用不同的域名,如 v1.api.test.com、v2.api.test.com。 - 通过
请求URL路径
进行区分,在同一个域名下使用不同的 URL 路径,如 test.com/api/v1/、test.com/api/v2/。 - 通过
请求参数
进行区分,在同一个 URL 路径下增加 version=v1 或 v2 等,然后根据不同的版本选择执行不同的方法。
在实际的项目中,一般选择第二种方式,因为这样既能保证水平扩展,又不影响以前的老版本。
6.4.2 Web API 的版本控制
Spring Boot 对 RESTful 的支持非常全面,因而实现 RESTful API 非常简单,同样对于 API 版本控制也有相应的实现方案,实现步骤如下:
1、API版本控制配置
(1)创建自定义的 @ApiVersion 注解。
(2)创建自定义 URL 匹配规则 ApiVersionCondition 类(实现 RequestCondition 接口)。
(3)创建自定义的映射处理程序 ApiRequestMappingHandlerMapping 类(继承 RequestMappingHandlerMapping 类)。
(4)创建 WebMvcRegistrationsConfig 配置类(实现 WebMvcRegistrations 接口),将自定义的映射处理程序 ApiRequestMappingHandlerMapping 注册到系统中。
2、配置实现接口
编写测试的控制器,实现相关接口的测试。
1、API版本控制配置
(1)创建自定义注解
创建一个自定义版本号标记注解 @ApiVersion。
ApiVersion.java
package com.example.restfulproject.comm.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义版本号标记注解
*/
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
/**
* @return 版本号
*/
int value() default 1;
}
在上面的示例中,创建了 @ApiVersion 自定义注解用于 API 版本控制,并返回了对应的版本号。
(2)自定义 URL 匹配逻辑
创建 ApiVersionCondition 类并实现 RequestCondition 接口,重写 combine()、getMatchingCondition()、compareTo() 方法,其作用是进行版本号筛选,将提取请求URL中的版本号
与注解上定义的版本号
进行对比,以此来判断某个请求应落在哪个控制器上。
ApiVersionCondition.java
package com.example.restfulproject.comm.condition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 自定义 URL 匹配逻辑
*/
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+).*");
private int apiVersion;
public ApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
private int getApiVersion() {
return apiVersion;
}
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
return new ApiVersionCondition(other.getApiVersion());
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
if(m.find()) {
Integer version = Integer.valueOf(m.group(1)