6.4 实战:实现 Web API 版本控制

news2024/9/21 20:39:20

第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 进行有效的版本控制。通过增加版本号来区分对应的版本,来满足各个接口调用方的需求。版本号的使用有以下几种方式:

  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 等,然后根据不同的版本选择执行不同的方法。

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

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;

/**
 * 自定义版本号标记注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
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));
            if(version >= this.apiVersion) {
                return this;
            }
        }
        return null;
    }

    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        return other.getApiVersion() - this.apiVersion;
    }
}

(3)自定义匹配的处理程序

创建 ApiRequestMappingHandlerMapping 类并继承 RequestMappingHandlerMapping 类,重写 getCustomTypeCondition()、getCustomMethodCondition() 方法,实现自定义的匹配处理程序。

ApiRequestMappingHandlerMapping.java

package com.example.restfulproject.comm.processor;

import com.example.restfulproject.comm.annotation.ApiVersion;
import com.example.restfulproject.comm.condition.ApiVersionCondition;
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<?> getCustomTypeCondition(Class<?> handlerType) {
        return createCondition(handlerType);
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        return createCondition(method.getClass());
    }
}

(4)配置注册自定义的 RequestMappingHandlerMapping

创建 WebMvcRegistrationsConfig 配置类并实现 WebMvcRegistrations 接口,重写 getRequestMappingHandlerMapping() 方法,将上面创建的 ApiRequestMappingHandlerMapping 类注册到系统中。

WebMvcRegistrationsConfig.java

package com.example.restfulproject.comm.config;

import com.example.restfulproject.comm.processor.ApiRequestMappingHandlerMapping;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * 配置注册自定义的 RequestMappingHandlerMapping
 */
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiRequestMappingHandlerMapping();
    }
}

2、配置实现接口

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

OrderV1Controller.java

package com.example.restfulproject.controller;

import com.example.restfulproject.comm.annotation.ApiVersion;
import com.example.restfulproject.comm.utils.JSONResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * V1 版本的接口定义
 */
@ApiVersion(value = 1)
@RestController
@RequestMapping("/api/{version}/order") // 使用 @RequestMapping({"/api/order", "/api/{version}/order"}),可以保证升级接口后,原有接口 /api/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 queryOrderById(@PathVariable String orderId) {
        System.out.println("V1 获取订单详情成功:" + orderId);
        return JSONResult.ok("V1 获取订单详情成功");
    }
}

备注:上面的示例中,假设原有接口是 /api/order,使用 @RequestMapping({“/api/order”, “/api/{version}/order”}),可以保证升级接口后,原有接口的调用方不受影响。

OrderV2Controller.java

package com.example.restfulproject.controller;

import com.example.restfulproject.comm.annotation.ApiVersion;
import com.example.restfulproject.comm.utils.JSONResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * V2 版本的接口定义
 */
@ApiVersion(value = 2)
@RestController
@RequestMapping("/api/{version}/order")
public class OrderV2Controller {
    @GetMapping("/detail/{orderId}")
    public JSONResult queryOrderById(@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订单列表接口");
    }
}

3、验证测试

启动项目,查看版本控制是否生效。

(1)删除订单接口:

http://localhost:8080/api/v1/order/delete/20011
V1删除订单接口.png

http://localhost:8080/api/v2/order/delete/20011
V2删除订单接口.png

(2)获取订单详情接口:

http://localhost:8080/api/v1/order/detail/20011
V1获取订单详情接口.png

http://localhost:8080/api/v2/order/detail/20011
V2获取订单详情接口.png

(3)新增订单列表接口:

http://localhost:8080/api/v1/order/list
V1新增订单列表接口.png

http://localhost:8080/api/v2/order/list
V2新增订单列表接口.png

(4)获取V5版本(假设V5版本不存在,最新版本只有V2)的订单详情接口:

http://localhost:8080/api/v5/order/detail/20011
V5获取订单详情接口.png

以上验证情况说明 Web API 版本控制配置成功,实现了旧版本的稳定和新版本的更新。

  1. 当请求正确的版本地址时,会自动匹配版本的对应接口。(示例:“获取订单详情接口”)
  2. 当请求的版本大于当前版本时,默认匹配最新的版本。(示例:“获取V5版本的订单详情接口”)
  3. 高版本会默认继承低版本的所有接口。实现版本升级只关注变化的部分,没有变化的部分会自动平滑升级,这就是所谓的版本继承。(示例:“删除订单接口”)
  4. 高版本的接口的新增和修改不会影响低版本。(示例:“新增订单列表接口”)

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

