【Spring Boot】构建RESTful服务 — 实战:实现Web API版本控制

实战:实现Web API版本控制

前面介绍了Spring Boot如何构建RESTful风格的Web应用接口以及使用Swagger生成API的接口文档。如果业务需求变更,Web API功能发生变化时应该如何处理呢?可以通过Web API的版本控制来处理。

1.为什么进行版本控制

一般来说,Web API是提供给其他系统或其他公司使用的,不能随意频繁地变更。然而,由于需求和业务不断变化,Web API也会随之不断修改。如果直接对原来的接口修改,势必会影响其他系统的正常运行。

例如,系统中用户添加的接口/api/user由于业务需求的变化,接口的字段属性也发生了变化,而且可能与之前的功能不兼容。为了保证原有的接口调用方不受影响,只能重新定义一个新的接口:/api/user2,这使得接口维护难度增加。

如何做到在不影响现有调用方的情况下,优雅地更新接口的功能呢?

最简单高效的办法就是对Web 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等,然后根据不同的版本选择执行不同的方法。

在实际项目开发中,一般选择第二种方式,因为这样既能保证水平扩展,又不影响以前的老版本。

2.Web API的版本控制

Spring Boot对RESTful的支持非常全面,因而实现RESTful API非常简单,同样对于API版本控制也有相应的实现方案:

1)创建自定义的@APIVersion注解。

2)自定义URL匹配规则ApiVersionCondition。

3)使用RequestMappingHandlerMapping创建自定义的映射处理程序,根据Request参数匹配符合条件的处理程序。

下面通过示例程序来演示Web API如何增加版本号。

步骤01 创建自定义注解。

创建一个自定义版本号标记注解@ApiVersion。实现代码如下:

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface ApiVersion {/*** @return版本号*/int value() default 1;}

在上面的示例中,创建了ApiVersion自定义注解用于API版本控制,并返回了对应的版本号。

步骤02 自定义URL匹配逻辑。

接下来定义URL匹配逻辑,创建ApiVersionCondition类并继承RequestCondition接口,其作用是进行版本号筛选,将提取请求URL中的版本号与注解上定义的版本号进行对比,以此来判断某个请求应落在哪个控制器上。实现代码如下:

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;}@Overridepublic ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {return new ApiVersionCondition(apiVersionCondition.getApiVersion());}@Overridepublic 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;}@Overridepublic int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {return apiVersionCondition.getApiVersion() - this.apiVersion;}
}

在上面的示例中,通过ApiVersionCondition类重写RequestCondition定义URL匹配逻辑。

当方法级别和类级别都有ApiVersion注解时,通过ApiVersionRequestCondition.combine方法将二者进行合并。最终将提取请求URL中的版本号,与注解上定义的版本号进行对比,判断URL是否符合版本要求。

步骤03 自定义匹配的处理程序。

接下来实现自定义匹配的处理程序。先创建ApiRequestMappingHandlerMapping类,重写部分RequestMappingHandlerMapping的方法,实现自定义的匹配处理程序。示例代码如下:

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());}@Overrideprotected RequestCondition<?> getCustomMethodCondition(Method method) {return createCondition(method.getClass());}@Overrideprotected RequestCondition<?> getCustomTypeCondition(Class<?> handerType) {return createCondition(handerType);}
}

步骤04 配置注册自定义的RequestMappingHandlerMapping。

创建WebMvcRegistrationsConfig类,重写getRequestMappingHandlerMapping( )的方法,将之前创建的ApiRequestMappingHandlerMapping注册到系统中。

public class WebMvcRegistrationsConfig implements WebMvcRegistrations {@Overridepublic RequestMappingHandlerMapping getRequestMappingHandlerMapping() {return new ApiRequestMappingHandlerMapping();}
}

通过以上4步完成API版本控制的配置。代码看起来复杂,其实都是重写Spring Boot内部的处理流程。

步骤05 配置实现接口。

配置完成之后,接下来编写测试的控制器(Controller),实现相关接口的测试。在Controller目录下分别创建OrderV1Controller和OrderV2Controller。示例代码如下:

//V1 版本的接口定义
@ApiVersion(value = 1)
@RestController
@RequestMapping("/api/{version}/order")
public class OrderV1Controller {@GetMapping("/delete/{orderId}")public JSONResult deleteOrderById(@PathVariable String orderId) {System.out.println("V1删除订单成功:" +orderId);return JSONResult.ok("V1删除订单成功");}@GetMapping("/detail/{orderId}")public JSONResult deleteOrderById(@PathVariable String orderId) {System.out.println("V1获取订单详情成功:" +orderId);return JSONResult.ok("V1获取订单详情成功");}
}//V2 版本的接口定义
@ApiVersion(value = 2)
@RestController
@RequestMapping("/api/{version}/order")
public class OrderV2Controller {@GetMapping("/delete/{orderId}")public JSONResult deleteOrderById(@PathVariable String orderId) {System.out.println("V2获取订单详情成功:" +orderId);return JSONResult.ok("V2获取订单详情成功");}@GetMapping("/list")public JSONResult list() {System.out.println("V2,获取list订单列表接口");return JSONResult.ok(200, "V2,新增list订单列表接口");}
}

