Spring Boot - 在Spring Boot中实现灵活的API版本控制(下)_ 封装场景启动器Starter

文章目录

  • Pre
  • 设计思路
    • `@ApiVersion` 功能特性
    • 使用示例
    • 配置示例
  • Project
  • Starter Code
    • 自定义注解 ApiVersion
    • 配置属性类用于管理API版本
    • 自动配置基于Spring MVC的API版本控制
    • 实现WebMvcRegistrations接口,用于自定义WebMvc的注册逻辑
    • 扩展RequestMappingHandlerMapping的类,支持API版本路由
    • spring.factories
  • Test Code
    • 无版本控制
    • 多版本控制
    • v1
    • v2
    • Test

在这里插入图片描述

Pre

Spring Boot - 在Spring Boot中实现灵活的API版本控制(上)


设计思路

@ApiVersion 功能特性

  1. 支持类和方法上使用:

    • 优先级:方法上的注解优先于类上的注解。
    • 如果类和方法同时使用 @ApiVersion,则以方法上的版本为准。
  2. 支持多版本同时生效:

    • @ApiVersion 的参数是数组,可以配置多个版本。例如:@ApiVersion({1, 2}),此配置允许通过 v1v2 访问。
  3. 可配置前缀和后缀:

    • 默认前缀是 v,可以通过配置项 api-version.prefix 修改。
    • 默认没有后缀,但可以通过 api-version.suffix 配置。
  4. 使用简单:

    • 仅需一个注解即可完成版本控制。

使用示例

假设你有一个 UserController,需要支持 v1v2 的版本访问:

