SpringBoot3整合knife4j(swagger3)

目录

一、引言

二、SpringBoot3整合knife4j(swagger3)关键点梳理

1、 添加/修改依赖

2、添加配置文件

3、启动springboot


一、引言

        使用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、添加配置文件

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 = "[email protected]"; 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.

(See https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#parameter-name-ret

等等

三、问题梳理

问题1: 启动springboot后访问doc.html页面报错

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添加的内容如下所示:

springdoc: # 防止全局异常处理器的响应定义覆盖所有接口[citation:2] override-with-generic-response: false # 禁用损坏引用清理(如遇到Schema解析问题可尝试)[citation:4] remove-broken-reference-definitions: false

Read more

GLM-Image WebUI多用户协作方案:Gradio队列+会话隔离+个人输出目录自动创建

GLM-Image WebUI多用户协作方案:Gradio队列+会话隔离+个人输出目录自动创建 1. 为什么需要多用户协作能力? 你可能已经用过GLM-Image WebUI,输入一段文字,点击生成,几秒钟后一张高清图像就出现在屏幕上——这个过程很流畅,但前提是:只有你在用。 可现实场景中,情况往往不是这样。比如团队内部共享一台高性能服务器做AI图像实验,或者教学环境中老师带着几十个学生同时上手实践,又或者公司为市场部、设计部、产品部统一部署一个图像生成服务入口……这时候你会发现,原生的Gradio界面立刻暴露出三个关键问题: * 请求挤占:多人同时点“生成图像”,GPU显存瞬间爆满,有人卡住不动,有人报错退出; * 结果混杂:所有人生成的图都默认存进同一个/outputs/文件夹,张三的赛博朋克海报和李四的水墨山水画堆在一起,找图像像大海捞针; * 会话干扰:王五刚调好一组参数准备批量生成,赵六刷新页面重置了所有设置,前功尽弃。 这些问题不是小毛病,而是从单人玩具升级为团队生产力工具时必须跨过的门槛。本文不讲模型原理,也不重复部署步骤,而是聚焦一个工程落地中真实存在

By Ne0inhk

ClawdBot步骤详解:前端无法访问时的SSH端口转发与Token链接获取

ClawdBot步骤详解:前端无法访问时的SSH端口转发与Token链接获取 1. ClawdBot是什么:你的本地AI助手,不依赖云端服务 ClawdBot 是一个真正属于你自己的个人 AI 助手——它不是网页上点几下就用的 SaaS 工具,而是一个能完整运行在你本地设备(笔记本、台式机、甚至树莓派)上的独立应用。它不像很多“AI助手”那样把你的提示词悄悄发到远端服务器,而是把模型推理、对话管理、插件调度全部留在你自己的机器里。 它的后端由 vLLM 驱动,这意味着你能以极高的吞吐和极低的延迟运行像 Qwen3-4B-Instruct 这样的高质量开源模型。vLLM 的 PagedAttention 技术让显存利用更高效,4GB 显存也能稳稳跑起 4B 级别模型,响应快、不卡顿、不排队。 更重要的是,ClawdBot 的设计哲学是“可控即可靠”。所有配置文件明文可读、所有模型路径清晰可见、所有日志本地留存。你不需要成为 DevOps

By Ne0inhk
Spring Web MVC从入门到实战

Spring Web MVC从入门到实战

—JavaEE专栏— 1. Spring Web MVC核心概念 1.1 什么是Spring Web MVC Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring框架中,其正式名称来源于源模块名称(spring-webmvc),通常简称为Spring MVC。 官方定义:Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. Servlet是Java Web开发的规范,定义了动态页面开发的技术标准,而Tomcat、Weblogic等Servlet容器则是该规范的具体实现,

By Ne0inhk