微服务API版本控制
API版本控制常用实践URLhttp://example.com/v1/helloworldHEADER各大公司做法http://www.lexicalscope.com/blog/2012/03/12/how-are-rest-apis-versioned/Spring Boot实践API版本管理原理在SpringMVC中RequestMappingHandlerMapping是比较重要的一个角
API版本控制常用实践
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
@Mapping
public @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;
@Configuration
public 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;
@SpringBootApplication
public 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/springmvc_restful_version.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
更多推荐
所有评论(0)