API版本控制-自定义RequestMappingHandlerMapping实现

1.干啥要使用版本控制

系统不断优化,功能不断增多,现有的业务逻辑不能满足用户需求,就必须要进行版本迭代,接口就必须进行改进,但是改进的同时又不能影响之前的业务,那么就需要进行版本迭代,出现版本迭代就需要进行版本控制。

2.版本控制的几种方式

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

4、请求头中区分版本,在同一url路径下,请求头中增加版本号version:1或version:2,然后根据不同的版本,选择执行不同的方法。

3.Spring Boot实现版本控制

实现方案:

1、首先创建自定义的@APIVersion 注解和自定义URL匹配规则ApiVersionCondition。

2、然后创建自定义的 RequestMappingHandlerMapping 匹配对应的request,选择符合条件的method handler。

1.创建自定义注解

首先,在com.smz.smzuser.config 包下,创建一个自定义版本号标记注解 @ApiVersion。

package com.smz.smzuser.config;

import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
 * 控制api版本注解
 *
 * @author dawn Date:2019年4月12日
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
  /** @return 版本号 */
  int value() default 1;
}

说明: ApiVersion 为自定义的注解,API版本控制,返回对应的版本号。

2、自定义url匹配逻辑

创建 ApiVersionCondition 类,并继承RequestCondition 接口,作用是:版本号筛选,将提取请求头中版本号,与注解上定义的版本号进行比对,以此来判断某个请求应落在哪个controller上。

在com.smz.smzuser.config 包下创建ApiVersionCondition 类,重写 RequestCondition,创建自定义的url匹配逻辑

package com.smz.smzuser.config;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;

/** * 版本号规则配置类 */
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
  private int apiVersion;

  public ApiVersionCondition(int apiVersion) {
    this.apiVersion = apiVersion;
  }

  @Override
  public ApiVersionCondition combine(ApiVersionCondition other) {
    // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
    return new ApiVersionCondition(other.getApiVersion());
  }

  @Override
  public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
    String ver = request.getHeader("version");
    // 因为请求头里面传来的是小数,所以需要乘以10
    int version = Integer.parseInt(ver);
    if (version >= this.apiVersion) { // 如果请求的版本号大于等于配置版本号, 则满足
      return this;
    }
    return null;
  }

  @Override
  public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
    // 优先匹配最新的版本号
    return other.getApiVersion() - this.apiVersion;
  }

  public int getApiVersion() {
    return apiVersion;
  }
}


3、自定义匹配的处理器

在com.smz.smzuser.config 包下创建 ApiRequestMappingHandlerMapping 类,重写部分 RequestMappingHandlerMapping 的方法。

package com.smz.smzuser.config;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

/** 创建自定义requestMapping类来配置规则 */
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
  @Override
  protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {
    ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
    return createCondition(apiVersion);
  }

  @Override
  protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {
    ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
    return createCondition(apiVersion);
  }

  private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
    return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
  }
}


4、配置注册自定义的RequestMappingHandlerMapping

package com.smz.smzuser.config;

import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/** 覆盖spring原生RequestMappingHandlerMapping类 */
@Component
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
  @Override
  public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
    RequestMappingHandlerMapping handlerMapping = new ApiRequestMappingHandlerMapping();
    handlerMapping.setOrder(0);
    return handlerMapping;
  }
}

上面四步,把api 版本控制配置完成,都是重写spring boot 内部的处理流程。

4.测试

创建Controller

1、在com.smz.smzuser.web.rest 目录下,分别创建version1 和 version2目录
分别在version1,version2目录下创建UserControllerV1,UserControllerV2

UserControllerV1

package com.smz.smzuser.web.rest.version1;

import com.smz.smzuser.config.ApiVersion;
import com.smz.smzuser.service.UserService;
import com.smz.smzuser.web.transform.UserWebTransform;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/api")
@ApiVersion(1)
@RestController
public class UserControllerV1 {
  @GetMapping(value = "/user/test")
  public String test() {
    return "version1";
  }
}

UserControllerV2

package com.smz.smzuser.web.rest.version2;

import com.smz.smzuser.config.ApiVersion;
import com.smz.smzuser.service.UserService;
import com.smz.smzuser.web.transform.UserWebTransform;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/api")
@ApiVersion(2)
@RestController
public class UserControllerV2 {
  @GetMapping(value = "/user/test")
  public String test() {
    return "user v2 test";
  }
}

2、启动项目后,输入相关地址,查看版本控制是否生效
正常版本地址

image.png

image.png

更高版本地址:
image.png
说明:有一种实现方式是直接在url地址中写明版本号例如:/api/v1/user
/api/v2/user
虽然依然可以实现版本控制但是如果使用更高的APP版本就会出现不可以兼容问题
例如接口是v2,app版本是v3这样就不可以访问了相当于接口被写死了
这种方式就可以解决向下兼容问题,就是说可以继承没有迭代版本的接口

遇到的问题

1.因为用到Swagger2所以使用了相关配置导致配置文件继承了WebMvcConfigurationSupport会到导致我们配置的自定义配置失效
在启动时会报错:说是方法1已经映射了路径,就不能再使用了,由于两个requestMapping 里面url被识别 一样
image.png
解决办法:
1.删掉继承WebMvcConfigurationSupport的配置文件
如果启动报错

Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

那么试一下降低SpringBoot版本2.6.7->2.5.7
启动后运行成功
image.png

完结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值