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、启动项目后,输入相关地址,查看版本控制是否生效
正常版本地址
更高版本地址:
说明:有一种实现方式是直接在url地址中写明版本号例如:/api/v1/user
/api/v2/user
虽然依然可以实现版本控制但是如果使用更高的APP版本就会出现不可以兼容问题
例如接口是v2,app版本是v3这样就不可以访问了相当于接口被写死了
这种方式就可以解决向下兼容问题,就是说可以继承没有迭代版本的接口
遇到的问题
1.因为用到Swagger2所以使用了相关配置导致配置文件继承了WebMvcConfigurationSupport会到导致我们配置的自定义配置失效
在启动时会报错:说是方法1已经映射了路径,就不能再使用了,由于两个requestMapping 里面url被识别 一样
解决办法:
1.删掉继承WebMvcConfigurationSupport的配置文件
如果启动报错
Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
那么试一下降低SpringBoot版本2.6.7->2.5.7
启动后运行成功