如何通过SpringDoc OpenAPI为两个模块暴露单个Swagger-UI
你遇到的核心问题是:rest1的Swagger-UI要展示分属独立容器的rest1(UserController)和rest2(ActionController)的API,但带Swagger注解的是rest-commons里的Spec接口,实际控制器只有@RestController,SpringDocUtils.getConfig().addRestControllers()不支持加载接口类。下面给几个可行的解决思路:
方案1:手动构建OpenAPI,加载Spec接口的注解
直接通过代码读取rest-commons里Spec接口的Swagger注解,手动构建API文档并注册到SpringDoc中,不需要依赖控制器的扫描逻辑。
实现步骤:
- 确保rest1引入
springdoc-openapi-starter-webmvc-ui依赖(适配Spring Boot版本),同时依赖rest-commons模块。 - 创建Swagger配置类,通过
GroupedOpenApi注册两个模块的API分组,用OpenApiCustomiser解析Spec接口的注解:
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.Paths; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.tags.Tag; import org.springdoc.core.customizers.OpenApiCustomiser; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @Configuration public class MultiModuleSwaggerConfig { // 注册用户模块API分组 @Bean public GroupedOpenApi userApiGroup() { return GroupedOpenApi.builder() .group("用户管理API") .addOpenApiCustomiser(buildUserApi()) .build(); } // 注册操作模块API分组 @Bean public GroupedOpenApi actionApiGroup() { return GroupedOpenApi.builder() .group("操作日志API") .addOpenApiCustomiser(buildActionApi()) .build(); } // 解析UserControllerSpec的注解,构建API定义 private OpenApiCustomiser buildUserApi() { return openApi -> { // 设置API基础信息 openApi.info(new Info().title("用户管理API").version("v1.0")); Paths paths = new Paths(); Method[] specMethods = UserControllerSpec.class.getDeclaredMethods(); for (Method method : specMethods) { Operation apiOperation = new Operation(); // 读取@Operation注解的摘要和描述 if (method.isAnnotationPresent(Operation.class)) { Operation ann = method.getAnnotation(Operation.class); apiOperation.summary(ann.summary()); apiOperation.description(ann.description()); // 可扩展处理@Parameter、@ApiResponse等注解 } // 从Spec接口的@RequestMapping读取路径(如果有的话),这里简化为方法名对应路径 String path = "/api/users/" + method.getName(); paths.addPathItem(path, new PathItem().get(apiOperation)); } openApi.paths(paths); // 添加标签 List<Tag> tags = new ArrayList<>(); if (UserControllerSpec.class.isAnnotationPresent(Tag.class)) { Tag annTag = UserControllerSpec.class.getAnnotation(Tag.class); tags.add(new Tag().name(annTag.name()).description(annTag.description())); } openApi.tags(tags); }; } // 解析ActionControllerSpec的注解,构建API定义 private OpenApiCustomiser buildActionApi() { return openApi -> { openApi.info(new Info().title("操作日志API").version("v1.0")); Paths paths = new Paths(); Method[] specMethods = ActionControllerSpec.class.getDeclaredMethods(); for (Method method : specMethods) { Operation apiOperation = new Operation(); if (method.isAnnotationPresent(Operation.class)) { Operation ann = method.getAnnotation(Operation.class); apiOperation.summary(ann.summary()); apiOperation.description(ann.description()); } String path = "/api/actions/" + method.getName(); paths.addPathItem(path, new PathItem().post(apiOperation)); } openApi.paths(paths); List<Tag> tags = new ArrayList<>(); if (ActionControllerSpec.class.isAnnotationPresent(Tag.class)) { Tag annTag = ActionControllerSpec.class.getAnnotation(Tag.class); tags.add(new Tag().name(annTag.name()).description(annTag.description())); } openApi.tags(tags); }; } }
方案2:让控制器实现Spec接口,自动继承注解
修改实际控制器,让它们实现rest-commons里的Spec接口,SpringDoc在扫描@RestController类时,会自动读取接口上的Swagger注解,这样不需要额外配置就能把接口的API定义关联到控制器上。
实现步骤:
- 修改rest1的UserController,实现UserControllerSpec:
@RestController @RequestMapping("/api/users") public class UserController implements UserControllerSpec { // 原有业务逻辑代码不变 }
- 修改rest2的ActionController,实现ActionControllerSpec:
@RestController @RequestMapping("/api/actions") public class ActionController implements ActionControllerSpec { // 原有业务逻辑代码不变 }
- 在rest1的Swagger配置中,扫描包含Spec接口和控制器的包:
import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SwaggerConfig { @Bean public GroupedOpenApi combinedApiGroup() { return GroupedOpenApi.builder() .group("全模块API") // 扫描rest1的控制器包和rest-commons的Spec接口包 .packagesToScan("com.yourproject.rest1", "com.yourproject.rest-commons") .build(); } }
注意:因为rest2部署在独立容器,rest1无法直接扫描到ActionController,但Spec接口在rest-commons里,SpringDoc会解析接口的注解生成API定义,同时你可以在rest1的配置中手动指定ActionControllerSpec.class到classesToScan中,确保接口被解析。
方案3:合并rest2的OpenAPI JSON
如果rest2本身也暴露了Swagger的OpenAPI接口(默认路径/v3/api-docs),可以在rest1中远程调用这个接口,获取JSON后合并到自己的Swagger-UI中。
实现步骤:
- 确保rest2启动后,rest1可以通过网络访问到
http://rest2-host:port/v3/api-docs。 - 在rest1的配置中,添加
OpenApiCustomiser来合并API定义:
import org.springdoc.core.customizers.OpenApiCustomiser; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import io.swagger.v3.oas.models.OpenAPI; @Configuration public class SwaggerMergeConfig { private final RestTemplate restTemplate = new RestTemplate(); @Bean public OpenApiCustomiser mergeRest2Api() { return openApi -> { try { // 调用rest2的API文档接口 OpenAPI rest2OpenApi = restTemplate.getForObject( "http://rest2-service:8080/v3/api-docs", OpenAPI.class ); if (rest2OpenApi != null) { // 合并路径、标签、组件定义 openApi.getPaths().putAll(rest2OpenApi.getPaths()); openApi.getTags().addAll(rest2OpenApi.getTags()); openApi.getComponents().addSchemas(rest2OpenApi.getComponents().getSchemas()); } } catch (Exception e) { // 处理服务不可达的异常,可根据需求调整日志或降级逻辑 e.printStackTrace(); } }; } }
这个方案适合微服务架构下,跨服务合并API文档的场景。
内容的提问来源于stack exchange,提问作者phlipe

