Spring Boot 3 多版本 API 控制器与动态 Bean 注册

Spring Boot 3 多版本 API 控制器与动态 Bean 注册

在实际业务中,API 接口往往需要 迭代更新,但不能影响已有的调用方,因此 API 多版本管理 成为微服务架构的重要课题。本文结合 Spring Boot 3 的新特性,介绍一种 基于动态 Bean 注册与条件配置 的多版本 API 管理方案,并提供扩展优化思路。


一、实现目标

支持 多版本 API 控制器(如 /api/v1/users、/api/v2/users)通过 配置文件 动态激活某个版本支持 请求头选择版本(X-API-Version: v2)兼顾 灵活扩展(灰度升级、弃用策略、API 文档分组)


二、核心方案

1. 定义版本注解与条件



@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ApiVersion {
    String value();
}


public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    private final String apiVersion;
 
    public ApiVersionCondition(String apiVersion) {
        this.apiVersion = apiVersion;
    }
 
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        return new ApiVersionCondition(other.getApiVersion());
    }
 
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        String version = request.getHeader("X-API-Version");
        return this.apiVersion.equals(version) ? this : null;
    }
 
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        return other.getApiVersion().compareTo(this.apiVersion);
    }
 
    public String getApiVersion() {
        return apiVersion;
    }
}

2. 创建多版本控制器



// V1 控制器
@RestController
@ApiVersion("v1")
@RequestMapping("/api/v1")
public class UserControllerV1 {
    
    @GetMapping("/users")
    public ResponseEntity<List<User>> getUsers() {
        // V1 实现
        return ResponseEntity.ok(Collections.emptyList());
    }
}
 
// V2 控制器
@RestController
@ApiVersion("v2")
@RequestMapping("/api/v2")
public class UserControllerV2 {
    
    @GetMapping("/users")
    public ResponseEntity<List<UserV2>> getUsers() {
        // V2 实现
        return ResponseEntity.ok(Collections.emptyList());
    }
}

3. 动态 Bean 注册配置

方案一:手动注册(灵活但稍显复杂)



@Configuration
public class DynamicControllerRegistration implements BeanDefinitionRegistryPostProcessor {
 
    private final Environment environment;
 
    public DynamicControllerRegistration(Environment environment) {
        this.environment = environment;
    }
 
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String activeVersion = environment.getProperty("api.version.active", "v1");
 
        if ("v1".equals(activeVersion)) {
            registerController(registry, UserControllerV1.class);
        } else if ("v2".equals(activeVersion)) {
            registerController(registry, UserControllerV2.class);
        }
    }
 
    private void registerController(BeanDefinitionRegistry registry, Class<?> controllerClass) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(controllerClass);
        registry.registerBeanDefinition(
            StringUtils.uncapitalize(controllerClass.getSimpleName()), 
            builder.getBeanDefinition()
        );
    }
 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {}
}

方案二:条件注解(推荐,更简洁)



@RestController
@RequestMapping("/api/v1")
@ConditionalOnProperty(name = "api.version.active", havingValue = "v1")
public class UserControllerV1 { ... }
 
@RestController
@RequestMapping("/api/v2")
@ConditionalOnProperty(name = "api.version.active", havingValue = "v2")
public class UserControllerV2 { ... }

4. 请求拦截与异常处理



public class ApiVersionInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (handler instanceof HandlerMethod) {
            ApiVersion apiVersion = handler.getBean().getClass().getAnnotation(ApiVersion.class);
            if (apiVersion != null) {
                String requestVersion = request.getHeader("X-API-Version");
                if (!apiVersion.value().equals(requestVersion)) {
                    throw new ApiVersionException("API version mismatch");
                }
            }
        }
        return true;
    }
}


@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ApiVersionException.class)
    public ResponseEntity<ErrorResponse> handleApiVersionException(ApiVersionException ex) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                             .body(new ErrorResponse("API_VERSION_MISMATCH", ex.getMessage()));
    }
}

5. 配置文件



# application.properties
api.version.active=v2

三、优化与扩展

1. 运行时热切换

结合 Spring Cloud Config / Nacos 配置中心 + @RefreshScope,可实现 API 版本 动态切换,支持灰度发布。

2. API 路由器模式(集中式)

如果版本非常多,可以写一个路由器,统一分发:



@RestController
@RequestMapping("/api/users")
public class UserControllerRouter {
    private final UserServiceV1 userServiceV1;
    private final UserServiceV2 userServiceV2;
 
    @GetMapping
    public Object getUsers(@RequestHeader("X-API-Version") String version) {
        if ("v1".equals(version)) return userServiceV1.getUsers();
        if ("v2".equals(version)) return userServiceV2.getUsers();
        throw new ApiVersionException("Unsupported version: " + version);
    }
}

3. OpenAPI/Swagger 文档分组

使用 SpringDoc OpenAPI 配置分组:

/v1/api-docs/v2/api-docs

让调用方清晰查看不同版本的接口文档。

4. 弃用策略

在旧版本接口上标记:

@Deprecated响应头:



Deprecation: true
Sunset: 2025-12-31
Link: <https://yourapi.com/docs/v2>; rel="successor-version"

便于调用方提前迁移。


四、架构全景图


五、方案优势

灵活性:支持 Header + URI 两种版本选择方式动态控制:配置文件决定激活版本,可扩展热切换清晰分离:每个版本独立,易维护、易测试全生命周期管理:支持新增、灰度、弃用、下线


✅ 这套方案可以覆盖从 开发 → 部署 → 升级 → 下线 的完整 API 版本管理流程

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容