【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版本控制的意义所在。
相关文章:

【Spring Boot】构建RESTful服务 — 实战:实现Web API版本控制
实战:实现Web API版本控制 前面介绍了Spring Boot如何构建RESTful风格的Web应用接口以及使用Swagger生成API的接口文档。如果业务需求变更,Web API功能发生变化时应该如何处理呢?可以通过Web API的版本控制来处理。 1.为什么进行版本控制 …...

6.Web后端开发【SpringBoot入门】
文章目录 1 SpringBoot快速入门1.1 Web分析 2. HTTP协议2.1 HTTP-概述2.1.1 介绍2.2.2 特点 2.2 HTTP-请求协议2.3 HTTP-响应协议2.3.1 格式介绍2.3.2 响应状态码 常见的相应状态码 3 WEB服务器3.1 服务器概述 1 SpringBoot快速入门 Spring的官网Spring Boot 可以帮助我们非常…...

[ubuntu]ubuntu18.04使用自带共享桌面实现vncserver连接
vncserver有很多方法比如你安装vnc4server,tightvncserver,x11vnc等都可以实现vnc局域网连接,今天使用系统共享桌面设置vnc连接 Ubuntu开启远程桌面 Ubuntu18.04使用gnome桌面环境,系统自带屏幕共享和远程登录功能,默认使用的是vino作为VNC…...

docker启用cgroup v2
要求 本人的操作系统是kali,基于debian docker info如果你这里是2那么说明启用了,如果是1,那么就未启用 对于Docker来说,Cgroups v2的使用需要满足以下条件: Linux内核版本在4.15以上。 uname -r 系统已经启用Cgro…...
Java课题笔记~ Axios
Axios 对原生的AJAX进行封装,简化书写。 Axios官网是:https://www.axios-http.cn 2.1 基本使用 axios 使用是比较简单的,分为以下两步: 引入 axios 的 js 文件 <script src"js/axios-0.18.0.js"></script…...
ip地址和地理位置有关系吗
在互联网时代,网络已经成为了人们生活中不可或缺的一部分。而在网络通信中,IP地址扮演着非常重要的角色。那么,IP地址和地理位置之间是否有关系呢?虎观代理小二二将从以下几个方面进行探讨。 一、IP地址和地理位置的基本概念 首…...
mac指定node版本 mac node版本降级 mac切换node版本
本文解决问题: mac指定node版本 mac切换node版本 mac node版本降级 第一步 进行nvm 安装操作 brew install nvm 执行 nvm --version nvm --version 出现zsh: command not found: nvm问题去进行配置第二步 nvm配置 1.输入: vim ~/.bash_profile 点击 i 进行插入…...

C# Windows登录界面进行截图,控制鼠标键盘等操作实现(一)
首先常规的账户进程是没办法获取登录界面的信息的,因为登录界面已经不在某个账户下了,登录界面显示了每一个账户的切换。所以得使用System权限的进程。 那么Windows系统究竟是怎么将登录界面与用户桌面隔离开的呢?首先先通过一些Windows操作系…...

因果推断(五)基于谷歌框架Causal Impact的因果推断
因果推断(五)基于谷歌框架Causal Impact的因果推断 除了传统的因果推断外,还有一些机器学习框架可以使用,本文介绍来自谷歌框架的Causal Impact。该方法基于合成控制法的原理,利用多个对照组数据来构建贝叶斯结构时间…...

VR全景加盟项目如何开展?如何共赢VR时代红利?
VR全景作为一个新兴蓝海项目,相信有着很多人刚接触VR行业的时候都会有这样的疑问:VR全景加盟后项目如何开展?今天,我们就从项目运营的三个阶段为大家讲解。 一、了解项目时 目前VR全景已经被应用到各行各业中去,学校、…...

Win10+anaconda+CUDA+pytorch+vscode配置
Win10anacondaCUDApytorchvscode配置 1.安装anaconda2.安装CUDA确认CUDA版本确认CUDA和pytorch版本安装CUDA 3.安装cudnn4.安装Pytorch5.vscode配置安装VScodevscode配置pytorch环境 1.安装anaconda 官网https://www.anaconda.com 下载安装,路径全英文然后记得有一…...
vue-router在vue2/3区别
构建选项区别 vue2-router const router-new VueRouter({mode:history,base:_name,})vue-next-router import { createRouter,createWebHistory} from vue-next-router const routercreateRouter({history:createHistory(/) })在上述代码中我们发现,vue2中的构建选项mode和ba…...
Apache Doris 入门教程33:统计信息
统计信息 统计信息简介 Doris 查询优化器使用统计信息来确定查询最有效的执行计划。Doris 维护的统计信息包括表级别的统计信息和列级别的统计信息。 表统计信息: 信息描述row_count表的行数data_size表的⼤⼩(单位 byte)update_rows收…...
有效需求的特征
如何区分优秀的软件需求和软件需求规格说明书(SRS)与可能导致问题的需求和规格说明书?在这篇文章中,我们将首先讨论单个需求应该具有的几种不同特性。然后,我们将讨论成功的SRS整体应具有的理想特征。 1.有效需求的特…...

基于51单片机无线温度报警控制器 NRF24L01 多路温度报警系统设计
一、系统方案 1、本设计默认采用STC89C52单片机,如需更换单片机请联系客服。 2、接收板LCD1602液晶实时显示当前检测的2点温度值以及对应的上下限报警值。发射板由DS18B20采集温度值,通过无线模块NRF24L01传给接收板。 3、按键可以设置温度上下限值&…...
Spring Data JPA的@Entity注解
一、示例说明 rules\CouponTypeConverter.java Converter public class CouponTypeConverterimplements AttributeConverter<CouponType, String> {Overridepublic String convertToDatabaseColumn(CouponType couponCategory) {return couponCategory.getCode();}Overr…...

CANoe panel中,Path Dialog如何保存选择的文件路径
这里写目录标题 Path Dialog控件的设置系统变量和环境变量 Path Dialog控件的设置 过滤加载的文件类型 填写格式为:Hex file |.hex 其中Hex file为自定义name,.hex为你想识别的文件类型 系统变量和环境变量 系统变量:在canoe的Environmen…...

关于es中索引,倒排索引的理解
下面是我查询进行理解的东西 也就是说我们ES中的索引就相当于我们mysql中的数据库表,索引库就相当于我们的数据库,我们按照mapping规则会根据相应的字段(index为true默认)来创建倒排索引,这个倒排索引就相当于我们索引…...

k8s service (二)
K8s service (二) Endpoint Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod访问地址,它是根据service匹配文件中selector描述产生的。 一个Service由一组Pod组成,这些Pod通过Endpoints…...

桌面软件开发框架 Electron、Qt、WPF 和 WinForms 怎么选?
一、Electron Electron 是一个基于 Web 技术的跨平台桌面应用程序开发框架。它使用 HTML、CSS 和 JavaScript 来构建应用程序界面,并借助 Chromium 渲染引擎提供强大的页面渲染能力。Electron 的主要特点包括: 跨平台:Electron 可以在 Windows、macOS 和 Linux 等多个主流操…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...

Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...