来源:《Spring Boot 从入门到实战》学习笔记

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

易错:List中的add方法添加对象时出现重复的问题

错误&#xff1a; 用list存储User对象信息&#xff0c;当存储多个对象时&#xff0c;发现存储的数据都是一样的 之前代码&#xff1a; User user new User();List<User> list new ArrayList<>();for (int i 0; i < 5; i) {user.setName("Tom"i);…

drf-yasg —— Django REST Framework 文档生成

drf-yasg —— Django REST Framework 文档生成 drf-yasg 安装及全局配置 安装以及这个官方文档非常详细的描述了&#xff0c;我就不多说了。 配置好并运行 Django 项目以后&#xff0c;就可以使用浏览器访问 /swagger/ 和 /redoc/ &#xff08;链接取决于你的 urls 的配置&am…

看见统计——第五章 统计推断:贝叶斯学派

看见统计——第五章 统计推断&#xff1a;贝叶斯学派 引言 推理的频率学派认为&#xff0c;概率在本质上是与频率联系在一起的。这种解释实际上是很自然的。按照频率学派的说法&#xff0c;一枚公平的硬币出现人头的概率是1/2。简单地说&#xff0c;在同一个硬币的无限次独立…

打造一款日志分析工具

一、简介 作为一名安全从业者&#xff0c;网络安全事件的应急响应工作是必不可少的&#xff0c;那么在应急支撑时&#xff0c;针对大量的日志数据便需要借助自动化工具实现快速的归类检测&#xff0c;并提取出所需的关键日志数据。本篇文章主要通过利用python语言编写一款web日…

自带超多工具,好用又免费,这3款手机浏览器你用过了吗

手机浏览器是我们手机中必备的一款软件APP&#xff0c;一款好用的浏览器&#xff0c;可以帮助我们提高工作效率&#xff0c;节省时间。对于懒癌患者来说&#xff0c;手机上安装各种各种的app很麻烦&#xff0c;下面给大家介绍自带超多的工具&#xff0c;好用且免费的浏览器&…

SpringCloud网关Zuul和GateWay区别

getway和zuul没有进行参数调优的时候&#xff0c;getway的性能会远高于zuul。 分析&#xff0c;在空负载的时候&#xff0c;SpringCloud Gateway比zuul 1 性能高50%左右&#xff0c;在模拟处理50ms业务后&#xff0c;&#xff0c;SpringCloud Gateway比zuul 1 性能高9倍左右。 …

嵌入式Qt 开发一个视频播放器

上篇文章&#xff1a;嵌入式 Qt开发一个音乐播放器&#xff0c;使用Qt制作了一个音乐播放器&#xff0c;并在OK3568开发板上进行了运行测试&#xff0c;实际测试效果还不错。 本篇继续来实现一个Qt视频播放器软件&#xff0c;可以实现视频列表的显示与选择播放等&#xff0c;先…

【干货】如何用低代码帮助企业实施OKR?

近年来受疫情的影响&#xff0c;许多企业都开始使用 OKR来进行目标管理。OKR是一套让企业持续保持活力的有效管理工具&#xff0c;能够帮助企业实现目标、激励员工、增加团队凝聚力、减少组织内耗&#xff0c;从而进一步实现创新。但是在实际布局中&#xff0c;很多企业在使用 …

基础面试题 :大端、小端及转换方式

理解网络中大端和小端往往是一道基础面试题 &#xff0c;这里作为记录和整理&#xff0c;希望能帮到大家 目录 前言 一、字节序 二、什么小端顺序 三、什么大端顺序 四、处理器体系所属网络字节顺序 五、大小端转换 1、大端整形转换为小端 2、小端转换为小端 3、…

知乎x-zse-96 参数补环境

本文精工学习参考 目标链接 aHR0cHM6Ly93d3cuemhpaHUuY29tL3NlYXJjaD90eXBlPWNvbnRlbnQmcT1weXRob24接口分析 参数x-zse-93&#xff1a;相当于版本号 参数x-zse-96&#xff1a;看起来需要破解 参数x-zst-81&#xff1a;请求发现可以置空 综上所述x-zse-96才需要逆向。 参数…

