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

news2024/9/22 11:27:48

文章目录

  • 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版本控制相关的设置
     */
    @Bean
    public 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版本的请求映射
     */
    @Override
    public 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版本的自定义条件
     */
    @Override
    protected 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 {

    @Bean
    public 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();
    }

    @Bean
    public 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"));
    }

    @Bean
    public 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.coloradmin.cn/o/1994162.html

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

相关文章

医院预约挂号小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;医生管理&#xff0c;科室分类管理&#xff0c;医生信息管理&#xff0c;预约挂号管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;医生信息&#…

Python酷库之旅-第三方库Pandas(074)

目录 一、用法精讲 301、pandas.Series.dt.components属性 301-1、语法 301-2、参数 301-3、功能 301-4、返回值 301-5、说明 301-6、用法 301-6-1、数据准备 301-6-2、代码示例 301-6-3、结果输出 302、pandas.Series.dt.to_pytimedelta方法 302-1、语法 302-2、…

17.1 分布式限流组件Sentinel

17.1 分布式限流组件Sentinel 1. Sentinel介绍1.1 Sentinel 介绍1.2 Sentinel 功能和设计理念流量控制2. Sentinel安装控制台2.1 概述2.2 启动控制台*****************************************************************************1. Sentinel介绍 github 官方中文文档 1.…

Rest风格快速开发

Rest风格开发简介 简单点来说&#xff0c;Rest风格的开发就是让别人不知道你在做什么&#xff0c;以deleteUserById和selectUserById为例&#xff1a; 普通开发&#xff1a;路径 /users/deleteById?Id666 /users/selectById?Id666 别人很容易知道你这是在干什么 Rest风…

半导体行业人士宋仕强谈生产力

近日&#xff0c;半导体行业人士&#xff0c;金航标电子和萨科微创始人宋仕强强调了技术进步与管理创新在提升生产效率中的作用。深圳作为中国效率驱动发展模式的典范&#xff0c;其核心竞争力在于高效利用资源。从早期的快速城市建设到现今华强北电子市场的繁荣&#xff0c;深…

批量ncm转mp3

软件上线一段时间后发现大家用ncm转MP3功能比较多&#xff0c;并且很多用户都是同时转换好几个音乐&#xff0c;为了方便大家使用这里就给大家提供了一个批量ncm转MP3的功能&#xff0c;下面简单介绍一下如何使用 打开智游剪辑&#xff08;zyjj.cc&#xff09;&#xff0c;搜索…

Mouser中元件特性对比功能

搜索所需的元件&#xff0c;并点击比对 在比对界面里搜索所需比对的另外元器件&#xff0c;并比对3.得到的结果

深入探索 Wireshark——网络封包分析的利器

一、引言 在当今数字化的时代&#xff0c;网络通信变得日益复杂和关键。无论是企业的网络运维&#xff0c;还是网络安全研究&#xff0c;都需要深入了解网络中传输的数据。Wireshark 作为一款强大的网络封包分析工具&#xff0c;成为了网络工程师、安全研究人员和技术爱好者不…

linux 查看端口占用并处理

lsof 命令 lsof -i:端口注意pid netstat 命令 netstat -tnpla | grep 端口注意pid 查看详情 ps -ef | grep 3766607删除 kill -9 PIDkill -9 3766607

OpenCV图像滤波(7)cv::getDerivKernels() 函数的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 函数返回用于计算空间图像导数的滤波系数。 该函数计算并返回用于空间图像导数的滤波系数。当 ksizeFILTER_SCHARR 时&#xff0c;生成 Scharr 3…

【Python机器学习】树回归——复杂数据的局部性建模

线性回归包含一些强大的方法&#xff0c;但这些方法创建的模型需要拟合所有的样本&#xff08;局部加权线性回归除外&#xff09;&#xff0c;当数据拥有众多特征并且特征之间关系十分复杂时&#xff0c;构建全局模型的想法就显得很困难&#xff0c;也略显笨拙。而且&#xff0…

MySql 5.7.1 分区的实践

在性能优化中&#xff0c;Mysql 进行分区&#xff0c;能有效提高查询效率&#xff0c;因此开始百度了起来。但是结果总不是那么一番风顺的。如今使用 uuid 作为主键的情况已是主流&#xff0c;因此在给表增加分区时&#xff0c;出现了如下错误&#xff1a; 错误&#xff1a; A …

FishSpeech 实测,免费语音克隆神器,5分钟部署实战,让川普给你来段中文绕口令?

拍短视频&#xff0c;开始的时候是真人语音&#xff0c;之后是电脑配音&#xff0c;今年年初剪映上线了克隆语音&#xff0c;很多人都用起来了。 想要克隆别人的语音怎么办&#xff1f; 之前需要用 GPT-SoVITS 训练声音模型&#xff0c;操作复杂&#xff0c;对电脑配置要求较…

【算法设计】深入理解波兰表达式与逆波兰表达式

文章目录 介绍1. 波兰表达式&#xff08;Prefix Notation&#xff09;2. 逆波兰表达式&#xff08;Postfix Notation&#xff09;3. 比较与优劣4. 简单示例5. 实例演示6. 应用场景和案例7. 中缀表达式转后缀表达式8. 结论 更多相关内容可查看 应用场景&#xff1a;Excel自定义公…

Mozilla Firefox侧边栏和垂直标签在131 Nightly版本中开始试用

垂直选项卡和全新的侧边栏体验现已在Mozilla Firefox Nightly 131 中提供。这一更新备受社区期待和要求&#xff0c;我们期待看到它如何提高您的浏览效率和工作效率。如果您想体验一下这项正在进行中的工作&#xff0c;请这样操作&#xff1a; 更新到最新的Nightly版 转到设置…

LeetCode刷题笔记第49题:字母异位词分组

LeetCode刷题笔记第49题&#xff1a;字母异位词分组 题目&#xff1a; 想法&#xff1a; 遍历列表中的所有字符串&#xff0c;将字符串中字符进行排序并作为字典的键&#xff0c;并将原字符串做为值保存&#xff0c;最终输出字典的值就是最终的答案。 class Solution:def gr…

MySQL 日志表改造为分区表

文章目录 前言1. 分区表改造方法2. 操作步骤2.1 调整主键2.2 无锁变更2.3 回滚策略 3. 分区表维护3.1 创建分区3.2 删除分区3.3 分区表查询 后记 前言 业务有一张日志表&#xff0c;只需要保留 3 个月的数据&#xff0c;仅 3 月的数据就占用 80G 的存储空间&#xff0c;如果不…

二维数组指针,指针数组,指针函数

指针 操作 二维字符型数组 1、 首先理解二维数组指针 int a[3][4]; 第一步&#xff0c;确定基类型&#xff1a;上面的数组从本质上讲&#xff0c;是一维数组的数组&#xff0c;写成int[4] a[3]可以更好的理解&#xff0c;a[3]是一个一维数组&#xff0c;其数组…

【机器学习sklearn实战】岭回归、Lasso回归和弹性网络

一 sklean中模型详解 1.1 Ride regression 1.2 Lasso regression 1.3 ElasticNet 二 算法实战 2.1 导入包 import numpy as np import pandas as pd from sklearn import datasets from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.linear…

【开源分享】PHP在线客服系统网站源码 附搭建教程

在互联网时代&#xff0c;用户对于线上服务的便捷性和高效性要求越来越高。官网在线客服系统作为企业与用户之间实时沟通的工具&#xff0c;不仅能够提高用户满意度&#xff0c;还能够有效促进业务转化。然而&#xff0c;市面上的在线客服系统大多价格昂贵且功能单一&#xff0…