好久没有更新博客,难得有空,记录一下今天写的一个小工具,供有需要的朋友参考。

在移动APP开发中,多版本接口同时存在的情况经常发生,通常接口支持多版本,有以下两种方式:

1.通过不同路径区分不同版本

如:

http://www.xxx.com/api/v1/product/detail?id=100 (版本1)
http://www.xxx.com/api/v2/product/detail?id=100 (版本2)

这种情况,可以通过建立多个文件的方式实现,优点是结构清晰、实现简单,缺点是大量重复工作导致实现不优雅。

2.通过不同调用参数区分不同版本

如:
http://www.xxx.com/api/v1/product/detail?id=100&@version=1(版本1)
http://www.xxx.com/api/v1/product/detail?id=100&@version=2(版本2)

【version还可以通过http请求头的header提供】

这种方式相对灵活且优雅,这篇文章主要讨论这种方式,直接上代码!

首先定义一个注解,用于在控制器的方法中标记API的版本号:

/*** Annotation for support Multi-version Restful API** @author Tony Mu(tonymu@qq.com)* @since 2017-07-07*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {/*** api version code*/double value() default 1.0;}

然后扩展SpringMVC的RequestMappingHandlerMapping,以便于根据不同的版本号,调用不同的实现逻辑:

/*** Custom RequestMappingHandlerMapping for support multi-version of spring mvc restful api with same url.* Version code provide by {@code ApiVersionCodeDiscoverer}.* <p>** How to use ?** Spring mvc config case:** <pre class="code">* @Configuration* public class WebConfig extends WebMvcConfigurationSupport {*      @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {*          MultiVersionRequestMappingHandlerMapping requestMappingHandlerMapping = new MultiVersionRequestMappingHandlerMapping();*          requestMappingHandlerMapping.registerApiVersionCodeDiscoverer(new DefaultApiVersionCodeDiscoverer());*          return requestMappingHandlerMapping;*      }* }</pre>** Controller/action case:** <pre class="code">* @RestController* @RequestMapping(value = "/api/product")* public class ProductController {**      @RequestMapping(value = "detail", method = GET)*      public something detailDefault(int id) {*          return something;*      }**      @RequestMapping(value = "detail", method = GET)*      @ApiVersion(value = 1.1)*      public something detailV11(int id) {*          return something;*      }**      @RequestMapping(value = "detail", method = GET)*      @ApiVersion(value = 1.2)*      public something detailV12(int id) {*          return something;*      }* }</pre>** Client case:** <pre class="code">* $.ajax({*      type: "GET",*      url: "http://www.xxx.com/api/product/detail?id=100",*      headers: {*          value: 1.1*      },*      success: function(data){*          do something*      }* });</pre>** @since 2017-07-07*/
public class MultiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {private static final Logger logger = LoggerFactory.getLogger(MultiVersionRequestMappingHandlerMapping.class);private final static Map<String, HandlerMethod> HANDLER_METHOD_MAP = new HashMap<>();/*** key pattern,such as:/api/product/detail[GET]@1.1*/private final static String HANDLER_METHOD_KEY_PATTERN = "%s[%s]@%s";private List<ApiVersionCodeDiscoverer> apiVersionCodeDiscoverers = new ArrayList<>();@Overrideprotected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {ApiVersion apiVersionAnnotation = method.getAnnotation(ApiVersion.class);if (apiVersionAnnotation != null) {registerMultiVersionApiHandlerMethod(handler, method, mapping, apiVersionAnnotation);return;}super.registerHandlerMethod(handler, method, mapping);}@Overrideprotected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {HandlerMethod restApiHandlerMethod = lookupMultiVersionApiHandlerMethod(lookupPath, request);if (restApiHandlerMethod != null)return restApiHandlerMethod;return super.lookupHandlerMethod(lookupPath, request);}public void registerApiVersionCodeDiscoverer(ApiVersionCodeDiscoverer apiVersionCodeDiscoverer){if(!apiVersionCodeDiscoverers.contains(apiVersionCodeDiscoverer)){apiVersionCodeDiscoverers.add(apiVersionCodeDiscoverer);}}private void registerMultiVersionApiHandlerMethod(Object handler, Method method, RequestMappingInfo mapping, ApiVersion apiVersionAnnotation) {PatternsRequestCondition patternsCondition = mapping.getPatternsCondition();RequestMethodsRequestCondition methodsCondition = mapping.getMethodsCondition();if (patternsCondition == null|| methodsCondition == null|| patternsCondition.getPatterns().size() == 0|| methodsCondition.getMethods().size() == 0) {return;}Iterator<String> patternIterator = patternsCondition.getPatterns().iterator();Iterator<RequestMethod> methodIterator = methodsCondition.getMethods().iterator();while (patternIterator.hasNext() && methodIterator.hasNext()) {String patternItem = patternIterator.next();RequestMethod methodItem = methodIterator.next();String key = String.format(HANDLER_METHOD_KEY_PATTERN, patternItem, methodItem.name(), apiVersionAnnotation.value());HandlerMethod handlerMethod = super.createHandlerMethod(handler, method);if (!HANDLER_METHOD_MAP.containsKey(key)) {HANDLER_METHOD_MAP.put(key, handlerMethod);if (logger.isDebugEnabled()) {logger.debug("register ApiVersion HandlerMethod of %s %s", key, handlerMethod);}}}}private HandlerMethod lookupMultiVersionApiHandlerMethod(String lookupPath, HttpServletRequest request) {String version = tryResolveApiVersion(request);if (StringUtils.hasText(version)) {String key = String.format(HANDLER_METHOD_KEY_PATTERN, lookupPath, request.getMethod(), version);HandlerMethod handlerMethod = HANDLER_METHOD_MAP.get(key);if (handlerMethod != null) {if (logger.isDebugEnabled()) {logger.debug("lookup ApiVersion HandlerMethod of %s %s", key, handlerMethod);}return handlerMethod;}logger.debug("lookup ApiVersion HandlerMethod of %s failed", key);}return null;}private String tryResolveApiVersion(HttpServletRequest request) {for (int i = 0; i < apiVersionCodeDiscoverers.size(); i++) {ApiVersionCodeDiscoverer apiVersionCodeDiscoverer = apiVersionCodeDiscoverers.get(i);String versionCode = apiVersionCodeDiscoverer.getVersionCode(request);if(StringUtils.hasText(versionCode))return versionCode;}return null;}
}

