API版本控制是確保系統平穩演進的關鍵策略。當API發生變化時,合理的版本控制機制能讓舊版客戶端繼續正常工作,同時允許新版客戶端使用新功能。
這是最直觀、應用最廣泛的版本控制方式,通過在URL路徑中直接包含版本號。
實現方式
@RestController @RequestMapping("/api/v1/users") public class UserControllerV1 { @GetMapping("/{id}") public UserV1DTO getUser(@PathVariable Long id) { // 返回v1版本的用戶信息 return userService.getUserV1(id); } } @RestController @RequestMapping("/api/v2/users") public class UserControllerV2 { @GetMapping("/{id}") public UserV2DTO getUser(@PathVariable Long id) { // 返回v2版本的用戶信息,可能包含更多字段 return userService.getUserV2(id); } }
優缺點
優點
缺點
通過在請求參數中指定版本號,保持URL路徑不變。
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public Object getUser(@PathVariable Long id, @RequestParam(defaultValue = "1") int version) { switch (version) { case 1: return userService.getUserV1(id); case 2: return userService.getUserV2(id); default: throw new IllegalArgumentException("Unsupported API version: " + version); } } }
或者使用SpringMVC的條件映射:
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping(value = "/{id}", params = "version=1") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", params = "version=2") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
通過自定義HTTP頭來指定API版本,這是一種更符合RESTful理念的方式。
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping(value = "/{id}", headers = "X-API-Version=1") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", headers = "X-API-Version=2") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
使用HTTP協議的內容協商機制,通過Accept頭指定媒體類型及其版本。
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping(value = "/{id}", produces = "application/vnd.company.app-v1+json") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", produces = "application/vnd.company.app-v2+json") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
客戶端請求時需要設置Accept頭:
Accept: application/vnd.company.app-v2+json
通過自定義注解和攔截器/過濾器實現更靈活的版本控制。
首先定義版本注解:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiVersion { int value() default 1; }
創建版本匹配的請求映射處理器:
@Component public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition getCustomTypeCondition(Class handlerType) { ApiVersion apiVersion = handlerType.getAnnotation(ApiVersion.class); return createCondition(apiVersion); } @Override protected RequestCondition getCustomMethodCondition(Method method) { ApiVersion apiVersion = method.getAnnotation(ApiVersion.class); return createCondition(apiVersion); } private ApiVersionCondition createCondition(ApiVersion apiVersion) { return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(apiVersion.value()); } } public class ApiVersionCondition implements RequestCondition { private final int apiVersion; public ApiVersionCondition(int apiVersion) { this.apiVersion = apiVersion; } @Override public ApiVersionCondition combine(ApiVersionCondition other) { // 采用最高版本 return new ApiVersionCondition(Math.max(this.apiVersion, other.apiVersion)); } @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { String version = request.getHeader("X-API-Version"); if (version == null) { version = request.getParameter("version"); } int requestedVersion = version == null ? 1 : Integer.parseInt(version); return requestedVersion >= apiVersion ? this : null; } @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { // 優先匹配高版本 return other.apiVersion - this.apiVersion; } }
配置WebMvc使用自定義的映射處理器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { return new ApiVersionRequestMappingHandlerMapping(); } }
使用自定義注解:
@RestController @RequestMapping("/api/users") public class UserController { @ApiVersion(1) @GetMapping("/{id}") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @ApiVersion(2) @GetMapping("/{id}") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
通過接口繼承和策略模式實現版本控制,核心思想是提供相同接口的不同版本實現類。
首先定義API接口:
public interface UserApi { Object getUser(Long id); } @Service @Primary public class UserApiV2Impl implements UserApi { // 最新版本實現 @Override public UserV2DTO getUser(Long id) { // 返回V2版本數據 return new UserV2DTO(); } } @Service @Qualifier("v1") public class UserApiV1Impl implements UserApi { // 舊版本實現 @Override public UserV1DTO getUser(Long id) { // 返回V1版本數據 return new UserV1DTO(); } }
控制器層根據版本動態選擇實現:
@RestController @RequestMapping("/api/users") public class UserController { private final Map apiVersions; // 通過構造注入收集所有實現 public UserController(List apis) { // 簡化示例,實際應通過某種方式標記每個實現的版本 this.apiVersions = Map.of( 1, apis.stream().filter(api -> api instanceof UserApiV1Impl).findFirst().orElseThrow(), 2, apis.stream().filter(api -> api instanceof UserApiV2Impl).findFirst().orElseThrow() ); } @GetMapping("/{id}") public Object getUser(@PathVariable Long id, @RequestParam(defaultValue = "2") int version) { UserApi api = apiVersions.getOrDefault(version, apiVersions.get(2)); // 默認使用最新版本 return api.getUser(id); } }
可以自己實現一個版本委托器來簡化版本選擇:
// 自定義API版本委托器 public class ApiVersionDelegator { private final Class apiInterface; private final Map versionedImpls = new HashMap(); private final Function versionExtractor; private final String defaultVersion; public ApiVersionDelegator(Class apiInterface, Function versionExtractor, String defaultVersion, ApplicationContext context) { this.apiInterface = apiInterface; this.versionExtractor = versionExtractor; this.defaultVersion = defaultVersion; // 從Spring上下文中查找所有實現了該接口的bean Map impls = context.getBeansOfType(apiInterface); for (Map.Entry entry : impls.entrySet()) { ApiVersion apiVersion = entry.getValue().getClass().getAnnotation(ApiVersion.class); if (apiVersion != null) { versionedImpls.put(String.valueOf(apiVersion.value()), entry.getValue()); } } } public T getApi(HttpServletRequest request) { String version = versionExtractor.apply(request); return versionedImpls.getOrDefault(version, versionedImpls.get(defaultVersion)); } // 構建器模式簡化創建過程 public static Builder builder() { return new Builder(); } public static class Builder { private Class apiInterface; private Function versionExtractor; private String defaultVersion; private ApplicationContext applicationContext; public Builder apiInterface(Class apiInterface) { this.apiInterface = apiInterface; return this; } public Builder versionExtractor(Function versionExtractor) { this.versionExtractor = versionExtractor; return this; } public Builder defaultVersion(String defaultVersion) { this.defaultVersion = defaultVersion; return this; } public Builder applicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; return this; } public ApiVersionDelegator build() { return new ApiVersionDelegator(apiInterface, versionExtractor, defaultVersion, applicationContext); } } }
配置和使用委托器:
@Configuration public class ApiConfiguration { @Bean public ApiVersionDelegator userApiDelegator(ApplicationContext context) { return ApiVersionDelegator.builder() .apiInterface(UserApi.class) .versionExtractor(request -> { String version = request.getHeader("X-API-Version"); return version == null ? "2" : version; }) .defaultVersion("2") .applicationContext(context) .build(); } } @RestController @RequestMapping("/api/users") public class UserController { private final ApiVersionDelegator apiDelegator; public UserController(ApiVersionDelegator apiDelegator) { this.apiDelegator = apiDelegator; } @GetMapping("/{id}") public Object getUser(@PathVariable Long id, HttpServletRequest request) { UserApi api = apiDelegator.getApi(request); return api.getUser(id); } }
以上6種API版本控制方式各有優劣,選擇時應考慮以下因素
最后,版本控制只是手段,不是目的。關鍵是要構建可演進的API架構,讓系統能夠持續滿足業務需求的變化。選擇合適的版本控制策略,能夠在保證系統穩定性的同時,實現API的平滑演進。
原文轉載自:https://mp.weixin.qq.com/s/F8EGqWcM0MSl6nD6hUDyOg