SpringBoot API增加version版本號方式
SpringBoot 增加 API Version
基于restful風(fēng)格上,增加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)
@Documented
public @interface ApiVersion {
/**
* api version begin 1
*/
double version() default 1;
}
二、新增RequestCondition自定義匹配條件
Spring提供RequestCondition接口,用于定義API匹配條件
這里通過自定義匹配條件,識別ApiVersion,進(jìn)行版本匹配
getMatchingCondition 用于檢查URL中,是否符合/v{版本號},用于過濾無版本號接口;
compareTo 用于決定多個相同API時,使用哪個接口進(jìn)行處理;
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<ApiVersionCondition> {
/**
* 接口路徑中的版本號前綴,如: 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;
}
/**
* [當(dāng)class 和 method 請求url相同時,觸發(fā)此方法用于合并url]
* 官方解釋:
* - 某個接口有多個規(guī)則時,進(jìn)行合并
* - 比如類上指定了@RequestMapping的 url 為 root
* - 而方法上指定的@RequestMapping的 url 為 method
* - 那么在獲取這個接口的 url 匹配規(guī)則時,類上掃描一次,方法上掃描一次,這個時候就需要把這兩個合并成一個,表示這個接口匹配root/method
* @param other 相同api version condition
* @return ApiVersionCondition
*/
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
// 此處按優(yōu)先級,method大于class
return new ApiVersionCondition(other.getApiVersion());
}
/**
* 判斷是否成功,失敗返回 null;否則,則返回匹配成功的條件
* @param httpServletRequest http request
* @return 匹配成功條件
*/
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
// 通過uri匹配版本號
System.out.println(httpServletRequest.getRequestURI());
Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
if (m.find()) {
// 獲得符合匹配條件的ApiVersionCondition
System.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 取版本號最大的
*/
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest httpServletRequest) {
// 當(dāng)出現(xiàn)多個符合匹配條件的ApiVersionCondition,優(yōu)先匹配版本號較大的
return other.getApiVersion().version() >= getApiVersion().version() ? 1 : -1;
}
public ApiVersion getApiVersion() {
return apiVersion;
}
}
三、重寫RequestMappingHandlerMapping處理
通過重寫 RequestMappingHandlerMapping 類,對RequestMappering進(jìn)行識別@ApiVersion注解,針對性處理;
這里考慮到有些接口不存在版本號,則使用Spring原來的ApiVersionRequestMappingHandlerMapping繼續(xù)處理;
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
*/
@Override
protected 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
*/
@Override
protected 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.*;
/**
* 用戶信息服務(wù)
* @author w
* @date 2020-11-06
*/
@RequestMapping("/api")
@RestController
public class UserInfoController extends BasicController{
@Autowired
private UserInfoService userInfoService;
/**
* 查詢所有用戶信息
* @param req 查詢條件信息
*/
@ApiVersion
@RequestMapping(value = "{version}/users", method = RequestMethod.GET)
@ResponseBody
public 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 = "{version}/users", method = RequestMethod.GET)
@ResponseBody
public 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");
}
/**
* 根據(jù)用戶ID獲取用戶信息
* @param userId 用戶ID
*/
@RequestMapping(value = "/users/uid/{userId}", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity getUserInfo(@PathVariable("userId") String userId){
return assemble(userInfoService.selectByUserId(userId));
}
}
五、測試調(diào)用
通過訪問以下URL,測試返回結(jié)果
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
六、總結(jié)
1.通過@ApiVersion注解方式,可以靈活指定接口版本;
2.缺點很明顯,需要在URL上加入{version},才能進(jìn)行匹配成功,這種PathVariable識別過于模糊,后期排查問題增加困難;
3.建議通過包名增加v1/v2明顯區(qū)分版本,且在controller的URL上直接寫死v1版本號,這種更直觀;
SpringBoot的項目API版本控制
一、自定義版本號標(biāo)記注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
/**
* 標(biāo)識版本號,從1開始
*/
int value() default 1;
}
二、重寫RequestCondition,自定義url匹配邏輯
@Data
@Slf4j
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
/**
* 接口路徑中的版本號前綴,如: api/v[1-n]/fun
*/
private final static Pattern VERSION_PREFIX = Pattern.compile("/v(\\d+)/");
private int apiVersion;
ApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
/**
* 最近優(yōu)先原則,方法定義的 @ApiVersion > 類定義的 @ApiVersion
*/
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
return new ApiVersionCondition(other.getApiVersion());
}
/**
* 獲得符合匹配條件的ApiVersionCondition
*/
@Override
public 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;
}
/**
* 當(dāng)出現(xiàn)多個符合匹配條件的ApiVersionCondition,優(yōu)先匹配版本號較大的
*/
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
return other.getApiVersion() - getApiVersion();
}
}
說明:
getMatchingCondition方法中,控制了只有版本小于等于請求參數(shù)中的版本的 ApiCondition 才滿足規(guī)則
compareTo 指定了當(dāng)有多個ApiCoondition滿足這個請求時,選擇最大的版本
三、重寫RequestMappingHandlerMapping,自定義匹配的處理器
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
// 掃描類上的 @ApiVersion
ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return createRequestCondition(apiVersion);
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
// 掃描方法上的 @ApiVersion
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createRequestCondition(apiVersion);
}
private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) {
if (Objects.isNull(apiVersion)) {
return null;
}
int value = apiVersion.value();
Assert.isTrue(value >= 1, "Api Version Must be greater than or equal to 1");
return new ApiVersionCondition(value);
}
}
四、配置注冊自定義WebMvcRegistrations
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
@Override
public 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";
}
}
頁面測試效果:

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring在多線程環(huán)境下如何確保事務(wù)一致性問題詳解
這篇文章主要介紹了Spring在多線程環(huán)境下如何確保事務(wù)一致性問題詳解,說到異步執(zhí)行,很多小伙伴首先想到Spring中提供的@Async注解,但是Spring提供的異步執(zhí)行任務(wù)能力并不足以解決我們當(dāng)前的需求,需要的朋友可以參考下2023-11-11
SpringBoot配置Access-Control-Allow-Origin教程
文章介紹了三種配置Spring Boot跨域訪問的方法:1. 使用過濾器;2. 在WebConfig配置文件中設(shè)置;3. 通過注解配置,作者分享了個人經(jīng)驗,并鼓勵讀者支持腳本之家2025-03-03
Springboot+WebSocket+Netty實現(xiàn)在線聊天/群聊系統(tǒng)
這篇文章主要實現(xiàn)在好友添加、建群、聊天對話、群聊功能,使用Java作為后端語言進(jìn)行支持,界面友好,開發(fā)簡單,文章中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2023-08-08
mybatis3.3+struts2.3.24+mysql5.1.22開發(fā)環(huán)境搭建圖文教程
這篇文章主要為大家詳細(xì)介紹了mybatis3.3+struts2.3.24+mysql5.1.22開發(fā)環(huán)境搭建圖文教程,感興趣的小伙伴們可以參考一下2016-06-06
使用java實現(xiàn)http多線程斷點下載文件(一)
Java 多線程斷點下載文件基本原理:利用URLConnection獲取要下載文件的長度、頭部等相關(guān)信息,并設(shè)置響應(yīng)的頭部信息,本文將詳細(xì)介紹,需要了解更多的朋友可以參考下2012-12-12