使用方式参考代码注释。

以下是用到的相关代码:

/*** Interface to discover api version code in http request.** @author Tony Mu(tonymu@qq.com)* @since 2017-07-11*/
public interface ApiVersionCodeDiscoverer {/*** Return an api version code that can indicate the version of current api.** @param request current HTTP request* @return an api version code that can indicate the version of current api or {@code null}.*/String getVersionCode(HttpServletRequest request);}

/*** Default implementation of the {@link ApiVersionCodeDiscoverer} interface, get api version code* named "version" in headers or named "@version" in parameters.** @author Tony Mu(tonymu@qq.com)* @since 2017-07-11*/
public class DefaultApiVersionCodeDiscoverer implements ApiVersionCodeDiscoverer {/*** Get api version code named "version" in headers or named "@version" in parameters.** @param request current HTTP request* @return api version code named "version" in headers or named "@version" in parameters.*/@Overridepublic String getVersionCode(HttpServletRequest request) {String version = request.getHeader("version");if (!StringUtils.hasText(version)) {String versionFromUrl = request.getParameter("@version");//for debugif (StringUtils.hasText(versionFromUrl)) {version = versionFromUrl;}}return version;}
}

转载于:https://www.cnblogs.com/tonymu/p/7147172.html

让SpringMVC Restful API优雅地支持多版本相关推荐

  1. SpringMVC Restful api接口实现

    [前言] 面向资源的 Restful 风格的 api 接口本着简洁,资源,便于扩展,便于理解等等各项优势,在如今的系统服务中越来越受欢迎. .net平台有WebAPi项目是专门用来实现Restful ...

  2. 人人都是 API 设计者:我对 RESTful API、GraphQL、RPC API 的思考

    有一段时间没怎么写文章了,今天提笔写一篇自己对 API 设计的思考.首先,为什么写这个话题呢?其一,我阅读了<阿里研究员谷朴:API 设计最佳实践的思考>一文后受益良多,前两天并转载了这篇 ...

  3. SpringBoot第十一篇:springboot集成swagger2,构建优雅的Restful API

    swagger,中文"拽"的意思.它是一个功能强大的api框架,它的集成非常简单,不仅提供了在线文档的查阅,而且还提供了在线文档的测试.另外swagger很容易构建restful风 ...

  4. 最好用的koa2+mysql的RESTful API脚手架,mvc架构,支持node调试,pm2部署。

    #基于webpack构建的 Koa2 restful API 服务器脚手架 这是一个基于 Koa2 的轻量级 RESTful API Server 脚手架,支持 ES6, 支持使用TypeScript ...

  5. springboot集成swagger2,构建优雅的Restful API

    springboot集成swagger2,构建优雅的Restful API 转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springboot/2017/07/ ...

  6. post如何获取到referrer_如何使用 ThinkJS 优雅的编写 RESTful API

    RESTful 是目前比较主流的一种用来设计和编排服务端 API 的一种规范.在 RESTful API 中,所有的接口操作都被认为是对资源的 CRUD,使用 URI 来表示操作的资源,请求方法表示具 ...

  7. springMvc接口开发--对访问的restful api接口进行拦截实现功能扩展

    1.视频参加Spring Security开发安全的REST服务\PART1\PART1 3-7 使用切片拦截REST服务三通it学院-www.santongit.com-.mp4 讲的比较的经典,后 ...

  8. Express框架Restful API Ajax 跨域 开启Cookie支持

    前端(Jquery Ajax): 1 $.ajax({ 2 url: "http://localhost/api/test/", 3 type: "POST", ...

  9. RESTful API 设计最佳实践

    2019独角兽企业重金招聘Python工程师标准>>> 背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中"RESTful API "简写为 ...

最新文章

  1. c++创建包含opencv的dll供C,C#调用
  2. 广义互相关的公式,这一文都搜集全了
  3. OpenWrt DNS问题排查
  4. windows和centos进行文件上传和下载
  5. 使用协同过滤推荐电影
  6. java二维数组的常见初始化
  7. 我的世界服务器的文件名叫什么,我的世界 外国服务器叫什么名字 | 手游网游页游攻略大全...
  8. Django登录验证——原生表单验证
  9. AndroidStudio安卓原生开发_利用Activity的Intent 以及Bundle在activity之间传递数据---Android原生开发工作笔记91
  10. 【宽度优先搜索】计蒜客:蒜头君回家(带条件的BFS)
  11. 【 Codeforces Round #395 (Div. 2) E】Timofey and remoduling【数学思维题 —— 等差/等比数列】
  12. java控制台通讯录
  13. 算法与数据结构——有序表(Java)(b站左程云课程笔记总结)
  14. 导致计算机重启的原因,电脑自动重启的原因分析
  15. 环洋市场咨询:全球EMS和ODM收入预计2028年达到7978.5亿美元
  16. 隐藏和isa :进化返祖以及白马非马(c++)
  17. 【转】欧盟物联网研究战略路线图(1)
  18. 深入理解android 包体积优化,给apk瘦身全部技巧
  19. oracle中的单表查询,单字段与多字段排序
  20. 冒泡排序java实现和分析

热门文章

  1. Eclipse 的一些调试技巧
  2. mysql表级锁和行级锁
  3. 网编编程必看书籍:unix网络编程
  4. muduo之Logger
  5. MySQL笔记3:深入理解MySQL中的NULL
  6. #ifdef __cplusplus extern “C” { #endif的作用!
  7. LED和LCD的区别
  8. 常考数据结构与算法:在二叉树中找到两个节点的最近公共祖先
  9. springmvc二十四:自定义国际化信息
  10. python四十九:封装