SpringBoot API增加version版本號(hào)方式
SpringBoot 增加 API Version
基于restful風(fēng)格上,增加version版本號(hào)
例如: get /api/v1/users/
一、增加ApiVersion自定義注解
作用于Controller上,指定API版本號(hào)
這里版本號(hào)使用了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匹配條件
這里通過自定義匹配條件,識(shí)別ApiVersion,進(jìn)行版本匹配
getMatchingCondition 用于檢查URL中,是否符合/v{版本號(hào)},用于過濾無版本號(hào)接口;
compareTo 用于決定多個(gè)相同API時(shí),使用哪個(gè)接口進(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> { /** * 接口路徑中的版本號(hào)前綴,如: 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 請(qǐng)求url相同時(shí),觸發(fā)此方法用于合并url] * 官方解釋: * - 某個(gè)接口有多個(gè)規(guī)則時(shí),進(jìn)行合并 * - 比如類上指定了@RequestMapping的 url 為 root * - 而方法上指定的@RequestMapping的 url 為 method * - 那么在獲取這個(gè)接口的 url 匹配規(guī)則時(shí),類上掃描一次,方法上掃描一次,這個(gè)時(shí)候就需要把這兩個(gè)合并成一個(gè),表示這個(gè)接口匹配root/method * @param other 相同api version condition * @return ApiVersionCondition */ @Override public ApiVersionCondition combine(ApiVersionCondition other) { // 此處按優(yōu)先級(jí),method大于class return new ApiVersionCondition(other.getApiVersion()); } /** * 判斷是否成功,失敗返回 null;否則,則返回匹配成功的條件 * @param httpServletRequest http request * @return 匹配成功條件 */ @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) { // 通過uri匹配版本號(hào) 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; } /** * 多個(gè)都滿足條件時(shí),用來指定具體選擇哪一個(gè) * @param other 多個(gè)時(shí) * @param httpServletRequest http request * @return 取版本號(hào)最大的 */ @Override public int compareTo(ApiVersionCondition other, HttpServletRequest httpServletRequest) { // 當(dāng)出現(xiàn)多個(gè)符合匹配條件的ApiVersionCondition,優(yōu)先匹配版本號(hào)較大的 return other.getApiVersion().version() >= getApiVersion().version() ? 1 : -1; } public ApiVersion getApiVersion() { return apiVersion; } }
三、重寫RequestMappingHandlerMapping處理
通過重寫 RequestMappingHandlerMapping 類,對(duì)RequestMappering進(jìn)行識(shí)別@ApiVersion注解,針對(duì)性處理;
這里考慮到有些接口不存在版本號(hào),則使用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注解指定該接口版本號(hào)
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.缺點(diǎn)很明顯,需要在URL上加入{version},才能進(jìn)行匹配成功,這種PathVariable識(shí)別過于模糊,后期排查問題增加困難;
3.建議通過包名增加v1/v2明顯區(qū)分版本,且在controller的URL上直接寫死v1版本號(hào),這種更直觀;
SpringBoot的項(xiàng)目API版本控制
一、自定義版本號(hào)標(biāo)記注解
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiVersion { /** * 標(biāo)識(shí)版本號(hào),從1開始 */ int value() default 1; }
二、重寫RequestCondition,自定義url匹配邏輯
@Data @Slf4j public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { /** * 接口路徑中的版本號(hào)前綴,如: 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)多個(gè)符合匹配條件的ApiVersionCondition,優(yōu)先匹配版本號(hào)較大的 */ @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { return other.getApiVersion() - getApiVersion(); } }
說明:
getMatchingCondition方法中,控制了只有版本小于等于請(qǐng)求參數(shù)中的版本的 ApiCondition 才滿足規(guī)則
compareTo 指定了當(dāng)有多個(gè)ApiCoondition滿足這個(gè)請(qǐng)求時(shí),選擇最大的版本
三、重寫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); } }
四、配置注冊(cè)自定義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"; } }
頁面測試效果:
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Spring 參數(shù)驗(yàn)證@Validated和@Valid的區(qū)別
這篇文章主要介紹了詳解參數(shù)驗(yàn)證 @Validated 和 @Valid 的區(qū)別,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01Spring AOP與AspectJ的對(duì)比及應(yīng)用詳解
這篇文章主要為大家介紹了Spring AOP與AspectJ的對(duì)比及應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Java后臺(tái)返回和處理JSon數(shù)據(jù)的方法步驟
這篇文章主要介紹了Java后臺(tái)返回和處理JSon數(shù)據(jù)的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09JavaWeb開發(fā)使用Cookie創(chuàng)建-獲取-持久化、自動(dòng)登錄、購物記錄、作用路徑
這篇文章主要介紹了JavaWeb開發(fā)使用Cookie創(chuàng)建-獲取-持久化、自動(dòng)登錄、購物記錄、作用路徑的相關(guān)知識(shí),非常不錯(cuò),對(duì)cookie持久化知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-08-08Java中如何給List進(jìn)行排序(這7種方法輕松實(shí)現(xiàn))
在Java項(xiàng)目中可能會(huì)遇到給出一些條件,將List元素按照給定條件進(jìn)行排序的情況,這篇文章主要給大家介紹了關(guān)于Java中如何給List進(jìn)行排序的相關(guān)資料,通過文中介紹的這7種方法可以輕松實(shí)現(xiàn),需要的朋友可以參考下2023-10-10Java中l(wèi)ist根據(jù)id獲取對(duì)象的幾種方式
這篇文章主要給大家介紹了關(guān)于Java中l(wèi)ist根據(jù)id獲取對(duì)象的幾種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07關(guān)于Java數(shù)組聲明、創(chuàng)建、初始化的相關(guān)介紹
這篇文章主要是關(guān)于Java數(shù)組聲明、創(chuàng)建、初始化的相關(guān)介紹,并給出其對(duì)應(yīng)的代碼,需要的朋友可以參考下2015-08-08SpringBoot整合ES多個(gè)精確值查詢 terms功能實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot整合ES多個(gè)精確值查詢 terms功能實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-06-06SpringCloud中的路由網(wǎng)關(guān)鑒權(quán)熔斷詳解
這篇文章主要介紹了SpringCloud中的路由網(wǎng)關(guān)鑒權(quán)熔斷詳解,Hystrix是一個(gè)用于處理分布式系統(tǒng)的延遲和容錯(cuò)的開源庫,在分布式系統(tǒng)里,許多依賴不可避免的會(huì)調(diào)用失敗,比如超時(shí)、異常等,需要的朋友可以參考下2024-01-01