Spring Boot入門系列(二十一)如何優雅的設計 Restful API 接口版本號,實現 API 版本控制!


前面介紹了Spring Boot 如何快速實現Restful api 接口,並以人員信息為例,設計了一套操作人員信息的接口。不清楚的可以看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1657780.html

有些人可能會問,為什么我看到很多公司的api接口文檔里面,都有/api/v1/ 這樣的地址呢?其實,/api 就是為了和一般的業務地址區分,標明這個地址是api 的接口。v1 則代表版本號。

可能很多人又會問了,為什么要版本號呢?那么,接下來就聊一聊Restful 接口為什么要加版本號? 如何優雅的設計 Restful API 接口版本號?

 

一、為什么加版本號

一般來說,api 接口是提供給其他系統或是其他公司使用,不能隨意頻繁的變更。然而,需求和業務不斷變化,接口和參數也會發生相應的變化。如果直接對原來的接口進行修改,勢必會影響線其他系統的正常運行。這就必須對api 接口進行有效的版本控制。

例如,添加用戶的接口,由於業務需求變化,接口的字段屬性也發生了變化而且可能和之前的功能不兼容。為了保證原有的接口調用方不受影響,只能重新定義一個新的接口。

 

Api 版本控制的方式:

  1、域名區分管理,即不同的版本使用不同的域名,v1.api.test.com,v2.api.test.com

  2、請求url 路徑區分,在同一個域名下使用不同的url路徑,test.com/api/v1/,test.com/api/v2

  3、請求參數區分,在同一url路徑下,增加version=v1或v2 等,然后根據不同的版本,選擇執行不同的方法。

實際項目中,一般選擇第二種:請求url路徑區分。因為第二種既能保證水平擴展,有不影響以前的老版本。

 

二、Spring Boot如何實現

實現方案:

1、首先創建自定義的@APIVersion 注解和自定義URL匹配規則ApiVersionCondition。

2、然后創建自定義的 RequestMappingHandlerMapping 匹配對應的request,選擇符合條件的method handler。

 

1、創建自定義注解

首先,在com.weiz.config 包下,創建一個自定義版本號標記注解 @ApiVersion。

package com.weiz.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

說明:

 ApiVersion 為自定義的注解,API版本控制,返回對應的版本號。

 

2、自定義url匹配邏輯

創建 ApiVersionCondition 類,並繼承RequestCondition 接口,作用是:版本號篩選,將提取請求URL中版本號,與注解上定義的版本號進行比對,以此來判斷某個請求應落在哪個controller上。

在com.weiz.config 包下創建ApiVersionCondition 類,重寫 RequestCondition,創建自定義的url匹配邏輯。

package com.weiz.config;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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;
    }
}

當方法級別和類級別都有ApiVersion注解時,二者將進行合並(ApiVersionRequestCondition.combine)。最終將提取請求URL中版本號,與注解上定義的版本號進行比對,判斷url是否符合版本要求。

 

3、自定義匹配的處理器

在com.weiz.config 包下創建 ApiRequestMappingHandlerMapping 類,重寫部分 RequestMappingHandlerMapping 的方法。

package com.weiz.config;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

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);
    }
}

 

4、配置注冊自定義的RequestMappingHandlerMapping

重寫請求過處理的方法,將之前創建的 ApiRequestMappingHandlerMapping 注冊到系統中。

package com.weiz.config;

import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

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

上面四步,把api 版本控制配置完了。代碼看着復雜,其實都是重寫spring boot 內部的處理流程。

 

測試

配置完成之后,接下來編寫測試的控制器進行測試。

1、在Controller/api 目錄下,分別創建UserV1Controller 和 UserV2Controller

UserV1Controller

@RequestMapping("api/{version}/user")
@RestController
public class UserV1Controller {

    @GetMapping("/test")
    public String test() {
        return "version1";
    }
    @GetMapping("/extend")
    public String extendTest() {
        return "user v1 extend";
    }
}

 

UserV2Controller

@RequestMapping("api/{version}/user")
@RestController
@ApiVersion(2)
public class UserV2Controller {
    @GetMapping("/test")
    public String test() {
        return "user v2 test";
    }
}

 

2、啟動項目后,輸入相關地址,查看版本控制是否生效

測試結果:

正確的接口地址

  

 

繼承的接口地址

說明:

  上圖的前兩個截圖說明,請求正確的版本地址,會自動匹配版本的對應接口。當請求的版本大於當前版本時,默認匹配當前版本。

  第三個截圖說明,當請求對應的版本不存在接口時,會匹配之前版本的接口,即請求/v2/user/extend 接口時,由於v2 控制器未實現該接口,所以自動匹配v1 版本中的接口。這就是所謂的版本繼承。

最后

以上,就把Spring Boot 如何優雅的設計 Restful API 接口版本號,實現 API 版本控制介紹完了。版本控制和權限驗證是rest api 的基礎,雖然看着比較復雜,但是理解了,要實現還是比較簡單的。

這個系列課程的完整源碼,也會提供給大家。大家關注我的微信公眾號(架構師精進),回復:springboot源碼。獲取這個系列課程的完整源碼。

 


免責聲明!

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



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