前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot实现API版本控制

SpringBoot实现API版本控制

原创
作者头像
用户10394238
发布2023-03-14 10:26:01
1.6K1
发布2023-03-14 10:26:01
举报
文章被收录于专栏:记事本

随着业务的发展,产品对外开放的API接口会随着业务的需要经常发生变动,考虑到快速响应新用户的需求,且不影响老用户当前使用的前提下,对开放API进行版本控制是非常有必要的。

API版本控制的优点如下:

1:降低代码冗余

2:兼容历史版本

3:新接口升级可进行增量迭代,且版本向下兼容,升级平滑,可在历史客户无感的情况下进行版本迭代更新。

实现步骤如下:

01:新增自定义注解

代码语言:javascript
复制
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    /**
     * 版本号
     * @return
     */
    int value() default 1;
}

ElementType.TYPE 表示注解可作用于类

ElementType.METHOD 表示注解可作用于方法

02:定义匹配逻辑

代码语言:javascript
复制
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;
    }

    public int getApiversion() {
        return apiversion;
    }

    @Override
    public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
        return new ApiVersionCondition(apiVersionCondition.getApiversion());
    }

    @Override
    public 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;
    }

1:通过ApiVersionCondition 重写 RequestCondition 的URL匹配逻辑,将提取请求URL中的版本号与注解上定义的版本号进行对比,以此来判断某个请求应落在哪个控制器上。

2:当类和方法上都包含 @ApiVersion注解时,通过ApiVersionRequestCondition.combine完成注解的合并,提取版本号,并与注解上的版本号进行比对,判断版本号是否符合要求。

03:自定义匹配处理

代码语言:javascript
复制
public class ApiRequestHandlerMapping extends RequestMappingHandlerMapping {
    public final static String VERSION_FLAG = "{version}";
    public 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<?> getCustomMethodCondition(Method method){
            return createCondition(method.getClass());
         }
        @Override
         protected  RequestCondition<?> getCustomTypeCondition(Class<?> handlerType){
            return createCondition(handlerType);
         }
}

1:spring mvc中把web框架和spring ioc融合在一起,是通过ContextLoaderListener监听servlet上下文的创建后来加载父容器完成的,然后通过配置一个servlet对象DispatcherServlet,在初始化DispatcherServlet时来加载具体子容器。

2:RequestMappingHandlerMapping也是在DispatcherServlet的初始化过程中自动加载的。

3: 默认会自动加载所有实现HandlerMapping接口的bean。

4:我们可以通过setOrder来设置优先级。

5:系统默认会加载RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、 SimpleUrlHandlerMapping 并且按照顺序使用。

04:增加配置类WebMvcRegistrationsConfig

代码语言:javascript
复制
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {

    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiRequestHandlerMapping();
    }
}

1:增加此类的目的是为了注册 ApiRequestHandlerMapping 到系统中。

04:增加两个版本的接口

接口【V1】【v2】版本 ,其中/search/{orderId} 接口【V1】【V2】 版本的接口均包含

代码语言:javascript
复制
@ApiVersion(value = 1)
@RestController
@RequestMapping("api/{version}/order")
public class ApiVersionTestV1Controller {
    @GetMapping("/save/{orderId}")
    public JSONResult saveOrderById(@PathVariable int orderId){
        String message = "订单号 V1 保存成功" + orderId;
        System.out.println(message);
        return JSONResult.OK(message);
    }
    @GetMapping("/search/{orderId}")
    public JSONResult searchOrderById(@PathVariable int orderId){
        String message = "获取订单详情成功" + orderId;
        System.out.println(message);
        return JSONResult.OK(message);
    }
}
代码语言:javascript
复制
@ApiVersion(value = 2)
@RestController
@RequestMapping("api/{version}/order")
public class ApiVersionTestV2Controller {
    @GetMapping("/delete/{orderId}")
    public JSONResult deleteOrderById(@PathVariable int orderId){
        String message = "订单号 V2 删除成功" + orderId;
        System.out.println(message);
        return JSONResult.OK(message);
    }
    @GetMapping("/query")
    public JSONResult queryOrder(){
        String message = "获取订单列表信息成功V2!";
        System.out.println(message);
        return JSONResult.OK(message);
    }
    @GetMapping("/search/{orderId}")
    public JSONResult searchOrderById(@PathVariable int orderId){
        String message = "V2获取订单详情成功" + orderId;
        System.out.println(message);
        return JSONResult.OK(message);
    }

【测试结果】

1:通过V2版本访问V1的接口

【结论】:高版本,可直接访问低版本接口。

2:通过V1版本访问V2的接口

【结论:低版本无法访问高版本接口】

3:访问V1,V2 版本均包含的接口

【结论】:同名接口均调用对应版本,互不影响。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档