SpringBoot|SpringBoot API增加version版本号方式

目录

  • SpringBoot 增加 API Version
    • 一、增加ApiVersion自定义注解
    • 二、新增RequestCondition自定义匹配条件
    • 三、重写RequestMappingHandlerMapping处理
    • 四、Controller接口增加@ApiVersion注解
    • 五、测试调用
    • 六、总结
  • SpringBoot的项目API版本控制
    • 一、自定义版本号标记注解
    • 二、重写RequestCondition,自定义url匹配逻辑
    • 三、重写RequestMappingHandlerMapping,自定义匹配的处理器
    • 四、配置注册自定义WebMvcRegistrations
    • 五、编写测试接口

SpringBoot 增加 API Version 基于restful风格上,增加version版本号
例如: get /api/v1/users/

一、增加ApiVersion自定义注解
作用于Controller上,指定API版本号
这里版本号使用了double ,考虑到小版本的情况,例如1.1
import java.lang.annotation.*; /** * API Version type */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ApiVersion {/*** api version begin 1*/double version() default 1; }


二、新增RequestCondition自定义匹配条件
Spring提供RequestCondition接口,用于定义API匹配条件
【SpringBoot|SpringBoot API增加version版本号方式】这里通过自定义匹配条件,识别ApiVersion,进行版本匹配
getMatchingCondition 用于检查URL中,是否符合/v{版本号},用于过滤无版本号接口;
compareTo 用于决定多个相同API时,使用哪个接口进行处理;
import org.springframework.web.servlet.mvc.condition.RequestCondition; import javax.servlet.http.HttpServletRequest; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * API version condition * @author w * @date 2020-11-16 */public class ApiVersionCondition implements RequestCondition {/*** 接口路径中的版本号前缀,如: api/v[1-n]/test*/private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v([0-9]+\\.{0,1}[0-9]{0,2})/"); /** API VERSION interface **/private ApiVersion apiVersion; ApiVersionCondition(ApiVersion apiVersion){this.apiVersion = apiVersion; }/*** [当class 和 method 请求url相同时,触发此方法用于合并url]* 官方解释:* - 某个接口有多个规则时,进行合并* - 比如类上指定了@RequestMapping的 url 为 root* - 而方法上指定的@RequestMapping的 url 为 method* - 那么在获取这个接口的 url 匹配规则时,类上扫描一次,方法上扫描一次,这个时候就需要把这两个合并成一个,表示这个接口匹配root/method* @param other 相同api version condition* @return ApiVersionCondition*/@Overridepublic ApiVersionCondition combine(ApiVersionCondition other) {// 此处按优先级,method大于classreturn new ApiVersionCondition(other.getApiVersion()); }/*** 判断是否成功,失败返回 null;否则,则返回匹配成功的条件* @param httpServletRequest http request* @return 匹配成功条件*/@Overridepublic ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {// 通过uri匹配版本号System.out.println(httpServletRequest.getRequestURI()); Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI()); if (m.find()) {// 获得符合匹配条件的ApiVersionConditionSystem.out.println("groupCount:"+m.groupCount()); double version = Double.valueOf(m.group(1)); if (version >= getApiVersion().version()) {return this; }}return null; }/*** 多个都满足条件时,用来指定具体选择哪一个* @param other 多个时* @param httpServletRequest http request* @return 取版本号最大的*/@Overridepublic int compareTo(ApiVersionCondition other, HttpServletRequest httpServletRequest) {// 当出现多个符合匹配条件的ApiVersionCondition,优先匹配版本号较大的return other.getApiVersion().version() >= getApiVersion().version() ? 1 : -1; }public ApiVersion getApiVersion() {return apiVersion; }}


三、重写RequestMappingHandlerMapping处理
通过重写 RequestMappingHandlerMapping 类,对RequestMappering进行识别@ApiVersion注解,针对性处理;
这里考虑到有些接口不存在版本号,则使用Spring原来的ApiVersionRequestMappingHandlerMapping继续处理;
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; /** * API version setting * @author w * @date 2020-11-15 */public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {/*** class condition* - 在class上加@ApiVersion注解&url加{version}* @param handlerType class type* @return ApiVersionCondition*/@Overrideprotected RequestCondition getCustomTypeCondition(Class handlerType) {ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType,ApiVersion.class); return null == apiVersion ? super.getCustomTypeCondition(handlerType) : new ApiVersionCondition(apiVersion); }/*** method condition* - 在方法上加@ApiVersion注解&url加{version}* @param method method object* @return ApiVersionCondition*/@Overrideprotected RequestCondition getCustomMethodCondition(Method method) {ApiVersion apiVersion = AnnotationUtils.findAnnotation(method,ApiVersion.class); return null == apiVersion ? super.getCustomMethodCondition(method) : new ApiVersionCondition(apiVersion); }}


