您好,登錄后才能下訂單哦!
這篇文章主要介紹“springboot怎么實現接口灰度發布”,在日常操作中,相信很多人在springboot怎么實現接口灰度發布問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”springboot怎么實現接口灰度發布”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
對灰度發布有所了解的同學應該知道,灰度發布的目的之一,就是能夠根據業務規則的調整,交互上呈現不同的形式,舉例來說,當前有2個版本,V1.0和V2.0 ,那么可能表現的形式大概有下面幾種:
V1.0,界面上的交互形態為A,V2.0版本界面上的交互形式為B;
某個交互,針對同一個接口A來說,V1.0,請求接口A,要求的返回值包括5個字段;V2.0,請求接口A,要求返回值包括10個字段;
某個交互,在V1.0和V2.0中,將使用不同的接口;
實際情況可能會更復雜,在微服務廣泛使用的今天,一般的思路是,通過一個獲取配置的接口,前端拿到所有的參數配置,根據參數配置的不同,具體實現思路如下:
比如V1版本下,某個配置的值為1,這時候使用A交互;如果要使用交互B,只需要更改配置中心這個值為2,則前端就可以將交互切位B;
或者說,交互不變,但是交互的處理邏輯更復雜了,于是原來的接口無法再滿足要求,這時候,可以重新提供一個接口,同樣通過配置參數的不同來控制;
于是,從后端接口層面來說,一個比較常用也是通用的處理方式是,通過配置接口來達到切換交互,或者說達到灰度發布的目的,灰度發布的核心本質也正在于通過某種方式從一種數據形態切換到另一種形態;
上面聊到了通過配置參數接口來達到灰度的目的,事實上,在一些規模較小的項目中,并沒有接入分布式配置中心的情況下,可能上面的解決辦法并不是一個很好的方式;
舉例來說,灰度要達到的目的是,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開發經驗的同學對這個圖應該不陌生;
從大的分類上,主要包括下面幾個核心處理組件:
Dispatcher Servlet ,請求分發器,收到請求調用處理器映射器HandlerMapping;
HandlerMapping,HandlerAdapter,處理器映射器和處理器適配器,根據請求的url地址,定位到具體的controller中的具體的處理方法;
View Resolver,視圖解析器 ,解析接口的返回數據并返回具體View給Dispatcher Servlet ;
在上面這幾個組件中,需要重點關注這個叫做 HandlerMapping 的組件,為了實現上文談到的灰度發布功能,就需要好好研究下HandlerMapping的原理;
HandlerMapping在這個SpringMVC體系結構中有著舉足輕重的地位,充當著url和Controller之間映射關系配置的角色,主要有三部分組成:
HandlerMapping 映射注冊;
根據url獲取對應的處理器;
攔截器注冊
在springmvc中,其核心類為 RequestMappingHandlerMapping ,該類中的囊括了與請求映射處理相關的所有實現,舉例來說,
match(HttpServletRequest request, String pattern) ,通過里面的match方法,可以將request中的請求路徑與規則路徑做匹配;
registerHandlerMethod,注冊處理器;
在該類中,我們注意到這樣兩個如下的方法,但是其方法內部無任何的實現邏輯,對spring源碼稍有了解的同學應該知道,這個肯定是spring框架對于該類預留出來的可供開發中擴展的方法,而這兩個方法就是用于實現本次需求的兩個核心方法;
我們注意到兩個方法的返回值均為RequestCondition,即請求條件的對象,從上面了解到HandlerMapping 是在容器初始化執行,那么一定有一個時機,只要客戶端重寫了HandlerMapping的這兩個方法內部的邏輯,就可以通過解析handleType的參數,達到通過某種參數條件,滿足本文的最小化前端改造的需求;
關于RequestCondition幾點補充:
RequestCondition是Spring MVC對一個請求匹配條件的概念建模;
實現類可能是針對以下情況之一:路徑匹配,頭部匹配,請求參數匹配,可產生MIME匹配,可消費MIME匹配,請求方法匹配,或者是以上各種情況的匹配條件的一個組合;
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怎么實現接口灰度發布”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。