让SpringMVC Restful优雅地支持多版本

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

在移动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的版本号:

/**
 * Multi-version Restful API support annotation
 *
 * @author Tony Mu([email protected])
 * @since 2017-07-07
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {

    /**
     * version code
     */
    double value() default 1.0;

}

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

/**
 * Custom HandlerMapping for support multi-value of spring mvc restful api with same url.
 * Version code put into request header.
 * <p>
 *
 * spring mvc config case:
 *
 * @Configuration
 * public class WebConfig extends WebMvcConfigurationSupport {
 *      @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
 *          return new MultiVersionRequestMappingHandlerMapping();
 *      }
 * }
 *
 * controller/action case:
 *
 * @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)
 *      @RestApi(version = 1.1)
 *      public something detailV11(int id) {
 *          return something;
 *      }
 *
 *      @RequestMapping(value = "detail", method = GET)
 *      @RestApi(version = 1.2)
 *      public something detailV12(int id) {
 *          return something;
 *      }
 * }
 *
 * client case:
 *
 * $.ajax({
 *      type: "GET",
 *      url: "http://www.xxx.com/api/product/detail?id=100",
 *      headers: {
 *          value: 1.1
 *      },
 *      success: function(data){
 *          do something
 *      }
 * });
 *
 * @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";

    @Override
    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        ApiVersion apiVersionAnnotation = method.getAnnotation(ApiVersion.class);
        if (apiVersionAnnotation != null) {
            registerRestApiHandlerMethod(handler, method, mapping, apiVersionAnnotation);
            return;
        }
        super.registerHandlerMethod(handler, method, mapping);
    }

    @Override
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        HandlerMethod restApiHandlerMethod = lookupRestApiHandlerMethod(lookupPath, request);
        if (restApiHandlerMethod != null)
            return restApiHandlerMethod;
        return super.lookupHandlerMethod(lookupPath, request);
    }

    private void registerRestApiHandlerMethod(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 lookupRestApiHandlerMethod(String lookupPath, HttpServletRequest request) {
        String version = tryResolveRestApiVersion(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 tryResolveRestApiVersion(HttpServletRequest request) {
        String version = request.getHeader("version");
        if (!StringUtils.hasText(version)) {
            String versionFromUrl = request.getParameter("@value");//for debug
            if (StringUtils.hasText(versionFromUrl)) {
                version = versionFromUrl;
            }
        }
        return version;
    }
}

使用方式参考代码注释。

时间: 2024-07-29 14:17:59

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

WebGL 支持检测与已支持浏览器版本汇总

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 是否我的浏览器支持 WebGL http://caniuse.com 在页面搜索 webgl,找到  WebGL - 3D Canvas grap

SpringMVC RESTful总结之GET请求

SpringMVC RESTful用法灵活,使用方便,介绍几中GET请求方法: 1,使用@PathVariable package com.zws.user.controller.rest; import java.io.UnsupportedEncodingException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.spring

让PDF.NET支持不同版本的SQL Server Compact数据库

最近项目中需要用到嵌入式数据库,我们选用的数据开发框架是PDF.NET(http://www.pwmis.com/SqlMap/),之前的博文已经总结了让PDF.NET支持最新的SQLite,今天我们来总结一下如何让PDF.NET支持不同版本的SQL Server Compact数据库.PDF.NET支持大部分主流的数据库,SQL Server Compact也不例外,但是PDF.NET只支持SQL Server Compact 4.0,而SQL Server Compact又没有做到向下兼容,

【Oracle】ORACLE SQL Developer不支持JAVA版本

ORACLE SQL Developer不支持JAVA版本 今天我打开 ORACLE SQL Developer准备开始练手.没有想到却给出了错误提示. 我 是安装了java JDK的而且是1.6版本的.我可能选择了bin目录下面的java.exe文件,原本觉得这样的操作应该是没有问题的结果还是出现了错误.具体错误信息如 下:不支持的java版本,不支持java 6.0发行版本1.6.0_xxx,请升级到java 6.0发行版本1.6.0_04.或者降级到java 5.0发行版本1.5.0或更高

js导出excel ()基于 OpenXML 支持 2007版本以上 浏览器 data 协议) 无须ie插件支持.

方法一(推荐): 基于微软OpenXML协议,支持excel2007版本以上. 基于浏览器 data 协议 , 完全不需要依赖ie 插件.不需要客户端是否安装excel. 欢迎点评,共同进步 ! 1 <html> 2 <head> 3 <meta http-equiv="content-Type" content="text/html;charset=utf-8"/> 4 <script type="text/ja

SQL Server附加数据库提示“版本为661,无法打开,支持655版本……”

在我们使用别人导出的数据库的时候,有时候我们会通过附加数据库的方法,把别人导出的数据库附加到我们的电脑中,这时,或许你会遇到这种问题,附加时,提示版本为XXX,无法打开,支持AAA版本. 这是怎么回事呢? 原来,版本号是指SQL Server的版本号,例如版本号661是SQL Server 2008 R2,版本号655是SQL Server 2008 等.它拥有向上兼容的特点.由此可见,标题的意思就是说,你要附加的数据库,只能在SQL Server 2008 R2及更高版本上运行,不能在SQL

Servlet与JSP版本历史以及Tomcat支持的版本

查询这个的关键字:Java EE的版本历史. JavaServer Pages (JSP) Java Servlet 参考: https://en.wikipedia.org/wiki/Java_EE_version_history https://zh.wikipedia.org/wiki/Java_Servlet 从维基百科中可以快速的查看Java EE的版本,然后再从版本对Servlet与JSP的支持上可以分析出两者的关系,就Java EE 7来说: 以下为tomcat支持的版本: 参考:

Xcode5.1.1支持低版本和image not found和Couldn&#39;t register XXXX with the bootstrap server. Error: unknown error code.

一:问题  targets中证书的设置 1.项目支持多设备(Xcode5.1.1支持低版本) 2.真机测试要确保Code Siging 设置没问题 支持的最低版本 二 :问题:image  not found Library not loaded: /System/Library/Frameworks/AdSupport.framework/AdSupport   Referenced from: /var/mobile/Applications/0083F6DD-6466-48B4-8F6D-

PHP不支持高版本的openssl

现有一台LNMP测试环境机器,Centos 6.9 先前升级过openssl版本由1.0.1e升级到1.1.0f, 因工作需求,升级php版本,升级到5.6.30,编译php中指定--with-openssl报错,搜了下资料,php不支持高版本openssl ../../mysys_ssl/libmysys_ssl.a(my_crypt.cc.o):/root/lnmp1.4/src/mariadb-10.2.5/mysys_ssl/my_crypt.cc:41: more undefined