在上面的示例中,我们在UserV1Controller中定义了/delete/{orderId}和/detail/{orderId}两个接口,在UserV2Controller中修改/detail/{orderId}接口,新增/list接口,然后使用@ApiVersion自定义注解设置两个Controller的版本号。

步骤06 验证测试。

配置完成之后启动项目,查看版本控制是否生效。在浏览器中分别访问api/v1/order/delete/20011和api/v2/order/ delete/20011订单删除接口,查看页面返回情况。如下图所示,调用V1和V2版本的order/ delete/20011订单删除接口,返回的都是“V1,删除订单成功”。这说明V2会默认继承V1的所有接口,新版本的原有接口功能保持不变。

在这里插入图片描述

接下来,在浏览器中分别访问api/v1/order/detail/20011和api/v2/order/delete/20011订单详情接口,查看页面返回情况。如下图所示,分别调用V1和V2版本的order/detail/20011订单详情接口,返回的是各自版本的接口信息,说明V2版本对order/detail订单详情接口的修改生效,同时也没有影响旧版本的订单详情接口。

在这里插入图片描述
在这里插入图片描述
最后,分别访问新增的order/list订单列表接口,查页面返回情况。如下图所示,请求V1的order/list订单列表返回404接口不存在,请求V2的order/list订单列表返回正确的结果,说明在高版本中新增的接口只在高版本中生效。
在这里插入图片描述

在这里插入图片描述
以上验证情况说明Web API的版本控制配置成功,实现了旧版本的稳定和新版本的更新。

1)当请求正确的版本地址时,会自动匹配版本的对应接口。

2)当请求的版本大于当前版本时,默认匹配最新的版本。

3)高版本会默认继承低版本的所有接口。实现版本升级只关注变化的部分,没有变化的部分会自动平滑升级,这就是所谓的版本继承。

4)高版本的接口的新增和修改不会影响低版本。

这些特性使得在升级接口时,原有接口不受影响,只关注变化的部分,没有变化的部分自动平滑升级。这样使得Web API更加简洁,这就是实现Web API版本控制的意义所在。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/99148.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++--深入类和对象(下)

续接上篇&#xff0c;接着来谈我们的类和对象的深入的知识&#xff0c;话不多说&#xff0c;我们即刻出发...... 目录 1.友元 1.1友元函数 输出流运算符的重载 1.2友元类 2.再谈构造函数 2.1构造函数体赋值和初始化列表 构造函数体赋值为何不能叫做初始化&#xff1f; …

ChatGPT-4: 半年的深度使用思考

几个月的时间一直在使用 ChatGpt-4&#xff0c;以口述语音转文字的形式说一下自己的体会。 1、选择版本 大前提&#xff1a;我使用的都是 GPT4 的版本。也就是说至少每个月要付费20$。 因为 3.5 的版本&#xff0c;实际上使用体验是非常差的&#xff0c;主要体现在答非所问上。…

[C语言]分支语句和循环语句

[C语言]分支语句和循环语句 文章目录 [C语言]分支语句和循环语句C语言语句分类分支语句if语法结构else的匹配规则switch语句switch语句中的breakswitch语句中default 循环语句while循环while循环中的break和continuefor循环for循环中的break和continuefor循环的变种do while循环…

小程序多图片组合

目录 子组件 index.js 子组件 index.wxml 子组件 index.wxss 父组件引用&#xff1a; 子组件&#xff1a;preview-image 子组件 index.js Component({properties: {previewData: {type: Array,default: [],observer: function (newVal, oldVal) {console.log(newVal, ol…

基于Pytorch实现的声纹识别系统

前言 本项目使用了EcapaTdnn、ResNetSE、ERes2Net、CAM等多种先进的声纹识别模型&#xff0c;不排除以后会支持更多模型&#xff0c;同时本项目也支持了MelSpectrogram、Spectrogram、MFCC、Fbank等多种数据预处理方法&#xff0c;使用了ArcFace Loss&#xff0c;ArcFace loss…

Java版本+企业电子招投标系统源代码+支持二开+Spring cloud tbms

​ 项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以…

【广州华锐互动】3D空间编辑器:一款简洁易用的VR/3D在线编辑工具

随着虚拟现实技术的不断发展&#xff0c;数字孪生技术的应用已经被广泛应用于产品设计和制作中&#xff0c;能充分发挥企业应用3D建模的优势&#xff0c;凸显了三维设计的价值&#xff0c;在生产阶段也能够充分发挥3D模型的作用。 如今&#xff0c;广州华锐互动开发的3D空间编辑…

C#程序配置读写例子 - 开源研究系列文章