四、Controller接口增加@ApiVersion注解
通过@ApiVersion注解指定该接口版本号
import com.panda.common.web.controller.BasicController; import com.panda.common.web.version.ApiVersion; import com.panda.core.umc.service.UserInfoService; import com.panda.core.umc.vo.QueryUsersConditionVo; import com.panda.face.umc.dto.user.QueryUsersReq; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; /** * 用户信息服务 * @author w * @date 2020-11-06 */@RequestMapping("/api")@RestControllerpublic class UserInfoController extends BasicController{@Autowiredprivate UserInfoService userInfoService; /*** 查询所有用户信息* @param req 查询条件信息*/@ApiVersion@RequestMapping(value = "https://www.it610.com/article/{version}/users", method = RequestMethod.GET)@ResponseBodypublic ResponseEntity getUsers(@PathVariable("version") String version, QueryUsersReq req){QueryUsersConditionVo condition = new QueryUsersConditionVo(); BeanUtils.copyProperties(req,condition); condition.setOrderBy("CREATE_TIME"); condition.setSort("DESC"); return assemble("1111"); }/*** 查询所有用户信息* @param req 查询条件信息*/@ApiVersion(version = 1.1)@RequestMapping(value = "https://www.it610.com/article/{version}/users", method = RequestMethod.GET)@ResponseBodypublic ResponseEntity getUsersV2(@PathVariable("version") String version, QueryUsersReq req){QueryUsersConditionVo condition = new QueryUsersConditionVo(); BeanUtils.copyProperties(req,condition); condition.setOrderBy("CREATE_TIME"); condition.setSort("DESC"); return assemble("222"); }/*** 根据用户ID获取用户信息* @param userId 用户ID*/@RequestMapping(value = "https://www.it610.com/users/uid/{userId}", method = RequestMethod.GET)@ResponseBodypublic ResponseEntity getUserInfo(@PathVariable("userId") String userId){return assemble(userInfoService.selectByUserId(userId)); }}


五、测试调用
通过访问以下URL,测试返回结果
GET http://127.0.0.1/api/v1/users
GET http://127.0.0.1/api/v1.1/users
GET http://127.0.0.1/api/v1.2/users
GET http://127.0.0.1/api/users/uid/U0001

六、总结
1.通过@ApiVersion注解方式,可以灵活指定接口版本;
2.缺点很明显,需要在URL上加入{version},才能进行匹配成功,这种PathVariable识别过于模糊,后期排查问题增加困难;
3.建议通过包名增加v1/v2明显区分版本,且在controller的URL上直接写死v1版本号,这种更直观;

SpringBoot的项目API版本控制
一、自定义版本号标记注解
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ApiVersion {/*** 标识版本号,从1开始*/int value() default 1; }


二、重写RequestCondition,自定义url匹配逻辑
@Data@Slf4jpublic class ApiVersionCondition implements RequestCondition {/*** 接口路径中的版本号前缀,如: api/v[1-n]/fun*/private final static Pattern VERSION_PREFIX = Pattern.compile("/v(\\d+)/"); private int apiVersion; ApiVersionCondition(int apiVersion) {this.apiVersion = apiVersion; }/*** 最近优先原则,方法定义的 @ApiVersion > 类定义的 @ApiVersion*/@Overridepublic ApiVersionCondition combine(ApiVersionCondition other) {return new ApiVersionCondition(other.getApiVersion()); }/*** 获得符合匹配条件的ApiVersionCondition*/@Overridepublic ApiVersionCondition getMatchingCondition(HttpServletRequest request) {Matcher m = VERSION_PREFIX.matcher(request.getRequestURI()); if (m.find()) {int version = Integer.valueOf(m.group(1)); if (version >= getApiVersion()) {return this; }}return null; }/*** 当出现多个符合匹配条件的ApiVersionCondition,优先匹配版本号较大的*/@Overridepublic int compareTo(ApiVersionCondition other, HttpServletRequest request) {return other.getApiVersion() - getApiVersion(); }}

说明:
getMatchingCondition方法中,控制了只有版本小于等于请求参数中的版本的 ApiCondition 才满足规则
compareTo 指定了当有多个ApiCoondition满足这个请求时,选择最大的版本

三、重写RequestMappingHandlerMapping,自定义匹配的处理器
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {@Overrideprotected RequestCondition getCustomTypeCondition(Class handlerType) {// 扫描类上的 @ApiVersionApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); return createRequestCondition(apiVersion); }@Overrideprotected RequestCondition getCustomMethodCondition(Method method) {// 扫描方法上的 @ApiVersionApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class); return createRequestCondition(apiVersion); }private RequestCondition createRequestCondition(ApiVersion apiVersion) {if (Objects.isNull(apiVersion)) {return null; }int value = https://www.it610.com/article/apiVersion.value(); Assert.isTrue(value>= 1, "Api Version Must be greater than or equal to 1"); return new ApiVersionCondition(value); }}


四、配置注册自定义WebMvcRegistrations
@Configurationpublic class WebMvcRegistrationsConfig implements WebMvcRegistrations {@Overridepublic RequestMappingHandlerMapping getRequestMappingHandlerMapping() {return new ApiRequestMappingHandlerMapping(); }}


五、编写测试接口
@RestController@RequestMapping("/api/{version}")public class ApiControler {@GetMapping("/fun")public String fun1() {return "fun 1"; }@ApiVersion(5)@GetMapping("/fun")public String fun2() {return "fun 2"; }@ApiVersion(9)@GetMapping("/fun")public String fun3() {return "fun 5"; }}

页面测试效果:
SpringBoot|SpringBoot API增加version版本号方式
文章图片

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

    推荐阅读