Spring Boot入门系列(二十一)如何优雅的设计 Restful API 接口版本号,实现 API 版本控制!
前面介绍了Spring Boot 如何快速实现Restful api 接口,并以人员信息为例,设计了一套操作人员信息的接口。不清楚的可以看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1657780.html。
有些人可能会问,为什么我看到很多公司的api接口文档里面,都有/api/v1/ 这样的地址呢?其实,/api 就是为了和一般的业务地址区分,标明这个地址是api 的接口。v1 则代表版本号。
可能很多人又会问了,为什么要版本号呢?那么,接下来就聊一聊Restful 接口为什么要加版本号? 如何优雅的设计 Restful API 接口版本号?
一、为什么加版本号
一般来说,api 接口是提供给其他系统或是其他公司使用,不能随意频繁的变更。然而,需求和业务不断变化,接口和参数也会发生相应的变化。如果直接对原来的接口进行修改,势必会影响线其他系统的正常运行。这就必须对api 接口进行有效的版本控制。
例如,添加用户的接口,由于业务需求变化,接口的字段属性也发生了变化而且可能和之前的功能不兼容。为了保证原有的接口调用方不受影响,只能重新定义一个新的接口。
Api 版本控制的方式:
1、域名区分管理,即不同的版本使用不同的域名,v1.api.test.com,v2.api.test.com
2、请求url 路径区分,在同一个域名下使用不同的url路径,test.com/api/v1/,test.com/api/v2
3、请求参数区分,在同一url路径下,增加version=v1或v2 等,然后根据不同的版本,选择执行不同的方法。
实际项目中,一般选择第二种:请求url路径区分。因为第二种既能保证水平扩展,有不影响以前的老版本。
二、Spring Boot如何实现
实现方案:
1、首先创建自定义的@APIVersion 注解和自定义URL匹配规则ApiVersionCondition。
2、然后创建自定义的 RequestMappingHandlerMapping 匹配对应的request,选择符合条件的method handler。
1、创建自定义注解
首先,在com.weiz.config 包下,创建一个自定义版本号标记注解 @ApiVersion。
package com.weiz.config; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* API版本控制注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
/**
* @return 版本号
*/
int value() default 1;
}
说明:
ApiVersion 为自定义的注解,API版本控制,返回对应的版本号。
2、自定义url匹配逻辑
创建 ApiVersionCondition 类,并继承RequestCondition 接口,作用是:版本号筛选,将提取请求URL中版本号,与注解上定义的版本号进行比对,以此来判断某个请求应落在哪个controller上。
在com.weiz.config 包下创建ApiVersionCondition 类,重写 RequestCondition,创建自定义的url匹配逻辑。
package com.weiz.config; import org.springframework.web.servlet.mvc.condition.RequestCondition; import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern; 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;
} private 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;
} @Override
public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
return apiVersionCondition.getApiVersion() - this.apiVersion;
}
}
当方法级别和类级别都有ApiVersion注解时,二者将进行合并(ApiVersionRequestCondition.combine)。最终将提取请求URL中版本号,与注解上定义的版本号进行比对,判断url是否符合版本要求。
3、自定义匹配的处理器
在com.weiz.config 包下创建 ApiRequestMappingHandlerMapping 类,重写部分 RequestMappingHandlerMapping 的方法。
package com.weiz.config; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private static final String VERSION_FLAG = "{version}"; private 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);
}
}
4、配置注册自定义的RequestMappingHandlerMapping
重写请求过处理的方法,将之前创建的 ApiRequestMappingHandlerMapping 注册到系统中。
package com.weiz.config; import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiRequestMappingHandlerMapping();
}
}
上面四步,把api 版本控制配置完了。代码看着复杂,其实都是重写spring boot 内部的处理流程。
测试
配置完成之后,接下来编写测试的控制器进行测试。
1、在Controller/api 目录下,分别创建UserV1Controller 和 UserV2Controller
UserV1Controller
@RequestMapping("api/{version}/user")
@RestController
public class UserV1Controller {
@GetMapping("/test")
public String test() {
return "version1";
}
@GetMapping("/extend")
public String extendTest() {
return "user v1 extend";
}
}
UserV2Controller
@RequestMapping("api/{version}/user")
@RestController
@ApiVersion(2)
public class UserV2Controller {
@GetMapping("/test")
public String test() {
return "user v2 test";
}
}
2、启动项目后,输入相关地址,查看版本控制是否生效
测试结果:
正确的接口地址

继承的接口地址

