目录
二、SpringBoot3整合knife4j(swagger3)关键点梳理
[1、 添加/修改依赖](#1、 添加/修改依赖)
一、引言
使用jdk21版本创建的spring boot项目, 在SpringBoot3整合swagger3过程中遇到一些问题,网上很多写的SpringBoot3 整合knife4j(swagger3)的步骤,完全照着做很多都是有问题的。 现在将遇到的问题做记录。
二、SpringBoot3整合knife4j(swagger3)关键点梳理
1、 添加/修改依赖
- Spring Boot 3.x必须使用
knife4j-openapi3-jakarta(Jakarta命名空间)
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.5.0</version> </dependency>
网上很多文章说使用下面的maven是有问题的。 本人调试实际上遇到很多问题,一个问题解决又出了另一个问题。
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
- 我用的SpringBoot版本是3.4.5。SpringBoot3.x相比SpringBoot2.x特性上有很大改变。必须选择SpringBoot3.x版本兼容的knife4j(swagger3)版本。
看看knife4j官网怎么说: https://doc.xiaominfo.com/docs/quick-start

2、添加配置文件
java
package com.Knife4j;
//
import cn.hutool.core.util.RandomUtil;
import io.swagger.v3.oas.annotations.Hidden;
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.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class Knife4jConfig {
private final static Logger logger = LoggerFactory.getLogger(Knife4jConfig.class);
private static final String SERVICE_URL = "http://127.0.0.1:8886/tj4/doc.html";
private static final String API_INFO_TITLE = "软件接口文档";
private static final String API_INFO_VERSION = "V1.0";
private static final String API_INFO_DESCRIPTION = "Api接口列表";
private static final String API_INFO_LICENSE = "2025年度内部文档,违拷必究.";
private static final String API_INFO_EMAIL = "zpphnkjxy@126.com";
private static final String API_INFO_NAME = "zpp";
// xxx1模块
@Bean
public GroupedOpenApi api4() {
return GroupedOpenApi.builder()
.group("regularGrade-module-api")
.displayName("平时成绩模块接口")
.packagesToScan("com.call.controller.regularGrade")//xxx1模块接口所在包
// 自定义全局响应码
.addOpenApiCustomizer((this::setCustomStatusCode))
.build();
}
// 智能评分模块
@Bean
public GroupedOpenApi api3() {
return GroupedOpenApi.builder()
.group("IntelligentScoring-module-api")
.displayName("智能评分模块接口")
.packagesToScan("com.call.controller.intelligentScoring")
// 自定义全局响应码
.addOpenApiCustomizer((this::setCustomStatusCode))
.build();
}
// AI大模型Agent接口
@Bean
public GroupedOpenApi api2() {
return GroupedOpenApi.builder()
.group("aiagent-module-api")
.displayName("AI大模型Agent接口")
.packagesToScan("com.ai.LangChain4j.agent2.DeclarativeAPI")
// .pathsToMatch("/v1/**")
.addOpenApiMethodFilter(method -> method.isAnnotationPresent(io.swagger.v3.oas.annotations.Operation.class))
// 自定义全局响应码
.addOpenApiCustomizer((this::setCustomStatusCode))
.build();
}
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title(API_INFO_TITLE)
.description(API_INFO_DESCRIPTION)
.version(API_INFO_VERSION)
.contact(new Contact().name(API_INFO_NAME).email(API_INFO_EMAIL))
.license(new License().name(API_INFO_LICENSE).url(SERVICE_URL))
);
}
/**
* 设置自定义错误码
*
* @param openApi openApi对象
*/
private void setCustomStatusCode(OpenAPI openApi) {
if (openApi.getPaths() != null) {
Paths paths = openApi.getPaths();
for (Map.Entry<String, PathItem> entry : paths.entrySet()) {
String key = entry.getKey();
PathItem value = entry.getValue();
// put方式自定义全局响应码
Operation put = value.getPut();
// get方式自定义全局响应码
Operation get = value.getGet();
// delete方式自定义全局响应码
Operation delete = value.getDelete();
// post方式自定义全局响应码
Operation post = value.getPost();
if (put != null) {
put.setResponses(handleResponses(put.getResponses()));
}
if (get != null) {
get.setResponses(handleResponses(get.getResponses()));
}
if (delete != null) {
delete.setResponses(handleResponses(delete.getResponses()));
}
if (post != null) {
post.setResponses(handleResponses(post.getResponses()));
}
}
}
}
/**
* 处理不同请求方式中的自定义响应码
* - 响应码中使用原有的响应体Content(否则会造成BaseRes中通用的data无法解析各自的对象)
* - 使用原生的ApiResponses作为返回体(否则会造成前端响应示例和响应内容中丢失注释)
*
* @param responses 响应体集合
* @return 返回处理后的响应体集合
*/
private ApiResponses handleResponses(ApiResponses responses) {
// 设置默认Content
Content content = new Content();
// 以下代码注释,因为无论如何都会从原生responses中获取到一个Content
// MediaType mediaType = new MediaType();
// Schema schema = new Schema();
// schema.set$ref("#/components/schemas/BaseRes");
// mediaType.setSchema(schema);
// content.addMediaType("*/*", mediaType);
// 从原来的responses中获取原生Content
for (Map.Entry<String, ApiResponse> entry : responses.entrySet()) {
String key = entry.getKey();
ApiResponse apiResponse = entry.getValue();
if (apiResponse != null) {
content = apiResponse.getContent();
break;
}
}
// 获取全部全局响应自定义列表
Map<Integer, String> map = StatusCode.toMap();
// 设置全局响应码
for (Map.Entry<Integer, String> entry : map.entrySet()) {
ApiResponse api = new ApiResponse();
api.setContent(content);
api.description(entry.getValue());
responses.addApiResponse(entry.getKey() + "", api);
}
return responses;
}
/**
* 根据@Tag 上的排序,写入x-order
*
* @return the global open api customizer
*/
@Bean
public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() {
return openApi -> {
if (openApi.getTags()!=null){
openApi.getTags().forEach(tag -> {
Map<String,Object> map=new HashMap<>();
map.put("x-order", RandomUtil.randomInt(0,100));
tag.setExtensions(map);
});
}
if(openApi.getPaths()!=null){
openApi.addExtension("x-test123","333");
openApi.getPaths().addExtension("x-abb",RandomUtil.randomInt(1,100));
}
};
}
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("AI大模型API")
.version("1.0")
.description( "Knife4j集成springdoc-openapi")
.termsOfService("http://doc.xiaominfo.com")
.license(new License().name("Apache 2.0")
.url("http://doc.xiaominfo.com")));
}
}
3、启动springboot
启动成功,访问:http://127.0.0.1:8886/doc.html
出现类似下面的页面:

