SpringBoot 2.x API 多版本 控制

之前有文章講述在Spring MVC 中擴展 RequestMappingHandlerMapping 實現(xiàn)對版本的控制。
但是在真正使用過程中不是很理想化,因為其需要替換掉WebMvcConfigurationSupport,替換后后,會將其提供的一系列默認組件全部移除。如我們注冊攔截器使用的(RequestMappingHandlerAdapter)、全局異常攔截(ExceptionHandlerExceptionResolver)等。
本文以Spring Boot 2.x為例,解決這個問題。

配置WebMvcRegistrationsConfig

@Configuration
public class WebMvcRegistrationsConfig  implements WebMvcRegistrations {
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiRequestMappingHandlerMapping();
    }
}

創(chuàng)建ApiRequestMappingHandlerMapping

public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    private static final String VERSION_FLAG = "{version}";

    private static RequestCondition<ApiVersionCondition> createCondition(Class<?> clazz) {
        RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
        if (classRequestMapping == null) {
            return null;
        }
        StringBuilder mappingUrlBuilder = new StringBuilder();
        if (classRequestMapping.value().length > 0) {
            mappingUrlBuilder.append(classRequestMapping.value()[0]);
        }
        String mappingUrl = mappingUrlBuilder.toString();
        if (!mappingUrl.contains(VERSION_FLAG)) {
            return null;
        }
        ApiVersion apiVersion = clazz.getAnnotation(ApiVersion.class);
        return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(apiVersion.value());
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        return createCondition(method.getClass());
    }

    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        return createCondition(handlerType);
    }
}

@ApiVersion自定義注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    /**
     * @return 版本號
     */
    int value() default 1;
}

ApiVersionCondition請求映射條件

RequestCondition相當針對請求與映射之間創(chuàng)建了判斷條件。以此判斷某個請求應(yīng)落在哪個controller上。

public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+).*");

    private int apiVersion;

    ApiVersionCondition(int apiVersion) {
        this.apiVersion = apiVersion;
    }

    private int getApiVersion() {
        return apiVersion;
    }


    @Override
    public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
        return new ApiVersionCondition(apiVersionCondition.getApiVersion());
    }

    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
        if (m.find()) {
            Integer version = Integer.valueOf(m.group(1));
            if (version >= this.apiVersion) {
                return this;
            }
        }
        return null;
    }

    @Override
    public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
        return apiVersionCondition.getApiVersion() - this.apiVersion;
    }
}

使用

V1Controller

@RequestMapping("/{version}/version")
@RestController
public class V1Controller {
    @GetMapping
    public String test() {
        return "version1";
    }
    @GetMapping("/extend")
    public String extendTest() {
        return "extend";
    }
}

V2Controller

@RequestMapping("/{version}/version")
@RestController
@ApiVersion(2)
public class V2Controller {
   @GetMapping
    public String test() {
        return "version2";
    }
}

版本測試

當請求http://localhost:8080/v1/version時會返回version1
當請求http://localhost:8080/v2/version時返回version2
實現(xiàn)了版本控制效果

版本繼承

當請求http://localhost:8080/v2/version/extend時,會返回V1Controller中的extend。
實現(xiàn)版本繼承效果。

版本分包

當引入版本控制后,為了不讓版本號污染我們的類名。
我們一般不希望命名出現(xiàn)類似V1、V2的字眼。使用包的形式進行區(qū)分會更直觀一些。如com.test.v1.UserController、com.test.v2.UserController。
但此時spring以及擴展spring的一些組件(如mybatis)會拋出bean名稱重復(fù)的錯誤。
此時我們需要覆蓋或手動指定spring的默認名稱生成器AnnotationBeanNameGenerator,該類默認使用class的名稱而非全類名作為bean的名稱,故會出現(xiàn)名稱沖突。拋出ConflictingBeanDefinitionException。

Bean名稱重復(fù)ConflictingBeanDefinitionException沖突解決

Bean名稱重復(fù)ConflictingBeanDefinitionException沖突解決

Spring Boot 1.x

Spring Boot 1.x中由于使用jdk1.8以下版本,沒有default方法,故無法直接實現(xiàn)WebMvcRegistrations,其提供了WebMvcRegistrationsAdapter供我們繼承。

    @Configuration
    public class WebMvcRegistrationsConfig extends WebMvcRegistrationsAdapter {
        @Override
        public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
            return new ApiRequestMappingHandlerMapping();
        }
    }

快速開發(fā)框架
高質(zhì)量圖片壓縮工具

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容