做爲一個主職的後端開發者,在平時的工做中,最討厭的作的事情能夠說是參數校驗和接口的版本支持了。對於客戶端的同窗來講,業務的歷史包袱會小不少,當出現不兼容的業務變更時,直接開發新的就好;然然後端就沒有這麼簡單了,歷史的接口得支持,新的業務也得支持,吭哧吭哧的新加一個服務接口,url 又不能和以前的相同,怎麼辦?只能在某個地方加一個相似v1
, v2
...git
那麼有沒有一種不改變 url,經過其餘的方式來支持版本管理的方式呢?github
本文將介紹一種,利用請求頭來傳遞客戶端版本,在相同的 url 中尋找最適合的這個版本請求的接口的實例 caseweb
主要用到的知識點爲:spring
<!-- more -->後端
咱們但願同一個業務始終用相同的 url,即使不一樣的版本之間業務徹底不兼容,經過請求參數中的版本選擇最合適的後端接口來響應這個請求api
須要實現上面的 case,首先有兩個約定app
明確上面兩點前提以後,就是基本規則了框架
版本定義ide
根據常見的三段式版本設計,版本格式定義以下
x.x.x
接口選擇
一般的 web 請求都是經過 url 匹配規則來選擇對應響應接口,可是在咱們這裏,一個 url,可能會有多個不一樣的接口,該怎麼選擇呢?
明確上面的應用場景以後,開始設計與實現
首先咱們須要一個版本定義的註解,用於標記 web 服務接口的版本,默認版本好爲 1.0.0
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Api { /** * 版本 * * @return */ String value() default "1.0.0"; }
其次須要一個版本對應的實體類,注意下面的實現中,默認版本爲1.0.0
,並實現了Comparable
接口,支持版本之間的比較
@Data public class ApiItem implements Comparable<ApiItem> { private int high = 1; private int mid = 0; private int low = 0; public ApiItem() { } @Override public int compareTo(ApiItem right) { if (this.getHigh() > right.getHigh()) { return 1; } else if (this.getHigh() < right.getHigh()) { return -1; } if (this.getMid() > right.getMid()) { return 1; } else if (this.getMid() < right.getMid()) { return -1; } if (this.getLow() > right.getLow()) { return 1; } else if (this.getLow() < right.getLow()) { return -1; } return 0; } }
須要一個將 string 格式的版本轉換爲 ApiItem 的轉換類,而且支持了默認版本爲1.0.0
的設定
public class ApiConverter { public static ApiItem convert(String api) { ApiItem apiItem = new ApiItem(); if (StringUtils.isBlank(api)) { return apiItem; } String[] cells = StringUtils.split(api, "."); apiItem.setHigh(Integer.parseInt(cells[0])); if (cells.length > 1) { apiItem.setMid(Integer.parseInt(cells[1])); } if (cells.length > 2) { apiItem.setLow(Integer.parseInt(cells[2])); } return apiItem; } }
須要一個 url,支持多個請求接口,能夠考慮經過RequestCondition
來實現,下面是具體的實現類
public class ApiCondition implements RequestCondition<ApiCondition> { private ApiItem version; public ApiCondition(ApiItem version) { this.version = version; } @Override public ApiCondition combine(ApiCondition other) { // 選擇版本最大的接口 return version.compareTo(other.version) >= 0 ? new ApiCondition(version) : new ApiCondition(other.version); } @Override public ApiCondition getMatchingCondition(HttpServletRequest request) { String version = request.getHeader("x-api"); ApiItem item = ApiConverter.convert(version); // 獲取全部小於等於版本的接口 if (item.compareTo(this.version) >= 0) { return this; } return null; } @Override public int compareTo(ApiCondition other, HttpServletRequest request) { // 獲取最大版本對應的接口 return other.version.compareTo(this.version); } }
雖然上面的實現比較簡單,可是有必要注意一下兩個邏輯
getMatchingCondition
方法中,控制了只有版本小於等於請求參數中的版本的 ApiCondition 才知足規則compareTo
指定了當有多個ApiCoondition
知足這個請求時,選擇最大的版本自定義RequestMappingHandlerMapping
實現類ApiHandlerMapping
public class ApiHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { return buildFrom(AnnotationUtils.findAnnotation(handlerType, Api.class)); } @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { return buildFrom(AnnotationUtils.findAnnotation(method, Api.class)); } private ApiCondition buildFrom(Api platform) { return platform == null ? new ApiCondition(new ApiItem()) : new ApiCondition(ApiConverter.convert(platform.value())); } }
註冊
@Configuration public class ApiAutoConfiguration implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiHandlerMapping(); } }
基於此,一個實現接口版本管理的微框架已經完成;接下來進入測試環節
設計三個接口,一個不加上註解,兩外兩個添加不一樣版本的註解
@RestController @RequestMapping(path = "v1") public class V1Rest { @GetMapping(path = "show") public String show1() { return "v1/show 1.0.0"; } @Api("1.1.2") @GetMapping(path = "show") public String show2() { return "v1/show 1.1.2"; } @Api("1.1.0") @GetMapping(path = "show") public String show3() { return "v1/show 1.1.0"; } }
在發起請求時,分別不帶上版本,帶指定版本,來測試對應的響應
1.0.0
的版本每一個方法上添加版本有點蛋疼,在上面的註解定義中,就支持了類上註解,從實現上也能夠看出,當方法和類上都有註解時,選擇最大的版本
@Api("2.0.0") @RestController @RequestMapping(path = "v2") public class V2Rest { @Api("1.1.0") @GetMapping(path = "show") public String show0() { return "v2/show0 1.1.0"; } @GetMapping(path = "show") public String show1() { return "v2/show1 2.0.0"; } @Api("2.1.1") @GetMapping(path = "show") public String show2() { return "v2/show2 2.1.1"; } @Api("2.2.0") @GetMapping(path = "show") public String show3() { return "v2/show3 2.2.0"; } }
根據咱們的實現規則,show0 和 show1 都會相應 <2.1.1
的版本請求,這個時候會出現衝突;
相關博文
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