@RestController
@RequestMapping("/users")
public class UserController {@GetMapping@ApiVersion({1, 2})public List<User> getUsers() {// 获取用户列表的实现}@GetMapping("/{id}")@ApiVersion(2)public User getUserV2(@PathVariable Long id) {// 获取用户详细信息的实现,仅在 v2 版本中有效}
}

在这个示例中,getUsers 方法在 v1v2 版本都可访问,而 getUserV2 方法仅在 v2 版本可访问。

配置示例

application.properties 中配置版本前缀和后缀:

api-version.prefix=v
api-version.suffix=-api

这样,API 的 URL 可以是 /v1-api/users/v2-api/users

通过这种方式,@ApiVersion 注解简化了 API 版本控制的实现,提高了代码的可维护性和灵活性。


Project

在这里插入图片描述


Starter Code

自定义注解 ApiVersion

package com.github.artisan.annotation;import org.springframework.web.bind.annotation.Mapping;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 接口版本标识注解* @author artisan*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {/*** 指定API的版本号。* 此方法返回一个整型数组,数组中的每个元素代表一个API版本号。** @return 代表API版本号的整数数组。*/int[] value();
}

配置属性类用于管理API版本

package com.github.artisan;import org.springframework.boot.context.properties.ConfigurationProperties;/*** 配置属性类用于管理API版本。* 通过前缀 "api-version" 绑定配置属性,以方便管理API版本。* @author Artisan*/@ConfigurationProperties(prefix = "api-version")
public class ApiVersionProperties {/*** API版本的前缀,用于定义版本的起始部分。*/private String prefix;/*** 获取API版本的前缀。** @return 返回API版本的前缀。*/public String getPrefix() {return prefix;}/*** 设置API版本的前缀。** @param prefix 设置API版本的前缀。*/public void setPrefix(String prefix) {this.prefix = prefix;}/*** API版本的后缀,用于定义版本的结束部分。*/private String suffix;/*** 获取API版本的后缀。** @return 返回API版本的后缀。*/public String getSuffix() {return suffix;}/*** 设置API版本的后缀。** @param suffix 设置API版本的后缀。*/public void setSuffix(String suffix) {this.suffix = suffix;}}

自动配置基于Spring MVC的API版本控制

package com.github.artisan;import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** ApiVersionAutoConfiguration类用于自动配置基于Spring MVC的API版本控制* 该类通过@EnableConfigurationProperties注解激活ApiVersionProperties配置类* 并且通过@Bean注解的方法创建和管理ApiVersionWebMvcRegistrations的单例对象* @author Artisan*/
@ConditionalOnWebApplication
@Configuration
@EnableConfigurationProperties(ApiVersionProperties.class)
public class ApiVersionAutoConfiguration {/*** 通过@Bean注解声明此方法将返回一个单例对象,由Spring容器管理* 该方法的目的是根据ApiVersionProperties配置生成ApiVersionWebMvcRegistrations实例* 这对于自动配置基于Spring MVC的API版本控制至关重要** @param apiVersionProperties 一个包含API版本控制相关配置的实体类*                             该参数用于初始化ApiVersionWebMvcRegistrations对象* @return 返回一个ApiVersionWebMvcRegistrations对象,用于注册和管理API版本控制相关的设置*/@Beanpublic ApiVersionWebMvcRegistrations apiVersionWebMvcRegistrations(ApiVersionProperties apiVersionProperties) {return new ApiVersionWebMvcRegistrations(apiVersionProperties);}
}

实现WebMvcRegistrations接口,用于自定义WebMvc的注册逻辑

package com.github.artisan;import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;/*** 实现WebMvcRegistrations接口,用于自定义WebMvc的注册逻辑* 主要用于API版本的请求映射配置**  @author Artisan*/
public class ApiVersionWebMvcRegistrations implements WebMvcRegistrations {/*** API版本配置属性* 用于获取API版本的前缀和后缀配置*/private ApiVersionProperties apiVersionProperties;/*** 构造函数,初始化API版本配置属性** @param apiVersionProperties API版本配置属性对象*/public ApiVersionWebMvcRegistrations(ApiVersionProperties apiVersionProperties) {this.apiVersionProperties = apiVersionProperties;}/*** 获取请求映射处理器映射对象* 此方法用于配置API版本的请求映射处理逻辑* 它根据配置决定映射路径的前缀和后缀** @return 返回一个初始化好的RequestMappingHandlerMapping对象,用于处理API版本的请求映射*/@Overridepublic RequestMappingHandlerMapping getRequestMappingHandlerMapping() {// 根据API版本配置的前缀情况决定使用默认前缀"v"还是用户配置的前缀// 如果未配置前缀,则默认使用"v",否则使用配置的前缀// 后缀直接使用配置的值return new ApiVersionRequestMappingHandlerMapping(StringUtils.isEmpty(apiVersionProperties.getPrefix()) ?"v" : apiVersionProperties.getPrefix(), apiVersionProperties.getSuffix());}}

扩展RequestMappingHandlerMapping的类,支持API版本路由

package com.github.artisan;import com.github.artisan.annotation.ApiVersion;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.validation.constraints.NotNull;
import java.lang.reflect.Method;/*** 一个扩展了RequestMappingHandlerMapping的类,支持API版本路由。* 它允许方法或类通过ApiVersion注解来支持版本控制。* @author Artisan*/
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {/*** API版本在URL中的前缀*/private final String prefix;/*** API版本在URL中的后缀,默认为空字符串,如果未提供则为空字符串*/private final String suffix;/*** 构造函数用于初始化API版本的前缀和后缀。** @param prefix API版本在URL中的前缀* @param suffix API版本在URL中的后缀,如果没有提供则默认为空字符串*/public ApiVersionRequestMappingHandlerMapping(String prefix, String suffix) {this.prefix = prefix;this.suffix = StringUtils.isEmpty(suffix) ? "" : suffix;}/*** 覆盖此方法以获取方法的路由信息,并支持基于ApiVersion注解的自定义条件。** @param method 需要获取路由信息的方法* @param handlerType 处理器类型* @return 方法的路由信息,包括基于API版本的自定义条件*/@Overrideprotected RequestMappingInfo getMappingForMethod(Method method, @NotNull Class<?> handlerType) {// 获取基本的路由信息RequestMappingInfo info = super.getMappingForMethod(method, handlerType);if (info == null) {return null;}// 检查方法是否使用了ApiVersion注解ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);if (methodAnnotation != null) {// 获取自定义方法条件RequestCondition<?> methodCondition = getCustomMethodCondition(method);// 创建基于API版本的信息并合并到基本信息中info = createApiVersionInfo(methodAnnotation, methodCondition).combine(info);} else {// 如果方法没有使用ApiVersion注解,则检查类是否使用了该注解ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);if (typeAnnotation != null) {// 获取自定义类条件RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);// 创建基于API版本的信息并合并到基本信息中info = createApiVersionInfo(typeAnnotation, typeCondition).combine(info);}}return info;}/*** 根据ApiVersion注解创建路由信息。** 该方法解析ApiVersion注解的值,并根据这些值构建URL模式,* 然后结合自定义条件创建RequestMappingInfo对象,用于支持版本控制。** @param annotation ApiVersion注解实例,包含API版本信息。* @param customCondition 自定义条件,用于进一步细化请求映射。* @return 基于API版本的路由信息,用于将请求映射到特定版本的API处理方法上。*/private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition<?> customCondition) {// 获取注解中指定的API版本数组int[] values = annotation.value();// 为每个API版本创建对应的URL模式String[] patterns = new String[values.length];for (int i = 0; i < values.length; i++) {// 构建URL前缀patterns[i] = prefix + values[i] + suffix;}// 使用构建的URL模式和其他请求条件创建并返回RequestMappingInfo对象return new RequestMappingInfo(new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()),new RequestMethodsRequestCondition(),new ParamsRequestCondition(),new HeadersRequestCondition(),new ConsumesRequestCondition(),new ProducesRequestCondition(),customCondition);}}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.artisan.ApiVersionAutoConfiguration

Test Code

无版本控制

package com.github.artisan.web;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author Artisan*/
@RestController
public class NoVersionController {@GetMapping("foo")public String foo() {return "不使用版本注解";}
}

多版本控制

package com.github.artisan.web;import com.github.artisan.annotation.ApiVersion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author Artisan*/
@RestController
public class MultiVersionController {@GetMapping("foo3")@ApiVersion({1, 2})public String foo3() {return "注解支持多版本";}
}

v1

package com.github.artisan.web.v1;import com.github.artisan.annotation.ApiVersion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author Artisan*/
@ApiVersion(1)
@RestController
public class TestController {@GetMapping("foo1")public String foo1() {return "方法没有注解, 使用类注解";}@GetMapping("foo2")@ApiVersion(1)public String foo2() {return "方法有注解, 使用方法注解";}}

v2

package com.github.artisan.web.v2;import com.github.artisan.annotation.ApiVersion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author Artisan*/
@ApiVersion(2)
@RestController
public class TestController {@GetMapping("foo1")public String foo1() {return "方法没有注解, 使用类注解";}@GetMapping("foo2")@ApiVersion(2)public String foo2() {return "方法有注解, 使用方法注解";}@GetMapping("foo4")@ApiVersion(1)public String foo4() {return "xxxx 方法有注解使用方法注解";}}

Test

整个swagger吧

 package com.github.artisan.swagger;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration
@EnableSwagger2
public class SwaggerConfig {@Beanpublic Docket swaggerAll() {Docket docket = new Docket(DocumentationType.SWAGGER_2);return docket.apiInfo(apiInfo("all")).groupName("all").select().apis(RequestHandlerSelectors.basePackage("com.github.artisan.web")).paths(PathSelectors.any()).build().enable(true);}private ApiInfo apiInfo(String version) {return new ApiInfoBuilder().title("api-version-test doc").description("api-version-test").termsOfServiceUrl("").version(version).build();}@Beanpublic Docket swaggerV1() {return new Docket(DocumentationType.SWAGGER_2).groupName("v1").select().apis(RequestHandlerSelectors.basePackage("com.github.artisan.web")).paths(PathSelectors.regex("/v1.*")).build().apiInfo(apiInfo("v1"));}@Beanpublic Docket swaggerV2() {return new Docket(DocumentationType.SWAGGER_2).groupName("v2").select().apis(RequestHandlerSelectors.basePackage("com.github.artisan.web")).paths(PathSelectors.regex("/v2.*")).build().apiInfo(apiInfo("v2"));}}

访问: http://localhost:9090/swagger-ui.html

在这里插入图片描述

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

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

相关文章

前端CSS画图形

我以前一直很好奇&#xff0c;这些下拉菜单中的小箭头是怎么实现的&#xff0c;直到我看到了进阶的CSS。 OK&#xff0c;let me tell you hao to do. 想要实现这个效果&#xff0c;方法很多&#xff0c;我知道的就两个&#xff1a; 图片作弊法&#xff0c;CSS妙用法 图片作弊…

uni-app 开发App时调用uni-push 实现在线系统消息推送通知 保姆教程

一、引言 在开发App时避免不了需要推送系统通知&#xff0c;以提高用户的使用体验。在自己的一个工具型的小app上全流程接入了uni-push2.0的推送能力&#xff0c;做个记录&#xff0c;以防后期需要用到。在阅读本教程前最好先看看官方文档&#xff0c;结合官方文档使用&#xf…

下载免费设计素材,有这7个网站就够了

7个免费设计素材网站&#xff0c;这些网站提供了大量的免费资源&#xff0c;包括图片、字体、图标、模板等&#xff0c;涵盖了多种风格和主题&#xff0c;能够满足不同设计师和创作者的需求。无论是用于个人项目还是商业用途&#xff0c;这些网站都能给你提供丰富的选择&#x…

10步搞定Python爬虫从零到精通!

学习Python网络爬虫可以分为以下几个步骤&#xff0c;每一步都包括必要的细节和示例代码&#xff0c;以帮助你从零开始掌握这一技能。 第一步&#xff1a;理解网络爬虫基础 什么是网络爬虫&#xff1f; 网络爬虫是一种自动化程序,用来从互联网上收集数据.它通过发送 HTTP 请求…

【数据结构】五、树:7.哈夫曼树、哈夫曼编码

3.哈夫曼树和哈夫曼编码 文章目录 3.哈夫曼树和哈夫曼编码3.1带权路径长度3.2哈夫曼树的定义和原理3.3哈夫曼树的构造代码实现 3.4特点3.5哈夫曼编码压缩比代码实现 3.6哈夫曼树-C 3.1带权路径长度 #mermaid-svg-yeVKyVnDwvdIc5ML {font-family:"trebuchet ms",verda…

CSS 实现两边固定宽,中间自适应

0. **Flexbox 实现**&#xff1a; css复制代码.container { display: flex; } ​ .fixed { width: 200px; /* 两边固定宽度 */ } ​ .flexible { flex: 1; /* 中间自适应 */ } html复制代码<div class…

数据科学 - Sklearn库总结

1. 前言 通过上几章对数据预处理的理解&#xff0c;最后来到我们数据分析的核心之一&#xff0c;机器学习。 机器学习涵盖了许多方面&#xff0c;如若每一次处理都是通过手写代码的方式去处理我们的数据集是十分繁琐&#xff0c;复杂的。但在scikit-learn库中&#xff0c;提供…

带你彻底搞懂useLayoutEffect的使用场景

开篇第一句: useLayoutEffect 可能会影响性能。尽可能使用 useEffect。 useLayoutEffect 是 useEffect 的一个版本&#xff0c;在浏览器重新绘制屏幕之前触发。 使用方法 useLayoutEffect(setup, dependencies?)调用 useLayoutEffect 在浏览器重新绘制屏幕之前进行布局测量&…

lvs详解及实例配置

目录 1.什么是负载均衡 1.1为什么用负载均衡 1.2.负载均衡类型 1.2.1.四层负载均衡 1.2.2.七层负载均衡 1.3 四层和七层的区别 2.LVS介绍 2.1LVS 的优势与不足 2.2LVS 核心组件和专业术语 3.ipvsadm命令 4.LVS集群中的增删改 4.1.管理集群服务中的增删改 4.2.管理集…

用户态tcp协议栈四次挥手-服务端发送fin时,客户端不返回ac

问题&#xff1a; 四次挥手时&#xff0c;服务端发送fin后&#xff0c;客户端不发送ack&#xff0c;反而过了2min后发了个rst报文 62505是客户端&#xff0c;8889是服务端 解决&#xff1a; 服务端返回fin报文时带上ack标记

数据结构(邓俊辉)学习笔记】优先级队列 03——完全二叉堆:结构

文章目录 1.完全二叉树2.结构性3.形神具备4.堆序性 1.完全二叉树 在上一节我们看到&#xff0c;就优先级队列的实现方式而言&#xff0c;采用基本的向量结构并不足够&#xff0c;而采用更高级的树形结构&#xff0c;虽然完全可以高效率地实现优先级队列&#xff0c;但却有杀鸡…

Codeforces Round 961 【C. Squaring】

C. Squaring 题目大意&#xff1a; 给你一个长度为n的数组&#xff0c;求最少次操作&#xff0c;使得数组&#xff08;非严格&#xff09;递增。一次操作&#xff1a;Ai 变为 Ai^2。 不可能实现输出-1。 关键思路&#xff1a; 分子分母同时取对数&#xff0c;比值不变。 …

流量日志分析

流量分析 [陇剑杯 2021]jwt&#xff08;问1&#xff09; 要求是通过流量分析判断网站使用的认证方式 在筛选http之后&#xff0c;发现有get请求&#xff0c;要认证方式就需要看请求流量包中的token 然后查看token tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTAwOD…

动手学深度学习7.3 网络中的网络(NiN)-笔记练习(PyTorch)

以下内容为结合李沐老师的课程和教材补充的学习笔记&#xff0c;以及对课后练习的一些思考&#xff0c;自留回顾&#xff0c;也供同学之人交流参考。 本节课程地址&#xff1a;26 网络中的网络 NiN【动手学深度学习v2】_哔哩哔哩_bilibili 本节教材地址&#xff1a;7.3. 网络…

三层架构与解耦——IoCDI机制【后端 7】

三层架构与解耦——IoC&DI机制 在软件开发领域&#xff0c;三层架构&#xff08;Controller、Service、Dao&#xff09;是一种广泛采用的架构模式&#xff0c;它通过将应用程序分为三个主要层次来组织代码&#xff0c;旨在提高代码的可维护性、复用性和可扩展性。而解耦&am…

怎么用dos编译python

无论windos还是Linux只要安装了python&#xff0c;配置好了环境变量&#xff0c;则在命令行输入python这个命令的时候就会进入交互模式。在这个模式下可以进行一些简单的python代码编写。退出可以使用exit()方法。 Python程序是可以执行的&#xff0c;例如下面代码&#xff0c…

文心一言 VS 讯飞星火 VS chatgpt (323)-- 算法导论22.4 4题

四、证明或反证下述论断&#xff1a;如果有向图G包含环路&#xff0c;则在算法TOPOLOGICAL-SORT(G)所生成的结点序列里&#xff0c;图G中与所生成序列不一致的“坏”边的条数最少。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 首先&#xff0c;我们需要明确几…

春秋云镜CVE-2023-38836

打开靶场环境 点击发现一个登陆框&#xff0c;弱口令试一下 发现账号密码为admin,password 随便点击点击 Media发现这里可以上传文件上传木马试试 <?php eval($_POST["wjq"]); ?> 发现不能上传php文件 php内容 修改他的格式 抓包绕过一下 302就可以其实已经…

同态加密和SEAL库的介绍(六)BGV 方案

前面介绍 BFV 和 CKKS 加密方案&#xff0c;这两者更为常用。并且也解释了 Batch Encoder 和 级别的概念&#xff0c;这对接下来演示 BGV 会很有帮助。 一、BGV简介 BGV (Brakerski-Gentry-Vaikuntanathan) 方案 是一种基于环学习同态加密&#xff08;RLWE&#xff09;问题的加…

华为OD笔试

机试总分400。三道题目。100&#xff0b;100&#xff0b;200 华为od考试时间为150分钟&#xff0c;共有三道编程题&#xff0c;分数分别为100、100和200。如果你是目标院校&#xff08;查看目标院校请戳&#xff09;的话&#xff0c;及格线是160分&#xff0c;非目标院校则不确…