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 启动后运行成功