4、配置项
springdoc: # 防止全局异常处理器的响应定义覆盖所有接口[citation:2] override-with-generic-response: false # 禁用损坏引用清理(如遇到Schema解析问题可尝试)[citation:4] remove-broken-reference-definitions: false swagger-ui: path: /doc.html tags-sorter: alpha operations-sorter: alpha api-docs: path: /v3/api-docs group-configs: - group: 'default' paths-to-match: '/**' packages-to-scan: com # knife4j的增强配置,不需要增强可以不配 knife4j: enable: true setting: language: zh_cn
运行结果(目前访问doc.html,会自动跳转到swagger-ui/index.html):

5、配置项中添加更多控制细节
配置项如下:
# 配置Knife4j,以启用Swagger文档的增强功能和定制化展示 knife4j: # 启用Knife4j扩展 enable: true # 配置展示的文档分组 documents: - # 文档分组标题 group: 2.X版本 # 文档分组描述 name: 接口签名 # 指定接口文档的位置 locations: classpath:sign/* # 配置Knife4j的展示细节和功能开关 setting: # 设置界面语言 language: zh-CN # 启用Swagger模型展示 enable-swagger-models: true # 启用文档管理功能 enable-document-manage: true # 设置Swagger模型的显示名称 swagger-model-name: 实体类列表 # 是否显示版本信息 enable-version: false # 是否启用参数缓存刷新 enable-reload-cache-parameter: false # 启用后端脚本支持 enable-after-script: true # 过滤特定方法类型的multipart/form-data接口 enable-filter-multipart-api-method-type: POST # 是否过滤所有multipart/form-data类型的接口 enable-filter-multipart-apis: false # 启用请求缓存 enable-request-cache: true # # 是否显示自定义主机名 # enable-host: false # # 设置自定义的主机名 # enable-host-text: 192.168.0.193:8000 # # 启用自定义首页 # enable-home-custom: true # # 设置自定义首页的路径 # home-custom-path: classpath:markdown/home.md # 是否启用搜索功能 enable-search: false # 是否显示页脚 enable-footer: false # 启用自定义页脚内容 enable-footer-custom: true # 设置自定义页脚的内容 footer-custom-content: Apache License 2.0 # 是否启用动态参数 enable-dynamic-parameter: false # 启用调试模式 enable-debug: true # 启用OpenAPI 3.0的支持 enable-open-api: false # 启用接口分组功能 enable-group: true # 是否启用CORS跨域支持 cors: false # 是否启用生产模式 production: false # 配置基本的认证信息 basic: # 启用基本认证 enable: false # 设置用户名 username: admin # 设置密码 password: 123 springdoc: # 防止全局异常处理器的响应定义覆盖所有接口[citation:2] override-with-generic-response: false # 禁用损坏引用清理(如遇到Schema解析问题可尝试)[citation:4] remove-broken-reference-definitions: false swagger-ui: path: /doc.html tags-sorter: alpha operations-sorter: alpha api-docs: path: /v3/api-docs group-configs: - group: 'default' paths-to-match: '/**' packages-to-scan: com
目前启动时会报如下错误(暂时未解决):
2025-12-24T23:04:56.337+08:00 WARN 35067 --- [langchain4jAI] [ restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'knife4jAutoConfiguration' defined in URL [jar:file:/Users/apple/.m2/repository/com/github/xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter/4.5.0/knife4j-openapi3-jakarta-spring-boot-starter-4.5.0.jar!/com/github/xiaoymin/knife4j/spring/configuration/Knife4jAutoConfiguration.class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties' available: expected single matching bean but found 2: knife4jProperties,knife4j-com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties
2025-12-24T23:04:56.444+08:00 INFO 35067 --- [langchain4jAI] [ restartedMain] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2025-12-24T23:04:56.478+08:00 INFO 35067 --- [langchain4jAI] [ restartedMain] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-12-24T23:04:56.762+08:00 ERROR 35067 --- [langchain4jAI] [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration required a single bean, but 2 were found:
knife4jProperties: defined in URL [jar:file:/Users/apple/.m2/repository/com/github/xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter/4.5.0/knife4j-openapi3-jakarta-spring-boot-starter-4.5.0.jar!/com/github/xiaoymin/knife4j/spring/configuration/Knife4jProperties.class]
knife4j-com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties: defined in unknown location
This may be due to missing parameter name information
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
Ensure that your compiler is configured to use the '-parameters' flag.
You may need to update both your build tool settings as well as your IDE.
等等
三、问题梳理
问题1: 启动springboot后访问doc.html页面报错
java
java.lang.NoSuchMethodError: 'void org.springframework.web.method.ControllerAdviceBean.<init>(java.lang.Object)'] with root cause
java.lang.NoSuchMethodError: 'void org.springframework.web.method.ControllerAdviceBean.<init>(java.lang.Object)'
at org.springdoc.core.service.GenericResponseService.lambda$getGenericMapResponse$8(GenericResponseService.java:702) ~[springdoc-openapi-starter-common-2.3.0.jar:2.3.0]
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178) ~[na:na]
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[na:na]
at org.springdoc.core.service.GenericResponseService.getGenericMapResponse(GenericResponseService.java:704) ~[springdoc-openapi-starter-common-2.3.0.jar:2.3.0]
at org.springdoc.core.service.GenericResponseService.build(GenericResponseService.java:246) ~[springdoc-openapi-starter-common-2.3.0.jar:2.3.0]
at org.springdoc.api.AbstractOpenApiResource.calculatePath(AbstractOpenApiResource.java:499) ~[springdoc-openapi-starter-common-2.3.0.jar:2.3.0]
网上查了很多资料,最终看到下面的两个内容解决了问题:
https://www.cnblogs.com/ybbit/p/18946108
https://blog.51cto.com/u_16099236/14391218
由于我不想降低springboot的版本, 故而选择修改配置文件。 最终解决办法: 确定该问题是增加异常处理器类后,对全局的异常起作用了,如对swagger也起作用了。所以可以在application配置里增加springdoc的配置,防止全局异常处理器的响应定义覆盖所有接口。
application.yml添加的内容如下所示:
java
springdoc:
# 防止全局异常处理器的响应定义覆盖所有接口[citation:2]
override-with-generic-response: false
# 禁用损坏引用清理(如遇到Schema解析问题可尝试)[citation:4]
remove-broken-reference-definitions: false