中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

springmvc實現restful api版本控制并兼容swagger的方法

發布時間:2021-06-23 14:33:41 來源:億速云 閱讀:204 作者:chen 欄目:編程語言

這篇文章主要講解了“springmvc實現restful api版本控制并兼容swagger的方法”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“springmvc實現restful api版本控制并兼容swagger的方法”吧!

springmvc實現restful api版本控制并兼容swagger

開發的時候經常會出現接口的變更,而接口的變更又常需兼容老的客戶端版本,此時便需要做接口的版本控制,比較好的方案是在header帶上version參數,但是不排除有些在url上做控制的,類似/v1/xxx/xxx,/v2_0/xxx/xxx,此文為第二種,使用如下:

[@RestController](https://my.oschina.net/u/4486326)
@Api(value = "測試", tags = {"測試"})
@RequestMapping("/{version}/test/")
public class TestController {
 @Autowired
 private TestService testService;
  
 @ApiOperation(value = "測試接口", notes = "測試")
 @PostMapping(value = "test")
 @ApiVersion("1_0")
 public Object region(@RequestBody TestReq model) {
 return testService.test(model);
 }
}
  • 首先是@ApiVersion的使用,定義如下

import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
 \* ClassName: ApiVersion<br/>
 \* Function: url版本注解<br/>
 \* Reason: value書寫格式 n\_n,匹配規則為小于等于n\_n最近的一個版本,示例如下:
 \* 現有接口 v1\_0,v1\_1,v2\_0,v2\_3
 \* 訪問 v1\_1/xxx   執行: v1\_1
 \* 訪問 v1\_2/xxx   執行: v1\_1
 \* 訪問 v2\_1/xxx   執行: v2\_0
 \* 訪問 v4\_0/xxx   執行: v2\_3
 \* <br/>
 \* Date: 2017-10-31 14:33<br/>
 *
 \* @author zoro
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
 // 版本號
 String value();
}
  • 接下來是自定義條件選擇器,當請求進來可以正確匹對需要調用的方法

import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 \* ClassName: ApiVesrsionCondition<br/>
 \* Function: API版本條件篩選器<br/>
 \* Reason: TODO ADD REASON(可選)<br/>
 \* Date: 2017-10-31 14:35<br/>
 *
 \* @author zoro
 */
