[轉]如何優雅的設計 Spring Boot API 接口版本號


原文:https://blog.mariojd.cn/how-to-design-spring-boot-api-version-number-elegantly.html

 

  一般來說,系統上線以后,需求仍會發生變動,功能也會迭代更新。可能是接口參數發生變更,也有可能是業務邏輯需要調整,如果直接在原來的接口上進行修改,必然會影響原有服務的正常運行。

   常見的解決方案,是在接口路徑中加入版本號用於區分,此外還可以在參數甚至 header 里帶上版本號。這里以在請求路徑中帶上版本號為例,如:http://IP:PORT/api/v1/test ,v1 即代表的是版本號。當然了,可以像這樣,直接寫死在 @RequestMapping("api/v1/test") 屬性中,不過下面提供了更為優雅的解決方案。

1. 自定義版本號標記注解:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {

    /**
     * 標識版本號,從1開始
     */
    int value() default 1;

}

 

2. 重寫相應的 RequestCondition

@Data
@Slf4j
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

    /**
     * 接口路徑中的版本號前綴,如: api/v[1-n]/test
     */
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v(\\d+)/");

    private int apiVersion;

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

    /**
     * 最近優先原則,方法定義的 @ApiVersion > 類定義的 @ApiVersion
     */
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        return new ApiVersionCondition(other.getApiVersion());
    }

    /**
     * 獲得符合匹配條件的ApiVersionCondition
     */
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
        if (m.find()) {
            int version = Integer.valueOf(m.group(1));
            if (version >= getApiVersion()) {
                return this;
            }
        }
        return null;
    }

    /**
     * 當出現多個符合匹配條件的ApiVersionCondition,優先匹配版本號較大的
     */
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        return other.getApiVersion() - getApiVersion();
    }

}

 

3. 重寫部分 RequestMappingHandlerMapping 的方法

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        // 掃描類上的 @ApiVersion
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        // 掃描方法上的 @ApiVersion
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }

    private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) {
        if (Objects.isNull(apiVersion)) {
            return null;
        }
        int value = apiVersion.value();
        Assert.isTrue(value >= 1, "Api Version Must be greater than or equal to 1");
        return new ApiVersionCondition(value);
    }

}

 

4. 配置注冊自定義的 CustomRequestMappingHandlerMapping

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new CustomRequestMappingHandlerMapping();
    }

}

 

5. 編寫接口,標記上相應的 @ApiVersion

@Slf4j
@ApiVersion
@RestController
@RequestMapping("api/{version}/test")
public class TestController {

    @GetMapping
    public String test01(@PathVariable String version) {
        return "test01 : " + version;
    }

    @GetMapping
    @ApiVersion(2)
    public String test02(@PathVariable String version) {
        return "test02 : " + version;
    }

}

 

6. 啟動 Application,測試及查看結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM