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

在移动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<>(); @Override
protected 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);
} @Override
protected 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.
*/
@Override
public String getVersionCode(HttpServletRequest request) {
String version = request.getHeader("version");
if (!StringUtils.hasText(version)) {
String versionFromUrl = request.getParameter("@version");//for debug
if (StringUtils.hasText(versionFromUrl)) {
version = versionFromUrl;
}
}
return version;
}
}

让SpringMVC Restful API优雅地支持多版本的更多相关文章

  1. SpringMVC Restful api接口实现

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

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

    原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. 有一段时间没怎么写文章了,今天提笔写一 ...

  3. Yii2 Restful API 原理分析

    Yii2 有个很重要的特性是对 Restful API的默认支持, 通过短短的几个配置就可以实现简单的对现有Model的RESTful API 参考另一篇文章: http://www.cnblogs. ...

  4. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(一)设计一套好的RESTful API

    写在前面的话 看了一下博客目录,距离上次更新这个系列的博文已经有两个多月,并不是因为不想继续写博客,由于中间这段时间更新了几篇其他系列的文章就暂时停止了,如今已经讲述的差不多,也就继续抽时间更新< ...

  5. SwaggerUI+SpringMVC——构建RestFul API的可视化界面

    今天给大家介绍一款工具,这个工具眼下可预见的优点是:自己主动维护最新的接口文档. 我们都知道,接口文档是非常重要的,可是随着代码的不断更新,文档却非常难持续跟着更新,今天要介绍的工具,完美的攻克了这个 ...

  6. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(二)RESTful API实战笔记(接口设计及Java后端实现)

    写在前面的话 原计划这部分代码的更新也是上传到ssm-demo仓库中,因为如下原因并没有这么做: 有些使用了该项目的朋友建议重新创建一个仓库,因为原来仓库中的项目太多,结构多少有些乱糟糟的. 而且这次 ...

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

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

  8. (转)第十一篇:springboot集成swagger2,构建优雅的Restful API

    声明:本部分内容均转自于方志明博友的博客,因为本人很喜欢他的博客,所以一直在学习,转载仅是记录和分享,若也有喜欢的人的话,可以去他的博客首页看:http://blog.csdn.net/forezp/ ...

  9. flask, SQLAlchemy, sqlite3 实现 RESTful API 的 todo list, 同时支持form操作

    flask, SQLAlchemy, sqlite3 实现 RESTful API, 同时支持form操作. 前端与后台的交互都采用json数据格式,原生javascript实现的ajax.其技术要点 ...

随机推荐

  1. 用 Deployment 运行应用 - 每天5分钟玩转 Docker 容器技术(123)

    从本章开始,我们将通过实践深入学习 Kubernetes 的各种特性.作为容器编排引擎,最重要也是最基本的功能当然是运行容器化应用,这就是本章的内容. Deployment 前面我们已经了解到,Kub ...

  2. 电铸3D18K硬金 电铸易熔合金 电铸中空硬金饰品合金

        俊霖电铸3DK金易熔合金是要求相互关连,互为条件,缺一不可,是产品完整性和完美性的重要体现.    第一.适用性:电铸3DK金易熔合金的性能应适用于电铸.首饰.K金饰品.摆件等工艺品的易熔合金 ...

  3. [国嵌攻略][149][Yaffs2文件系统应用]

    嵌入式系统自启动 MTD技术通过把Nand FLash划分成bootloader分区,Linux kernel分区和file system分区来达到自启动的效果. 配置和编译内核 1.配置Linux内 ...

  4. 《你不知道的JavaScript上卷》知识点笔记

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px "PingFang SC" } p.p2 { margin: 0.0px ...

  5. docker运行dubbo-admin

    一:简介 dubbo-admin是dubbo框架的管理平台. 二: 创建继续镜像 Dockerfile FROM fangjipu/jdk8:8 RUN yum -y install epel-rel ...

  6. 本地apache 可以正常访问,lnmp服务器访问404错误

    if (!-e $request_filename) { rewrite  ^(.*)$  /index.php?s=/$1  last; break; }

  7. AVFrame转换到Mat,yuv420p转换到RGB源代码

    FFmpeg中AVFrame到OpenCV中Mat的两种转换方法 方法一:查表法 void AVFrame2Img(AVFrame *pFrame, cv::Mat& img) { int f ...

  8. winform webbrowser如何强制使用ie11内核?

    webkit.net ,cefsharp,openwebkit.net等这些基于谷歌或者基于firfox内核的浏览器有个共同点,就是必须指定winform为x86的才能使用, 而且使用过程中也是各种坑 ...

  9. python3 第十七章 - sequence(序列)

    之前我们在讲for循环语句时就提到过序列,那么什么是序列(sequence)? 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 —— 它的索引(位置),第一个索引是0,第二个索引 ...

  10. python_如何使用临时文件

    案例: 某项目中,从传感器中获得采集数据,每收集到1G的数据后做是数据分析,最终只保留数据分析的结果,收集到的数据放在内存中,将会消耗大量内存,我们希望把这些数据放到一个临时的文件中 临时文件不能命名 ...