原创

SpringBoot实战:整合Knife4j

Knife4j 在 Spring Boot 项目里的作用很直接:把接口定义、参数说明、返回结构、调试入口和分组展示整合到一套文档界面里。放到实际开发中,它解决的不是“有没有文档”问题,而是“文档能不能跟代码一起演进、能不能直接调接口、能不能让前后端和测试用同一份说明”问题。

先把版本关系说清楚

整合 Knife4j,第一步不是写配置,而是先选对依赖。这个地方最容易抄错。

  • Spring Boot 3.x

    • 只建议走 OpenAPI 3
    • 使用 knife4j-openapi3-jakarta-spring-boot-starter
    • 底层基于 springdoc-openapi
    • JDK 要求至少 17
    • 不要再额外重复引入其他 springdoc starter,避免 Jar 冲突 (doc.xiaominfo.com)
  • Spring Boot 2.4.x ~ 2.7.x

    • 如果项目还是 OpenAPI 3,使用 knife4j-openapi3-spring-boot-starter
    • 如果项目还是 Swagger 2 / OpenAPI 2,使用 knife4j-openapi2-spring-boot-starter
    • 官方对 Spring Boot 2 的建议区间是 2.4.0 ~ 3.0.0 之前
    • Spring Boot 低于 2.4 时,更适合使用 Knife4j 4.0 之前的版本 (doc.xiaominfo.com)

这篇文章后面的代码,统一按 Spring Boot 3.x + Knife4j + OpenAPI 3 来写。这也是现在新项目更合理的组合。Knife4j 在 Boot 3 场景下本质上是对 springdoc 的增强展示层,因此很多扫描、分组、文档路径配置仍然遵循 springdoc 的方式。(doc.xiaominfo.com)

一套能直接落地的整合方式

先看 Maven 依赖。核心思路很简单:Web、参数校验、Knife4j starter 三件套就够了。

<dependencies>
    <!-- Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 参数校验 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- Knife4j:Spring Boot 3.x 使用 Jakarta Starter -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
        <version>4.4.0</version>
    </dependency>
</dependencies>

对于 Spring Boot 3,官方文档给出的 starter 就是 knife4j-openapi3-jakarta-spring-boot-starter。同时,Knife4j 已经带上 springdoc 相关能力,通常不需要你再手动补一个 springdoc-openapi-starter-webmvc-ui。(doc.xiaominfo.com)

application.yml 配置

下面这份配置足够覆盖大部分业务项目。

server:
  port: 8080

springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  group-configs:
    - group: default
      paths-to-match: /**
      packages-to-scan: com.example.demo.controller

knife4j:
  enable: true
  setting:
    language: zh_cn

这里有三个关键点:

  1. springdoc.group-configs 决定扫描哪些包、哪些路径。
  2. knife4j.enable=true 表示启用 Knife4j 增强能力。
  3. 接口元数据输出路径默认是 /v3/api-docs,Knife4j 页面入口通常还是 /doc.html。官方示例也明确保留了 springdoc 配置项和 Knife4j 增强配置的组合方式。(doc.xiaominfo.com)

补一份 OpenAPI 基础信息

如果你只配了 starter,页面能出来,但文档标题、描述、联系人这些信息还是空的。生产项目里最好显式补上。

package com.example.demo.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("用户中心接口文档")
                        .description("提供用户查询、创建、更新等接口")
                        .version("1.0.0")
                        .contact(new Contact()
                                .name("backend-team")
                                .email("backend@example.com")));
    }
}

这部分虽然不是 Knife4j 独有配置,但在 Boot 3 方案里,Knife4j 基于 OpenAPI 3 和 springdoc 工作,因此文档基础信息仍然建议按 OpenAPI 对象方式统一声明。(doc.xiaominfo.com)

Controller 怎么写,文档才会完整

如果只是把接口暴露出来而不写注解,Knife4j 也能扫描到基础信息,但说明会很贫瘠。实际项目里建议至少把接口摘要、字段含义、参数约束写出来。

请求对象

package com.example.demo.model.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Schema(description = "创建用户请求")
public class UserCreateRequest {

    @Schema(description = "用户名", example = "zhangsan")
    @NotBlank(message = "用户名不能为空")
    @Size(max = 32, message = "用户名长度不能超过32")
    private String username;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    @Schema(description = "昵称", example = "张三")
    @Size(max = 50, message = "昵称长度不能超过50")
    private String nickname;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
}

返回对象

package com.example.demo.model.vo;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "用户详情")
public class UserVO {

    @Schema(description = "用户ID", example = "1")
    private Long id;

    @Schema(description = "用户名", example = "zhangsan")
    private String username;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    private String email;

    @Schema(description = "昵称", example = "张三")
    private String nickname;

    public UserVO() {
    }

    public UserVO(Long id, String username, String email, String nickname) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.nickname = nickname;
    }

    public Long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public String getEmail() {
        return email;
    }

    public String getNickname() {
        return nickname;
    }
}

Controller 示例

package com.example.demo.controller;

import com.example.demo.model.request.UserCreateRequest;
import com.example.demo.model.vo.UserVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
@Tag(name = "用户管理")
public class UserController {

    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户")
    public UserVO getById(
            @Parameter(description = "用户ID", required = true, example = "1")
            @PathVariable Long id) {
        return new UserVO(id, "zhangsan", "zhangsan@example.com", "张三");
    }

    @PostMapping
    @Operation(summary = "创建用户")
    public UserVO create(@Valid @RequestBody UserCreateRequest request) {
        return new UserVO(
                1L,
                request.getUsername(),
                request.getEmail(),
                request.getNickname()
        );
    }
}

这种写法有几个实际效果:

  • @Tag 决定文档里的模块分组
  • @Operation 决定接口摘要
  • @Schema 决定模型字段说明
  • jakarta.validation 注解不仅参与运行时校验,也能帮助文档更准确展示字段约束

springdoc 官方文档明确说明,它会自动基于 Spring 配置、类结构和注解推断 API 语义,并支持校验注解信息的整合。(OpenAPI 3 Library for spring-boot)

启动后访问哪些地址

项目启动后,最常用的是这两个地址:

  • http://localhost:8080/doc.html
  • http://localhost:8080/v3/api-docs

其中:

  • /doc.html 是 Knife4j 的增强文档入口
  • /v3/api-docs 是 OpenAPI 原始描述数据
  • 如果你启用了 springdoc.swagger-ui.path=/swagger-ui.html,那么 Swagger UI 页面也会存在于对应路径下 (OpenAPI 3 Library for spring-boot)

很多团队会把 /v3/api-docs 作为网关聚合、测试平台或自动化工具的输入,把 /doc.html 作为日常联调入口,这样分层会更清楚。

如果项目接了 Spring Security

很多人说“Knife4j 配好了但打不开”,根因不是 Knife4j 本身,而是安全配置把文档地址拦住了。Boot 3 项目如果用了 Spring Security,至少要把文档相关路径放行。

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(
                                "/doc.html",
                                "/swagger-ui.html",
                                "/swagger-ui/**",
                                "/v3/api-docs/**",
                                "/webjars/**"
                        ).permitAll()
                        .anyRequest().authenticated()
                )
                .httpBasic(Customizer.withDefaults());

        return http.build();
    }
}

这里放行的重点不是只写 /doc.html,而是要把它依赖的静态资源和 OpenAPI 数据地址一起放开。否则页面能进,但脚本或接口文档请求还是会 401/403。

Boot 2 项目要怎么改

如果你的项目还在 Spring Boot 2,不是不能用 Knife4j,但要区分你当前走的是哪套规范。

Spring Boot 2 + OpenAPI 3

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>

Spring Boot 2 + OpenAPI 2 / Swagger 2

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>

Boot 2 和 Boot 3 最大的区别,不只是 starter 名字不同,而是 Boot 3 进入了 Jakarta 体系。因此你在代码里看到的校验、Servlet、注解生态都会和 Boot 2 的 javax.* 时代不同。Knife4j 官方文档也明确把 Boot 3 的 starter 单独拆成了 jakarta 版本。(doc.xiaominfo.com)

实战里最常见的几个坑

1. doc.html 返回 404

优先检查这几项:

  • Starter 是否选错版本
  • Boot 3 是否误用了 Boot 2 的 starter
  • 是否被 Spring Security 拦截
  • 是否又额外引入了别的 springdoc / swagger 相关依赖,导致资源路径或自动配置冲突

Boot 3 官方示例明确提示:Knife4j starter 已经引用 springdoc 相关 Jar,需要注意依赖冲突。(doc.xiaominfo.com)

2. 页面能打开,但没有接口

这个问题通常不是“Knife4j 扫不到”,而是“你限制了扫描范围”。

重点检查:

  • packages-to-scan 是否写对包名
  • paths-to-match 是否把接口路径排除了
  • Controller 是否真的是 @RestController
  • 接口是否走了 WebMVC,而不是其他未被当前 starter 覆盖的模式

3. 模型字段说明不完整

根因一般有两个:

  • DTO 没有写 @Schema
  • 只在实体类里堆数据库字段,没给接口层单独定义请求对象和返回对象

真正要把文档写清楚,接口模型最好和持久化模型分离。文档展示的是“接口契约”,不是数据库表结构。

4. Boot 3 项目里还在混用 javax.*

这个问题最隐蔽。项目能编译不代表文档一定正常,特别是从 Boot 2 升级上来的项目,常见情况是:

  • 旧 DTO 还在用 javax.validation.*
  • 旧拦截器、过滤器还在用 javax.servlet.*

Boot 3 既然已经切到 Jakarta 体系,接口文档相关注解和校验体系也应一起迁移。

一套更适合团队项目的组织方式

单体项目里,Knife4j 常常只是“把接口展示出来”;但在团队项目里,更推荐按下面的方式组织:

  • group-configs 按模块分组,比如用户、订单、支付
  • 所有接口都补 @Operation
  • 请求对象、响应对象都补 @Schema
  • 对公共错误码、鉴权方式、Header 约定做统一描述
  • 在网关层或 CI 流程里复用 /v3/api-docs

这样做的好处是,Knife4j 就不再只是一个“可视化页面”,而是项目接口契约的一部分。接口是否变更、参数是否新增、返回结构是否变化,都能通过代码提交一起被管理。

小结

Spring Boot 整合 Knife4j 本身并不复杂,真正容易出问题的地方有两个:版本选错,以及 把它当成一个纯前端页面看待

只要记住下面这条线,整合基本不会偏:

  • Boot 3knife4j-openapi3-jakarta-spring-boot-starter
  • Boot 2 + OpenAPI 3knife4j-openapi3-spring-boot-starter
  • Boot 2 + OpenAPI 2knife4j-openapi2-spring-boot-starter

然后围绕 springdoc 的思路去做扫描、分组、模型注解和安全放行,Knife4j 就能稳定落地。对于新项目,优先选 Spring Boot 3 + OpenAPI 3,这条路线更清晰,也更符合现在的文档生态。(doc.xiaominfo.com)

参考资料

正文到此结束
评论插件初始化中...
Loading...