public class ApiVesrsionCondition implements RequestCondition<ApiVesrsionCondition> {
 private String apiVersion;
 public ApiVesrsionCondition(String apiVersion){
 this.apiVersion = apiVersion;
 }
 public ApiVesrsionCondition combine(ApiVesrsionCondition other) {
 // 采用最后定義優先原則,則方法上的定義覆蓋類上面的定義
 return new ApiVesrsionCondition(other.getApiVersion());
 }
 public ApiVesrsionCondition getMatchingCondition(HttpServletRequest request) {
 if (compare(getPathInfo(request)) >= 0) {
 return this;
 }
 return null;
 }
 public int compareTo(ApiVesrsionCondition other, HttpServletRequest request) {
 // 優先匹配最新的版本號
 return compare("v" + other.getApiVersion() + "/");
 }
 public String getApiVersion() {
 return apiVersion;
 }
 private String getPathInfo(HttpServletRequest request) {
 String uri = request.getRequestURI();
 String contextPath = request.getContextPath();
 if (contextPath != null && contextPath.length() > 0) {
 uri = uri.substring(contextPath.length());
 }
 return uri;
 }
 /**
 \* Description: 這里是版本的控制規則,不是很優雅,時間有限,寫死了只識別vn_n的格式.<br/>
 \* Date: 2017/11/10.<br/>
 \* @param version
 \* @return 
 \* @throws 
 */
 private int compare(String version) {
 // 路徑中版本的前綴, 這里用 /v\[0-9\]_\[0-9\]/的形式
 Pattern req\_patten = Pattern.compile("v(\\\d+)\_(\\\d+)/");
 // api版本的前綴, 這里用 \[0-9\]_\[0-9\]的形式
 Pattern api\_patten = Pattern.compile("(\\\d+)\_(\\\d+)");
 Matcher req\_m = req\_patten.matcher(version);
 Matcher api\_m = api\_patten.matcher(this.apiVersion);
 if (req\_m.find() && api\_m.find()) {
 Integer first\_version = Integer.valueOf(req\_m.group(1));
 Integer first\_apiVersion = Integer.valueOf(api\_m.group(1));
 // 如果請求的版本號大于配置版本號, 則滿足
 if(first\_version > first\_apiVersion) {
 return 1;
 } else if (first\_version == first\_apiVersion) {
 Integer last\_version = Integer.valueOf(req\_m.group(2));
 Integer last\_apiVersion = Integer.valueOf(api\_m.group(2));
 if(last\_version > last\_apiVersion) {
 return 1;
 } else if (last\_version == last\_apiVersion) {
 return 0;
 }
 }
 }
 return -1;
 }
}
  • 我們知道springmvc默認是用RequestMappingInfoHandlerMapping根據RequestMappingInfo來匹配條件的,所以我們自定義一個Handler來修改RequestMappingInfoHandlerMapping原有的匹配規則

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;
/**
 \* ClassName: CustomRequestMappingHandlerMapping<br/>
 \* Function: TODO ADD FUNCTION(可選)<br/>
 \* Reason: TODO ADD REASON(可選)<br/>
 \* Date: 2017-10-31 15:13<br/>
 *
 \* @author zoro
 */
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
 @Override
 protected RequestCondition<ApiVesrsionCondition> getCustomTypeCondition(Class<?> handlerType) {
 ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
 return createCondition(apiVersion);
 }
 @Override
 protected RequestCondition<ApiVesrsionCondition> getCustomMethodCondition(Method method) {
 ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
 return createCondition(apiVersion);
 }
 private RequestCondition<ApiVesrsionCondition> createCondition(ApiVersion apiVersion) {
 return apiVersion == null ? null : new ApiVesrsionCondition(apiVersion.value());
 }
}
  • 最后,只需要重新裝配一下自定義的HandlerMapping即可

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
 \* ClassName: WebConfig<br/>
 \* Function: TODO ADD FUNCTION(可選)<br/>
 \* Reason: TODO ADD REASON(可選)<br/>
 \* Date: 2017-10-31 15:15<br/>
 *
 \* @author zoro
 */
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
 @Override
 @Bean
 public RequestMappingHandlerMapping requestMappingHandlerMapping() {
 RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
 handlerMapping.setOrder(0);
 handlerMapping.setInterceptors(getInterceptors());
 return handlerMapping;
 }
}
  • 至此,便可以正常的使用了,但是如果項目引入了swagger的,會發現一個問題,項目啟動后,swagger頁面無法打開,這是因為繼承WebMvcConfigurationSupport后,靜態文件的映射出現了問題,需要重新指定一下,WebConfig修改如下:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
 \* ClassName: WebConfig<br/>
 \* Function: TODO ADD FUNCTION(可選)<br/>
 \* Reason: TODO ADD REASON(可選)<br/>
 \* Date: 2017-10-31 15:15<br/>
 *
 \* @author zoro
 */
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
 @Override
 @Bean
 public RequestMappingHandlerMapping requestMappingHandlerMapping() {
 RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
 handlerMapping.setOrder(0);
 handlerMapping.setInterceptors(getInterceptors());
 return handlerMapping;
 }
 /**
 \* 發現如果繼承了WebMvcConfigurationSupport,則在yml中配置的相關內容會失效。
 \* 需要重新指定靜態資源
 \* @param registry
 */
 @Override
 public void addResourceHandlers(ResourceHandlerRegistry registry) {
 registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
 registry.addResourceHandler("swagger-ui.html")
 .addResourceLocations("classpath:/META-INF/resources/");
 registry.addResourceHandler("/webjars/**")
 .addResourceLocations("classpath:/META-INF/resources/webjars/");
 super.addResourceHandlers(registry);
 }
 /**
 \* 配置servlet處理
 */
 @Override
 public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
 configurer.enable();
 }
}
  • 此時,swagger也能正常打開了,卻發現swagger生成的api文檔是{version}/xxx/xxx的,這就非常惡心了,我們知道,springmvc的映射信息是放在RequestMappingInfo里的,先來看一段RequestMappingHandlerMapping的源碼

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware {
 .......上面的代碼省略......
 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
 RequestMappingInfo info = this.createRequestMappingInfo(method);
 if (info != null) {
 RequestMappingInfo typeInfo = this.createRequestMappingInfo(handlerType);
 if (typeInfo != null) {
 info = typeInfo.combine(info);
 }
 }
 return info;
 }
 private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
 RequestMapping requestMapping = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
 RequestCondition<?> condition = element instanceof Class ? this.getCustomTypeCondition((Class)element) : this.getCustomMethodCondition((Method)element);
 return requestMapping != null ? this.createRequestMappingInfo(requestMapping, condition) : null;
 }
 protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
 return null;
 }
 protected RequestCondition<?> getCustomMethodCondition(Method method) {
 return null;
 }
 protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, RequestCondition<?> customCondition) {
 return RequestMappingInfo.paths(this.resolveEmbeddedValuesInPatterns(requestMapping.path())).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name()).customCondition(customCondition).options(this.config).build();
 }
 .......下面的代碼省略......
}
  • 從getMappingForMethod方法可以知道,springmvc是先讀取方法上的@RequestMapping上的value,再讀類上@RequestMapping上的value,然后兩個值拼接在一起,理論上是可以在生成RequestMappingInfo后通過反射修改RequestMappingInfo里的值來達到目的的,這里想折騰一下,從createRequestMappingInfo的時候入手,通過改變注解上的value來達到效果,修改CustomRequestMappingHandlerMapping類如下

