本文所有程式碼基於 SpringBoot 2。1。9(spring-webmvc-5。1。10。RELEASE)版本進行編寫測試,若版本不一致呢可能會導致部分配置報錯。
目的: 可以將業務介面以版本分開管理,針對於每次版本的迭代更新,只需要新增新的版本介面,從而不會影響仍在使用舊版本介面的使用者體驗,即是請求介面的時候以版本向下匹配,匹配最新的介面,這樣使得系統升級比較平滑,對使用者體驗比較友好。
編寫版本註解
此註解@ApiVersion用於介面註解,標識這個介面是歸屬於哪個版本的。
package org。apiserver。spring。config;
import org。springframework。web。bind。annotation。Mapping;
import java。lang。annotation。*;
/**
* @author JTJ
*/
@Target({ ElementType。METHOD, ElementType。TYPE })
@Retention(RetentionPolicy。RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
/**
* @Author: JTJ
* @Date: 2020/09/15 上午 09:20
* @Description: 版本號
*/
int value() default 1;
}
構建一個自定義請求條件
這裡採用了 v[1-9] 這樣的版本請求方式的寫法
package org。apiserver。spring。config;
import lombok。extern。slf4j。Slf4j;
import org。springframework。web。servlet。mvc。condition。RequestCondition;
import javax。servlet。http。HttpServletRequest;
import java。util。regex。Matcher;
import java。util。regex。Pattern;
/**
* @author JTJ
*/
@Slf4j
public class ApiVersionCondition implements RequestCondition
/**
* 路徑中版本的字首, 這裡用 /v[1-9]/的形式
*/
private final static Pattern VERSION_PREFIX_PATTERN = Pattern。compile(“v(\\d+)/”);
private int apiVersion;
public ApiVersionCondition(int apiVersion) {
this。apiVersion = apiVersion;
}
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
// 採用最後定義優先原則,則方法上的定義覆蓋類上面的定義
log。info(“combine方法({})”,other);
return new ApiVersionCondition(other。getApiVersion());
}
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
// 優先匹配最新的版本號
log。info(“compareTo方法({})”,other);
return other。getApiVersion() - this。apiVersion;
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
Matcher m = VERSION_PREFIX_PATTERN。matcher(request。getServletPath());
Integer version;
if (m。find()) {
version = Integer。valueOf(m。group(1));
if (version >= this。apiVersion) {
return this;
}
} else {
return this;
}
log。info(“compareTo方法({})”,version);
return null;
}
public int getApiVersion() {
return apiVersion;
}
}
構建自定義請求對映處理器
package org。apiserver。spring。config;
import lombok。extern。slf4j。Slf4j;
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;
/**
* @author JTJ
*/
@Slf4j
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return createCondition(AnnotationUtils。findAnnotation(handlerType,ApiVersion。class));
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
return createCondition(AnnotationUtils。findAnnotation(method,ApiVersion。class));
}
private RequestCondition
log。info(“開始構建請求條件({})”,apiVersion);
return apiVersion == null ? null : new ApiVersionCondition(apiVersion。value());
}
}
配置請求對映器
package org。apiserver。spring。config;
import org。springframework。context。annotation。Configuration;
import org。springframework。web。servlet。config。annotation。WebMvcConfigurationSupport;
import org。springframework。web。servlet。mvc。method。annotation。RequestMappingHandlerMapping;
/**
* @author JTJ
*/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
handlerMapping。setOrder(0);
handlerMapping。setInterceptors(getInterceptors());
return handlerMapping;
}
}
測試
編寫測試介面
這裡編寫v1、v2、v3版本介面
package org。apiserver。spring。v_1;
import lombok。extern。slf4j。Slf4j;
import org。apiserver。spring。config。ApiVersion;
import org。springframework。web。bind。annotation。GetMapping;
import org。springframework。web。bind。annotation。RequestMapping;
import org。springframework。web。bind。annotation。RestController;
/**
* @author JTJ
*/
@Slf4j
@RestController
@RequestMapping(“api/{version}”)
public class TestControllerV1 {
@ApiVersion()
@GetMapping(“test”)
public void v1(){
log。error(“v1 的 test方法!!”);
}
@ApiVersion(2)
@GetMapping(“test”)
public void v2(){
log。error(“v2 的 test方法!!”);
}
@ApiVersion(3)
@GetMapping(“test”)
public void v3(){
log。error(“v3 的 test方法!!”);
}
}
測試結果
http://localhost:8080/api/v1/test 響應 ‘v1 的 test方法!!’
http://localhost:8080/api/v2/test 響應 ‘v2 的 test方法!!’
http://localhost:8080/api/v3/test 響應 ‘v3 的 test方法!!’
http://localhost:8080/api/v4/test 響應 ‘v3 的 test方法!!’
可以分析出已經做到了請求版本匹配的規則了:若版本存在則匹配相應版本的介面,若不存在則匹配最新的介面,v[1-9]是版本取值範圍,若請求v0或v10等等,則會找不到對應的介面。