<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
對灰度釋出有所瞭解的同學應該知道,灰度釋出的目的之一,就是能夠根據業務規則的調整,互動上呈現不同的形式,舉例來說,當前有2個版本,V1.0和V2.0 ,那麼可能表現的形式大概有下面幾種:
實際情況可能會更復雜,在微服務廣泛使用的今天,一般的思路是,通過一個獲取設定的介面,前端拿到所有的引數設定,根據引數設定的不同,具體實現思路如下:
於是,從後端介面層面來說,一個比較常用也是通用的處理方式是,通過設定介面來達到切換互動,或者說達到灰度釋出的目的,灰度釋出的核心本質也正在於通過某種方式從一種資料形態切換到另一種形態;
上面聊到了通過設定引數介面來達到灰度的目的,事實上,在一些規模較小的專案中,並沒有接入分散式設定中心的情況下,可能上面的解決辦法並不是一個很好的方式;
舉例來說,灰度要達到的目的是,V1.0 的 獲取使用者列表的介面返回的是本月新增的使用者,而V2.0要求返回最近2個月註冊的使用者,而且介面地址不變,最多就是在引數上面允許適當變更,即做到前端最小化改動;
這個需求,乍然一想,覺得很是不可思議,一個controller類裡面,兩個同樣的介面對映路徑肯定不行的啊,比如看下面這個例子,
@RestController public class UserController { @Autowired private UserService userService; @GetMapping("/list") public Object getUserLists1(){ return userService.getUserLists1(); } @GetMapping("/list") public Object getUserLists2(){ return userService.getUserLists2(); } }
當前請求介面時,直接報錯了,這個錯誤想必大家都能理解吧,我就不過多做解釋了
下面貼出一張關於springmvc介面請求原理的流程圖,即一個請求最終到達某個具體的controller時經歷的一個完整的過程,相信有個SSM開發或者springboot開發經驗的同學對這個圖應該不陌生;
從大的分類上,主要包括下面幾個核心處理元件:
在上面這幾個元件中,需要重點關注這個叫做 HandlerMapping 的元件,為了實現上文談到的灰度釋出功能,就需要好好研究下HandlerMapping的原理;
HandlerMapping在這個SpringMVC體系結構中有著舉足輕重的地位,充當著url和Controller之間對映關係設定的角色,主要有三部分組成:
在springmvc中,其核心類為 RequestMappingHandlerMapping ,該類中的囊括了與請求對映處理相關的所有實現,舉例來說,
在該類中,我們注意到這樣兩個如下的方法,但是其方法內部無任何的實現邏輯,對spring原始碼稍有了解的同學應該知道,這個肯定是spring框架對於該類預留出來的可供開發中擴充套件的方法,而這兩個方法就是用於實現本次需求的兩個核心方法;
我們注意到兩個方法的返回值均為RequestCondition,即請求條件的物件,從上面瞭解到HandlerMapping 是在容器初始化執行,那麼一定有一個時機,只要使用者端重寫了HandlerMapping的這兩個方法內部的邏輯,就可以通過解析handleType的引數,達到通過某種引數條件,滿足本文的最小化前端改造的需求;
關於RequestCondition幾點補充:
public interface RequestCondition<T> { //和另外一個請求匹配條件合併,具體合併邏輯由實現類提供 T combine(T var1); // 檢查當前請求匹配條件和指定請求request是否匹配,如果不匹配返回null, // 如果匹配,生成一個新的請求匹配條件,該新的請求匹配條件是當前請求匹配條件 // 針對指定請求request的剪裁。 // 舉個例子來講,如果當前請求匹配條件是一個路徑匹配條件,包含多個路徑匹配模板, // 並且其中有些模板和指定請求request匹配,那麼返回的新建的請求匹配條件將僅僅 // 包含和指定請求request匹配的那些路徑模板。 @Nullable T getMatchingCondition(HttpServletRequest var1); // 針對指定的請求物件request比較兩個請求匹配條件。 // 該方法假定被比較的兩個請求匹配條件都是針對該請求物件request呼叫了 // #getMatchingCondition方法得到的,這樣才能確保對它們的比較 // 是針對同一個請求物件request,這樣的比較才有意義(最終用來確定誰是 // 更匹配的條件)。 int compareTo(T var1, HttpServletRequest var2); }
由介面原始碼可以看出,介面RequestCondition是一個泛型介面。事實上,它的泛型引數T通常也會是一個RequestCondition物件,搞清這一點就能和上面的HandlerMapping中的兩個即將要重寫的方法就能產生聯絡了;
通過上面的分析,我們瞭解到可以通過HandlerMapping 中的getCustomTypeCondition方法和getCustomMethodCondition方法,讀取到介面類或者介面方法中的元資訊,比如介面路徑,註解,方法名稱等,
怎樣才能實現前端的最小化改造呢?主要思路是,通過引數控制的形式,比如前端不用改動原來的介面地址,只需傳入不同的引數即可滿足要求,於是可以通過自定義註解的形式,給不同的方法新增註解,通過封裝註解引數為RequestCondition的方式來實現;
import java.lang.annotation.*; @Documented @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiVersion { //具體版本號 double value(); }
新增一個類,繼承RequestMappingHandlerMapping,重寫裡面的兩個方法,封裝成RequestCondition提供後續呼叫;
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; /** * 支援使用多版本的控制器 */ public class ApiVersionHandleMapping extends RequestMappingHandlerMapping { /** * 容器初始化執行 * 所有controller都會使用該方法 * @param handlerType * @return */ @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.getAnnotation(handlerType, ApiVersion.class); return new ApiVersionRequestCondition(apiVersion != null ? apiVersion.value() : 1.0); } /** * 容器初始化時執行 * @param method * @return */ @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { ApiVersion apiVersion = AnnotationUtils.getAnnotation(method, ApiVersion.class); if(apiVersion == null){ apiVersion = AnnotationUtils.getAnnotation(method.getDeclaringClass(), ApiVersion.class); } return new ApiVersionRequestCondition(apiVersion != null ? apiVersion.value() : 1.0); } }
封裝子自定義的RequestCondition邏輯,該類會在使用者端請求介面時,根據入參進行一系列的與真正的執行介面進行匹配的邏輯操作,比如,預設情況下,如果請求URL中不傳入任何引數,將返回預設的 V1.0的介面;
import org.apache.commons.lang.StringUtils; import org.springframework.web.servlet.mvc.condition.RequestCondition; import javax.servlet.http.HttpServletRequest; public class ApiVersionRequestCondition implements RequestCondition<ApiVersionRequestCondition> { private double apiVersion = 1.0; private static final String VERSION_NAME = "api-version"; public double getApiVersion() { return apiVersion; } public ApiVersionRequestCondition(double apiVersion){ this.apiVersion=apiVersion; } @Override public ApiVersionRequestCondition combine(ApiVersionRequestCondition method) { return method; } @Override public int compareTo(ApiVersionRequestCondition other, HttpServletRequest request) { return Double.compare(other.getApiVersion(),this.getApiVersion()); } @Override public ApiVersionRequestCondition getMatchingCondition(HttpServletRequest request) { double reqVersionDouble = 1.0; String reqVersion = request.getHeader(VERSION_NAME); if(StringUtils.isEmpty(reqVersion)){ reqVersion = request.getParameter(VERSION_NAME); } if(!StringUtils.isEmpty(reqVersion)){ reqVersionDouble = Double.parseDouble(reqVersion); } if(this.getApiVersion() == reqVersionDouble){ return this; } return null; } }
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; public class ApiVersionMappingRegister implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiVersionHandleMapping(); } }
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BaseConfiguration { @Bean public WebMvcRegistrations getWebMvcRegistrations(){ return new ApiVersionMappingRegister(); } }
對本文開篇的介面做簡單的改造,新增自定義註解
import com.congge.configs.ApiVersion; import com.congge.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @ApiVersion(3.0) public class UserController { @Autowired private UserService userService; @GetMapping("/list") @ApiVersion(1.0) public Object getUserLists1(){ return userService.getUserLists1(); } @GetMapping("/list") @ApiVersion(2.0) public Object getUserLists2(){ return userService.getUserLists2(); } }
啟動專案後,做如下介面測試:
1、不新增任何引數,預設不加任何引數,將請求V1版本的介面
2、介面請求中新增 api-version = 2.0 ,將請求到V2對應的介面
通過以上的演示,我們基本上實現了一個基於 springboot 實現介面多版本控制的介面灰度釋出的功能。
到此這篇關於springboot 實現介面灰度釋出的文章就介紹到這了,更多相關springboot 灰度釋出內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45