微服务--API版本控制

1,314 阅读1分钟

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