说明:
上图的前两个截图说明,请求正确的版本地址,会自动匹配版本的对应接口。当请求的版本大于当前版本时,默认匹配当前版本。
最后
以上,就把Spring Boot 如何优雅的设计 Restful API 接口版本号,实现 API 版本控制介绍完了。版本控制和权限验证是rest api 的基础,虽然看着比较复杂,但是理解了,要实现还是比较简单的。
这个系列课程的完整源码,也会提供给大家。大家关注我的微信公众号(架构师精进),回复:springboot源码。获取这个系列课程的完整源码。
Spring Boot入门系列(二十一)如何优雅的设计 Restful API 接口版本号,实现 API 版本控制!的更多相关文章
- Spring Boot入门系列(二十)快速打造Restful API 接口
spring boot入门系列文章已经写到第二十篇,前面我们讲了spring boot的基础入门的内容,也介绍了spring boot 整合mybatis,整合redis.整合Thymeleaf 模板 ...
- Spring Boot入门系列(十六)使用pagehelper实现分页功能
之前讲了Springboot整合Mybatis,然后介绍了如何自动生成pojo实体类.mapper类和对应的mapper.xml 文件,并实现最基本的增删改查功能.接下来要说一说Mybatis 的分页 ...
- Spring Boot入门系列(十七)整合Mybatis,创建自定义mapper 实现多表关联查询!
之前讲了Springboot整合Mybatis,介绍了如何自动生成pojo实体类.mapper类和对应的mapper.xml 文件,并实现最基本的增删改查功能.mybatis 插件自动生成的mappe ...
- Spring Boot入门系列(十三)如何实现事务
前面介绍了Spring Boot 中的整合Mybatis并实现增删改查.不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1 ...
- spring boot 入门操作(二)
spring boot入门操作 使用FastJson解析json数据 pom dependencies里添加fastjson依赖 <dependency> <groupId>c ...
- Spring boot入门(二):Spring boot集成MySql,Mybatis和PageHelper插件
上一篇文章,写了如何搭建一个简单的Spring boot项目,本篇是接着上一篇文章写得:Spring boot入门:快速搭建Spring boot项目(一),主要是spring boot集成mybat ...
- Spring Boot 入门系列(二十二)使用Swagger2构建 RESTful API文档
前面介绍了如何Spring Boot 快速打造Restful API 接口,也介绍了如何优雅的实现 Api 版本控制,不清楚的可以看我之前的文章:https://www.cnblogs.com/zha ...
- Spring Boot 入门系列(二十三)整合Mybatis,实现多数据源配置!
d之前介绍了Spring Boot 整合mybatis 使用注解方式配置的方式实现增删改查以及一些复杂自定义的sql 语句 .想必大家对spring boot 项目中,如何使用mybatis 有了一定 ...
- Spring Boot 入门系列(二十四)多环境配置,3分钟搞定!
之前讲过Spring Boot 的系统配置和自定义配置,实现了按照实际项目的要求配置系统的相关熟悉.但是,在实际项目开发过程中,需要面对不同的环境,例如:开发环境,测试环境,生产环境.各个环境的数据库 ...
随机推荐
- Dos拒绝服务Syn-Flood泛洪攻击--Smurf 攻击(一)
Dos拒绝服务利用程序漏洞或一对一资源耗尽的Denial of Service 拒绝服务DDos 分布式拒绝服务 多对一 Syn-Flood泛洪攻击 发送syn包欺骗服务器建立半连接 攻击代码,利用s ...
- c#中的ReadOnlySequenceSegment<T>和ReadOnlySequenceSegment<T>
关于.net core高性能编程中的Span<T>和Memory<T>网上资料很多,这里就不说了.今天一直在看ReadOnlySequenceSegment<T>和 ...
- Android实现二值点阵图识别
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star 前言 我这几天在做一个东西,就是一张像二维码这样的 n*n ...
- IDEA使用 live template添加groovy脚本给方法,类,js方法添加注释(转载)
IDEA添加Live Template: File->Setting->Editor->Live Templates Abbreviation: * Template text: * ...
- nginx实战(1):宝塔设置反向代理
以下操作适用于默认80端口转其他地址非80端口情况. 添加网站 注意:因我只是拿来当反向代理来使用,所PHP为纯静态模式. 开启反向代理 注:目标URL为最终目的地,发送域名为默认,如设置后无效则修改 ...
- rocketmq-console修改logo,修改ip,修改port及完整编译安装图文版
一.下载源码到本地 这里使用IDEA,作为编译工具 https://gitee.com/mrliuNumberOne/rocketmq-externals.git 导入成功后如图: 二.Maven编译 ...
- hystrix(8) 插件
上一节讲到HystrixCommand的执行流程. Hystrix内部将一些模块实现成了插件,并且提供了用户提供自己的实现,通过配置来替换插件.Hystrix提供了5个插件,分别为并发相关插件(Hys ...
- 2.JAVA自带的序列化反序列化机制
- SpringBoot普通消息队列线程池配置
1 package com.liuhuan.study.config; 2 3 import com.google.common.util.concurrent.ThreadFactoryBuilde ...
- svg的学习
svg的学习 1,初步了解 1,大致看了一下svg的简介,在图形的操作和展示上有很大的优势,例如不会失精:灵活的dom操作:很好的兼容性(IE需要下载插件).so,是一门值得深究的前端课程: 2,看了 ...