今天讲讲关于C#的配置文件读写的例子。 对于应用程序的配置文件&#xff0c;以前都是用的ini文件进行读写的&#xff0c;这个与现在的json类似&#xff0c;都是键值对应的&#xff0c;这次介绍的是基于XML的序列化和反序列化的读写例子。对于ini文件&#xff0c;操作系统已经提…

动态内存开辟

动态内存开辟 1.动态内存开辟相关试题 题目1&#xff1a; void GetMemory(char *p) {p (char *)malloc(100); } void Test(void) {char *str NULL;GetMemory(str);strcpy(str, "hello world");printf(str); }解释&#xff1a;这里在Test函数中&#xff0c;只是将…

【Matter】基于Ubuntu 22.04搭建matter开发环境:chip-tool 配网之 matter-over-wifi

前言 主要是记录一下学习过程&#xff0c;梳理下思路&#xff0c;抛转~ 官方的开发环境&#xff0c;基于Linux版本&#xff0c;官方的环境是基于树莓派环境的&#xff0c;原理其实也比较明了&#xff0c;目的也比较明确&#xff0c;就是达到Linux 主机和wifi 路由在同一局域网…

flink jira 提交开源bug

注册apache issue账号,并申请flink空间的权限后. 提问题/bug 查看已经提交的问题:

获取excel中的图片(包含wps中嵌入单元格图片)

项目中有excel导入功能,并且需要导入excel中的图片;模板如图: 已知office中插入的图片为浮动形式;如图: wps中可以插入浮动图片,也可以插入嵌入单元格图片;如图: 并且在wps嵌入单元格形式的图片可以看到使用的是公式;如图: 问题来了,如何获取图片 并且将图片与单元格进行对应 …

Spring Boot+Redis 实现消息队列实践示例

Spring BootRedis 实现一个轻量级的消息队列 文章目录 Spring BootRedis 实现一个轻量级的消息队列0.前言1.基础介绍2.步骤2.1. 引入依赖2.2. 配置文件2.3. 核心源码 4.总结答疑 5.参考文档6. Redis从入门到精通系列文章 0.前言 本文将介绍如何利用Spring Boot与Redis结合实现…

uni-app自定义多环境配置,动态修改appid

背景 在企业级项目开发中&#xff0c;一般都会分为开发、测试、预发布、生产等多个环境&#xff0c;在工程化中使用不同的打包命令改变环境变量解决不同环境各种变量需要手动修改的问题&#xff0c;比如接口请求地址&#xff0c;不同环境的请求路径前缀都是不同的。在使用uni-…

【仿写tomcat】五、响应静态资源(访问html页面)、路由支持以及多线程改进

访问html页面 如果我们想访问html页面其实就是将本地的html文件以流的方式响应给前端即可&#xff0c;下面我们对HttpResponseServlet这个类做一些改造 package com.tomcatServer.domain;import com.tomcatServer.utils.ScanUtil;import java.io.IOException; import java.io…

Android企业项目开发实训室建设方案

一 、系统概述 Android企业项目开发作为新一代信息技术的重点和促进信息消费的核心产业&#xff0c;已成为我国转变信息服务业的发展新热点&#xff1a;成为信息通信领域发展最快、市场潜力最大的业务领域。互联网尤其是移动互联网&#xff0c;以其巨大的信息交换能力和快速渗透…

Postman返回了一个html页面

问题记录 调用公司的测试环境接口&#xff0c;从浏览器控制台接口处cCopy as cURL(cmd)&#xff0c;获取完整的请求内容&#xff0c;然后导入postman发起请求 提测时发现返回一个html页面&#xff0c;明显是被请求在网管处被拦截了&#xff0c;网关返回的这个报错html页面 …

商城-学习整理-高级-商城业务-Sentinel限流熔断降级Sleuth+Zipkin链路追踪(二十二)

目录 一、秒杀系统的架构二、SpringCloud Alibaba-Sentinel简介1、熔断降级限流什么是熔断什么是降级异同&#xff1a;什么是限流 2、Sentinel 简介官方文档&#xff1a;Sentinel 具有以下特征:Sentinel 分为两个部分: 3、Hystrix 与 Sentinel 比较4、整合 FeignSentinel 测试熔…

如何使用好Tik Tok标签流量?这些关键点要知道!

TikTok 每天有数百万用户创建大量内容。如何让你的内容受到关注&#xff1f;如何让视频能够最大限度获得曝光&#xff1f;答案之一是用好【关键词标签】&#xff0c;这将有利于你创作的内容展示到更多更合适的人面前&#xff0c;也可能会给你带来额外的流量。 要在TikTok上利用…

vscode用ssh远程连接linux

1、vscode是利用ssh远程连接linux的&#xff0c;所以首先确保vscode已经安装了这两个插件 2、点击左下角的连接 3、选择Connect to Host…… 5、按格式输入 ssh 主机名ip 比如我的&#xff1a;ssh mnt192.168.198.128 6、选择第一个打开配置文件&#xff0c;确保输入正确 7、…