springmvc实现restfulapi版本控制并兼容swagger的方法

这篇文章主要讲解了“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
 \* Function: url版本注解
 \* 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  \* 
 \* Date: 2017-10-31 14:33
 *  \* @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
 \* Function: API版本条件筛选器
 \* Reason: TODO ADD REASON(可选)
 \* Date: 2017-10-31 14:35
 *  \* @author zoro  */ public class ApiVesrsionCondition implements RequestCondition {  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的格式.
 \* Date: 2017/11/10.
 \* @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
 \* Function: TODO ADD FUNCTION(可选)
 \* Reason: TODO ADD REASON(可选)
 \* Date: 2017-10-31 15:13
 *  \* @author zoro  */ public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {  @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);  }  private RequestCondition 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
 \* Function: TODO ADD FUNCTION(可选)
 \* Reason: TODO ADD REASON(可选)
 \* Date: 2017-10-31 15:15
 *  \* @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
 \* Function: TODO ADD FUNCTION(可选)
 \* Reason: TODO ADD REASON(可选)
 \* Date: 2017-10-31 15:15
 *  \* @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
 \* Function: TODO ADD FUNCTION(可选)
 \* Reason: TODO ADD REASON(可选)
 \* Date: 2017-10-31 15:13
 *  \* @author zoro  */ public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {  @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);  }  private RequestCondition 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的方法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!


分享名称:springmvc实现restfulapi版本控制并兼容swagger的方法
网站链接:http://hbruida.cn/article/gpcjgh.html