很久沒有更新博客,可貴有空,記錄一下今天寫的一個小工具,供有須要的朋友參考。
在移動APP開發中,多版本接口同時存在的狀況常常發生,一般接口支持多版本,有如下兩種方式:ajax
1.經過不一樣路徑區分不一樣版本spring
如:api
http://www.xxx.com/api/v1/product/detail?id=100 (版本1)
http://www.xxx.com/api/v2/product/detail?id=100 (版本2)restful
這種狀況,能夠經過創建多個文件的方式實現,優勢是結構清晰、實現簡單,缺點是大量重複工做致使實現不優雅。mvc
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提供】
app
這種方式相對靈活且優雅,這篇文章主要討論這種方式,直接上代碼!
首先定義一個註解,用於在控制器的方法中標記API的版本號:ide
/** * 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; } }
使用方式參考代碼註釋。url
如下是用到的相關代碼:
spa
/** * 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; } }