SpringBoot3整合knife4j(swagger3)

目录

一、引言

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

[1、 添加/修改依赖](#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、添加配置文件

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.

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

等等

三、问题梳理

问题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
相关推荐
Geoking.1 天前
【设计模式】策略模式(Strategy)详解:把 if-else 变成可切换的算法
java·设计模式·策略模式
代码改变生活-1201 天前
idea 清除缓存之后重启项目编译失败
java·缓存·intellij-idea
Coder_Boy_1 天前
基于SpringAI的在线考试系统软件系统验收案例
人工智能·spring boot·软件工程·devops
Microsoft Word1 天前
HashMap面试题总结
java·开发语言
stillaliveQEJ1 天前
【MyBatis】DML映射
java·mybatis
qq_12498707531 天前
基于SSM框架的智能密室逃脱信息管理系统(源码+论文+部署+安装)
java·大数据·人工智能·spring boot·后端·毕业设计·计算机毕业设计
ekkcole1 天前
java实现对excel文件合并单元格(只针对文件)
java·开发语言·excel
no24544101 天前
RAGFlow 全面接入 MinerU 2.0,支持 pipeline、vlm-transformers、vlm-sglang 三种模式,解析精度大幅度up
java·大数据·人工智能·python·ai·sglang
lkbhua莱克瓦241 天前
MySQL事务隔离级别:从并发混乱到数据一致性守护者
java·数据库·mysql·隔离级别