import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;
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.*;
import java.util.Map;
/**
 \* ClassName: CustomRequestMappingHandlerMapping<br/>
 \* Function: TODO ADD FUNCTION(可選)<br/>
 \* Reason: TODO ADD REASON(可選)<br/>
 \* Date: 2017-10-31 15:13<br/>
 *
 \* @author zoro
 */
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
 @Override
 protected RequestCondition<ApiVesrsionCondition> getCustomTypeCondition(Class<?> handlerType) {
 ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
 return createCondition(apiVersion);
 }
 @Override
 protected RequestCondition<ApiVesrsionCondition> getCustomMethodCondition(Method method) {
 ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
 return createCondition(apiVersion);
 }
 private RequestCondition<ApiVesrsionCondition> createCondition(ApiVersion apiVersion) {
 return apiVersion == null ? null : new ApiVesrsionCondition(apiVersion.value());
 }
 @Override
 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
 RequestMappingInfo info = this.createRequestMappingInfo(method, null);
 if (info != null) {
 ApiVersion apiVersion = (ApiVersion) AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
 RequestMappingInfo typeInfo = this.createRequestMappingInfo(handlerType, apiVersion);
 if (typeInfo != null) {
 info = typeInfo.combine(info);
 }
 }
 return info;
 }
 private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element, ApiVersion apiVersion) {
 RequestMapping requestMapping = (RequestMapping) AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
 RequestCondition<?> condition = element instanceof Class ? this.getCustomTypeCondition((Class)element) : this.getCustomMethodCondition((Method)element);
 if (element instanceof Class && null != apiVersion) {
 try {
 // 動態修改RequestMapping注解的屬性
 InvocationHandler invocationHandler = Proxy.getInvocationHandler(requestMapping);
 Field field = invocationHandler.getClass().getDeclaredField("valueCache");
 // SynthesizedAnnotationInvocationHandler的valueCache是私有變量,需要打開權限
 field.setAccessible(true);
 Map map = (Map) field.get(invocationHandler);
 String\[\] paths = new String\[requestMapping.path().length\];
 for (int i = 0; i< requestMapping.path().length; i++) {
 paths\[i\] = requestMapping.path()\[i\].replace("{version}", "v".concat(apiVersion.value()));
 }
 map.put("path", paths);
 String\[\] values = new String\[requestMapping.value().length\];
 for (int i = 0; i< requestMapping.value().length; i++) {
 values\[i\] = requestMapping.value()\[i\].replace("{version}", "v".concat(apiVersion.value()));
 }
 map.put("value", values);
 // 上面改了value和path是因為注解里@AliasFor,兩者互為,不曉得其它地方有沒有用到,所以都改了,以免其它問題
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 return requestMapping != null ? this.createRequestMappingInfo(requestMapping, condition) : null;
 }
}

感謝各位的閱讀,以上就是“springmvc實現restful api版本控制并兼容swagger的方法”的內容了,經過本文的學習后,相信大家對springmvc實現restful api版本控制并兼容swagger的方法這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

遂昌县| 福清市| 巴东县| 庆安县| 军事| 仲巴县| 临澧县| 包头市| 习水县| 洪泽县| 水城县| 荥经县| 巴楚县| 宁城县| 璧山县| 繁昌县| 项城市| 微博| 阜阳市| 当涂县| 浮山县| 武川县| 贡山| 邛崃市| 三门峡市| 余江县| 肇源县| 常熟市| 凉城县| 渭南市| 莱阳市| 洪洞县| 万年县| 金平| 石狮市| 青河县| 寿宁县| 承德县| 固始县| 当雄县| 赤城县|