中间件安全—Apache常见漏洞

中间件安全—Apache常见漏洞1.Apache常见漏洞1.1.Apache介绍1.2.Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09;1.2.1.漏洞介绍1.2.2.漏洞环境1.2.2.1.运行漏洞环境1.2.2.2.访问漏洞环境1.2.3.漏洞复现1.2.3.1.拦截1.2.3.2.添加换行1.2.3.3.访问文件1.3.Apa…

[机器学习]卷积神经网络DLC

一、基本结构 CNN的大概模式可以总结为&#xff1a;卷积层池化层全连接层激活函数 而一些比较大型的网络如VGG一般将CNN作为构成单元进行堆叠&#xff0c;而内部卷积核和池化也可以堆叠多个。各个部分的功能如下&#xff1a; 卷积&#xff1a;特征提取 池化&#xff1a;降维和防…

硬件设备 之一 详解 JTAG、SWD 接口、软 / 硬件断点、OpenOCD、J-link

JTAG 和 SWD 在嵌入式开发中可以说是随处可见&#xff0c;他们通常被用来配合 J-Link 、ULINK、ST-LINK 等仿真器在线调试嵌入式程序。此外&#xff0c;还有飞思卡尔芯片中的 Background debug mode&#xff08;BDM&#xff09; 接口&#xff0c;Atmel 芯片中的 debugWIRE &…

文本生成图像应用指南【Stable Diffusion】

Stable Diffusion 是一种文本到图像的潜在扩散模型&#xff0c;由来自 CompVis、Stability AI 和 LAION 的研究人员和工程师创建。 它使用来自 LAION-5B 数据库子集的 512x512 图像进行训练。 稳定扩散&#xff0c;生成人脸&#xff0c;也可以在自己的机器上运行&#xff0c;如…

车载技术开发—{Android CarFrameWork}

Android Automotive平台 Android Automotive是通过Android的通用框架&#xff0c;语言和API来实现的一个全栈&#xff0c;开源&#xff0c;高度可定制的平台。 Android Automotive与整个Android生态系统的关系 Android Automotive是Android的一部分。 Android Automotive不是…

pbootcms被黑木马问题(3)

昨天经过同事告知发现了很早之间做的几个企业官方都中木马了,然后看了一下木马情况,跟之间的两次都有所不同,这里记录一下新的木马的清理过程,有遇到的朋友可以借鉴一下。&#xff08;之前有做过一些防止批量扫站的措施&#xff0c;因为嫌麻烦就没有给这些网站上进行修改&#…

【Spark分布式内存计算框架——Spark SQL】13. 自定义UDF函数

第七章 自定义UDF函数 无论Hive还是SparkSQL分析处理数据时&#xff0c;往往需要使用函数&#xff0c;SparkSQL模块本身自带很多实现公共功能的函数&#xff0c;在org.apache.spark.sql.functions中。SparkSQL与Hive一样支持定义函数&#xff1a;UDF和UDAF&#xff0c;尤其是U…

黑格尔的实践观探究

&#xff08;江苏大学马克思主义学院 212000&#xff09;一、引言人的独特性在于实践活动&#xff0c;以及由实践活动带来的人类社会的不断进化与发展。人类的实践史体现了人的全部本质。但是&#xff0c;人类从理论的高度反思自己的实践活动&#xff0c;尤其是在哲学的层面上进…

【基础算法】之 冒泡排序优化

冒泡排序思想基本思想: 冒泡排序&#xff0c;类似于水中冒泡&#xff0c;较大的数沉下去&#xff0c;较小的数慢慢冒起来&#xff08;假设从小到大&#xff09;&#xff0c;即为较大的数慢慢往后排&#xff0c;较小的数慢慢往前排。直观表达&#xff0c;每一趟遍历&#xff0c;…

大数据框架之Hadoop:MapReduce(三)MapReduce框架原理——shuffle机制

3.3.1Shuffle机制 Map方法之后&#xff0c;Reduce方法之前的数据处理过程称之为Shuffle。 3.3.2Partition分区 1、问题引出 要求将统计结果按照条件输出到不同文件中&#xff08;分区&#xff09;。比如&#xff1a;将统计结果按照手机归属地不同省份输出到不同文件中&#…