URL
http://example.com/v1/helloworld
HEADER
各大公司做法
http://www.lexicalscope.com/blog/2012/03/12/how-are-rest-apis-versioned/
Spring Boot實踐API版本管理
原理 在SpringMVC中RequestMappingHandlerMapping是比較重要的一個角色,它決定了每個URL分發至哪個Controller。
Spring Boot載入過程如下,所以我們可以通過自定義WebMvcRegistrationsAdapter來改寫RequestMappingHandlerMapping。
ApiVersion.java
package com.freud.apiversioning.configuration;
import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.Mapping;
@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Mappingpublic @interface ApiVersion {
/** * version * * @return */ int value();}複製程式碼
ApiVersionCondition.java
package com.freud.apiversioning.configuration;
import java.util.regex.Matcher;import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
// extract the version part from url. example [v0-9] private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
private int apiVersion;
public ApiVersionCondition(int apiVersion) { this.apiVersion = apiVersion; }
public ApiVersionCondition combine(ApiVersionCondition other) { // latest defined would be take effect, that means, methods definition with // override the classes definition return new ApiVersionCondition(other.getApiVersion()); }
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI()); if (m.find()) { Integer version = Integer.valueOf(m.group(1)); if (version >= this.apiVersion) // when applying version number bigger than configuration, then it will take // effect return this; } return null; }
public int compareTo(ApiVersionCondition other, HttpServletRequest request) { // when more than one configured version number passed the match rule, then only // the biggest one will take effect. return other.getApiVersion() - this.apiVersion; }
public int getApiVersion() { return apiVersion; }
}複製程式碼
ApiVersioningRequestMappingHandlerMapping.java
package com.freud.apiversioning.configuration;
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;
public class ApiVersioningRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); return createCondition(apiVersion); }
@Override protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class); return createCondition(apiVersion); }
private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) { return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value()); }}複製程式碼
WebMvcRegistrationsConfig.java
package com.freud.apiversioning.configuration;
import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Configurationpublic class WebMvcRegistrationsConfig extends WebMvcRegistrationsAdapter {
@Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiVersioningRequestMappingHandlerMapping(); }
}複製程式碼
測試
TestVersioningController.java
package com.freud.apiversioning.v1.controller;
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
import com.freud.apiversioning.configuration.ApiVersion;
@ApiVersion(1)@RequestMapping("/{api_version}")@RestController("TestVersioningController-v1")public class TestVersioningController {
@RequestMapping("/hello") public String hello() { return "hello v1"; }}複製程式碼
TestVersioningController.java
package com.freud.apiversioning.v2.controller;
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
import com.freud.apiversioning.configuration.ApiVersion;
@ApiVersion(2)@RequestMapping("/{api_version}")@RestController("TestVersioningController-v2")public class TestVersioningController {
@RequestMapping("/hello") public String hello() { return "hello v2"; }}複製程式碼
ApiVersioningApplication.java
package com.freud.apiversioning;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class ApiVersioningApplication {
public static void main(String[] args) { SpringApplication.run(ApiVersioningApplication.class, args); }}複製程式碼
application.yml
server: port: 7905複製程式碼
專案結構
演示
v1 訪問http://localhost:7905/v1/hello
v2 訪問http://localhost:7905/v2/hello
v100 訪問http://localhost:7905/v100/hello
參考資料
Spring Boot API 版本許可權控制: http://blog.csdn.net/u010782227/article/details/74905404
讓SpringMVC支援可版本管理的Restful介面:
http://www.cnblogs.com/jcli/p/springmvcrestfulversion.html
如何做到API相容:
https://kb.cnblogs.com/page/108253/
解析@EnableWebMvc 、WebMvcConfigurationSupport和WebMvcConfigurationAdapter:
http://blog.csdn.net/pinebud55/article/details/53420481
How are REST APIs versioned?:
http://blog.csdn.net/pinebud55/article/details/53420481