SpringBoot如何實現接口版本控制
SpringBoot 接口版本控制
一個系統(tǒng)在上線后會不斷迭代更新,需求也會不斷變化,有可能接口的參數也會發(fā)生變化,如果在原有的參數上直接修改,可能會影響到現有項目的正常運行,這時我們就需要設置不同的版本,這樣即使參數發(fā)生變化,由于老版本沒有變化,因此不會影響上線系統(tǒng)的運行。
這里我們選擇使用帶有一位小數的浮點數作為版本號,在請求地址末尾中帶上版本號,大致的地址如:http://api/test/1.0,其中,1.0即代表的是版本號。具體做法請看代碼
自定義一個版本號的注解接口ApiVersion.java
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
* 版本控制
* @author Zac
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
/**
* 標識版本號
* @return
*/
double value();
}
版本號篩選器ApiVersionCondition
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 版本號匹配篩選器
* @author Zac
*/
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
/**
* 路徑中版本的正則表達式匹配, 這里用 /1.0的形式
*/
private static final Pattern VERSION_PREFIX_PATTERN = Pattern.compile("^\\S+/([1-9][.][0-9])$");
private double apiVersion;
public ApiVersionCondition(double apiVersion) {
this.apiVersion = apiVersion;
}
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
// 采用最后定義優(yōu)先原則,則方法上的定義覆蓋類上面的定義
return new ApiVersionCondition(other.getApiVersion());
}
@Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
if (m.find()) {
Double version = Double.valueOf(m.group(1));
// 如果請求的版本號大于配置版本號, 則滿足
if (version >= this.apiVersion) {
return this;
}
}
return null;
}
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
// 優(yōu)先匹配最新的版本號
return Double.compare(other.getApiVersion(), this.apiVersion);
}
public double getApiVersion() {
return apiVersion;
}
}
版本號匹配攔截器
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 Zac
*/
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return createCondition(apiVersion);
}
@Override protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createCondition(apiVersion);
}
private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
}
}
配置WebMvcRegistrationsConfig
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@SpringBootConfiguration
public class WebMvcRegistrationsConfig extends WebMvcRegistrationsAdapter {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new CustomRequestMappingHandlerMapping();
}
}
controller層實現
/**
* version
* @return
*/
@GetMapping("/api/test/{version}")
@ApiVersion(2.0)
public String searchTargetImage() {
return "Hello! Welcome to Version2";
}
/**
* 多目標類型搜索,關聯圖片查詢接口
*
* @param attribute
* @return
*/
@GetMapping("/api/test/{version}")
@ApiVersion(1.0)
public AppResult searchTargetImage() {
return "Hello! Welcome to Version1";
}
SpringBoot 2.x 接口多版本
準備將現有的接口加上版本管理,兼容以前的版本。網上一調研,發(fā)現有很多示例,但是還是存在以下兩個問題。
1.大部分使用Integer作為版本號,但是通常的版本號形式為v1.0.0,
2.版本號攜帶在header中,對接調用不清晰。
針對以上兩個問題,做如下改造。
1.自定義接口版本注解ApiVersion
后面條件映射使用equals匹配,此處是否將String變?yōu)镾tring[]應對多個版本使用同一代碼的問題。
package com.yugioh.api.common.core.version;
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
* 接口版本
*
* @author lieber
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
String value() default "1.0.0";
}
2.請求映射條件ApiVersionCondition
package com.yugioh.api.common.core.version;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 版本控制
*
* @author lieber
*/
@AllArgsConstructor
@Getter
@Slf4j
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private String version;
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+(.\\d+){0,2}).*");
public final static String API_VERSION_CONDITION_NULL_KEY = "API_VERSION_CONDITION_NULL_KEY";
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
// 方法上的注解優(yōu)于類上的注解
return new ApiVersionCondition(other.getVersion());
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
if (m.find()) {
String version = m.group(1);
if (this.compareTo(version)) {
return this;
}
}
// 將錯誤放在request中,可以在錯誤頁面明確提示,此處可重構為拋出運行時異常
request.setAttribute(API_VERSION_CONDITION_NULL_KEY, true);
return null;
}
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
return this.compareTo(other.getVersion()) ? 1 : -1;
}
private boolean compareTo(String version) {
return Objects.equals(version, this.version);
}
}
3.創(chuàng)建自定義匹配處理器ApiVersionRequestMappingHandlerMapping
網上大部分只重寫了getCustomTypeCondition和getCustomMethodCondition方法。這里為了解決路由映射問題,重寫getMappingForMethod方法,在路由中加入前綴{version},加入后路由變?yōu)?api/{version}/xxx
package com.yugioh.api.common.core.version;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
/**
* 自定義匹配處理器
*
* @author lieber
*/
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private final static String VERSION_PREFIX = "{version}";
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return createCondition(apiVersion);
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createCondition(apiVersion);
}
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);
if (requestMappingInfo == null) {
return null;
}
return createCustomRequestMappingInfo(method, handlerType, requestMappingInfo);
}
private RequestMappingInfo createCustomRequestMappingInfo(Method method, Class<?> handlerType, RequestMappingInfo requestMappingInfo) {
ApiVersion methodApi = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
ApiVersion handlerApi = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion.class);
if (methodApi != null || handlerApi != null) {
return RequestMappingInfo.paths(VERSION_PREFIX).options(this.config).build().combine(requestMappingInfo);
}
return requestMappingInfo;
}
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
}
}
4.使用ApiVersionConfig配置來決定是否開啟多版本
package com.yugioh.api.common.core.version;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 版本管理器配置
*
* @author lieber
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "api.config.version", ignoreInvalidFields = true)
public class ApiVersionConfig {
/**
* 是否開啟
*/
private boolean enable;
}
5.配置WebMvcRegistrations,啟用自定義路由Mapping
package com.yugioh.api.common.core.version;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* @author lieber
*/
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ApiWebMvcRegistrations implements WebMvcRegistrations {
private final ApiVersionConfig apiVersionConfig;
@Autowired
public ApiWebMvcRegistrations(ApiVersionConfig apiVersionConfig) {
this.apiVersionConfig = apiVersionConfig;
}
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
if (apiVersionConfig.isEnable()) {
return new ApiVersionRequestMappingHandlerMapping();
}
return null;
}
}
至此我們就能在項目中愉快的使用@APIVersion來指定版本了,但是現在這個和swagger整合還會有問題,繼續(xù)研究中…
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java Hutool工具實現驗證碼生成及Excel文件的導入和導出
Hutool是一個小而全的Java工具類庫,通過靜態(tài)方法封裝,降低相關API的學習成本,提高工作效率,本文主要介紹了使用Hutool工具實現驗證碼生成和excel文件的導入、導出,需要的朋友可參考一下2021-11-11
Java字符串拼接的五種方法及性能比較分析(從執(zhí)行100次到90萬次)
字符串拼接一般使用“+”,但是“+”不能滿足大批量數據的處理,Java中有以下五種方法處理字符串拼接及性能比較分析,感興趣的可以了解一下2021-12-12
Java實戰(zhàn)之小蜜蜂擴音器網上商城系統(tǒng)的實現
這篇文章主要介紹了如何利用Java實現簡單的小蜜蜂擴音器網上商城系統(tǒng),文中采用到的技術有JSP、Servlet?、JDBC、Ajax等,感興趣的可以動手試一試2022-03-03
SpringBoot整合Ip2region獲取IP地址和定位的詳細過程
ip2region v2.0 - 是一個離線IP地址定位庫和IP定位數據管理框架,10微秒級別的查詢效率,提供了眾多主流編程語言的 xdb 數據生成和查詢客戶端實現 ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06
java?stream實現分組BigDecimal求和以及自定義分組求和
這篇文章主要給大家介紹了關于java?stream實現分組BigDecimal求和以及自定義分組求和的相關資料,Stream是Java8的一大亮點,是對容器對象功能的增強,它專注于對容器對象進行各種非常便利、高效的聚合操作或者大批量數據操作,需要的朋友可以參考下2023-12-12

