一,為什么接口站的api要使用版本號?
1,當服務端接口的功能發生改進后,
客戶端如果不更新版本,
3,版本號需要能向下兼容,
如果訪問一個高的版本不存在時,應該能訪問到比它小的離它最近的版本,
因為如果每個入口或每個功能在有新版本時都要重新寫一遍,
則和把代碼復制一份沒有兩樣了,
這樣才可以方便的管理
4,版本號可以放到請求頭和url中,
我們采用比較直觀的放到url中的形式演示,
形如: /v1.2/home/page?
5,說明:生產環境中沒發現有必要在次版本號后面再加一級,
所以有次版本號后已經夠用了,
大家如果認為有必要增加一級的話,可以修改代碼中VERSION_PREFIX_PATTERN的正則:
說明:劉宏締的架構森林是一個專注架構的博客,地址:https://www.cnblogs.com/architectforest
對應的源碼可以訪問這里獲取: https://github.com/liuhongdi/
說明:作者:劉宏締 郵箱: 371125307@qq.com
二,演示項目的相關信息
1,項目地址:
https://github.com/liuhongdi/apiversion
2,項目說明:
我們給接口站的接口增加api的版本號
支持整數方式(例:v1)和次版本號形式(例:v1.3)
3,項目結構:如圖:
三,java代碼說明
1,ApiVersion.java
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiVersion { //版本號的值,從1開始 String value() default "1"; }
用來添加版本號的一個注解
2,ApiVersionCondition.java
//實現RequestCondition public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { //api版本號 private String apiVersion; //版本號的格式,如: /v[1-n]/api/test or /v1.5/home/api private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v((\\d+\\.\\d+)|(\\d+))/"); public ApiVersionCondition(String apiVersion) { this.apiVersion = apiVersion; } //將不同的篩選條件進行合並 @Override public ApiVersionCondition combine(ApiVersionCondition other) { // 采用最后定義優先原則,則方法上的定義覆蓋類上面的定義 return new ApiVersionCondition(other.getApiVersion()); } //版本比對,用於排序 @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { //優先匹配最新版本號 return compareTo(other.getApiVersion(),this.apiVersion)?1:-1; } //獲得符合匹配條件的ApiVersionCondition @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI()); if (m.find()) { String version = m.group(1); if (compareTo(version,this.apiVersion)){ return this; } } return null; } //compare version private boolean compareTo(String version1,String version2){ if (!version1.contains(".")) { version1 += ".0"; } if (!version2.contains(".")) { version2 += ".0"; } String[] split1 = version1.split("\\."); String[] split2 = version2.split("\\."); for (int i = 0; i < split1.length; i++) { if (Integer.parseInt(split1[i])<Integer.parseInt(split2[i])){ return false; } } return true; } public String getApiVersion() { return apiVersion; } }
實現查找和比較版本號的功能
3,ApiVersionRequestMappingHandlerMapping.java
//擴展RequestMappingHandlerMapping public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { //類上有 @ApiVersion注解時生效 @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); return createRequestCondition(apiVersion); } //方法上有 @ApiVersion注解時生效 @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class); return createRequestCondition(apiVersion); } //返回ApiVersionCondition private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) { return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value()); } }
繼承RequestMappingHandlerMapping,在類上和方法上有注解時,
使用ApiVersionCondition進行處理
4,WebMvcConfig.java
@Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { //在獲取RequestMappingHandlerMapping時 //返回我們自定義的ApiVersionRequestMappingHandlerMapping @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { return new ApiVersionRequestMappingHandlerMapping(); } }
把createRequestMappingHandlerMapping方法返回時使用我們自定義的ApiVersionRequestMappingHandlerMapping
5,HomeController.java
@RestController @RequestMapping("/{version}/home") public class HomeController { //匹配版本v1的訪問 @ApiVersion("1") @GetMapping @RequestMapping("/home") public String home01(@PathVariable String version) { return "home v1 : version:" + version; } //匹配版本v2的訪問 @ApiVersion("2.0") @GetMapping @RequestMapping("/home") public String home02(@PathVariable String version) { return "home v2 version: " + version; } //匹配版本v1.5-2.0的訪問 @ApiVersion("1.5") @GetMapping @RequestMapping("/home") public String home15(@PathVariable String version) { return "home v1.5 version: " + version; } }
6,GoodsV1Controller和GoodsV2Controller
把版本號加到controller上,
和加在方法上的使用一樣,
為節省篇幅,不再貼代碼,大家可以自己去github上訪問
四,測試效果
1,訪問
http://127.0.0.1:8080/v1.0/home/home
返回:
home v1 : version:v1.0
訪問
http://127.0.0.1:8080/v2/home/home
返回:
home v2 version: v2
可見版本號帶不帶點,不妨礙它訪問到對應的版本
2,訪問:
http://127.0.0.1:8080/v3.0/home/home
返回:
home v2 version: v3.0
訪問:
http://127.0.0.1:8080/v1.14/home/home
返回:
home v1.5 version: v1.14
可見版本號可以自動訪問到距它最近的版本的功能
五, 查看spring boot的版本
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.2.RELEASE)