随着 Java 生态向 JDK 17 及 Jakarta EE
的演进,许多项目面临从 JDK 8 升级的挑战,其中 Swagger
(API 文档工具)的兼容性调整尤为关键。本文将从 技术栈差异、升级迁移步骤、常见问题 等多个维度,解析 JDK 8(SpringFox
)向 JDK 17(SpringDoc/Knife4j
)的升级路径。
JDK 版本演进:
JDK 17 是继 JDK 8 后的首个 LTS 版本,支持模块化、Records
等新特性,但移除了部分旧 API(如 javax.servlet
)。
直接影响:基于 JDK 8 构建的 SpringFox
(Swagger 2.x
)因依赖旧规范无法兼容新版本。
Jakarta EE 的崛起:
Java EE 移交 Eclipse 基金会后更名为 Jakarta EE
,包名从 javax.*
改为 jakarta.*
。
核心冲突:Spring Boot 3.x
和 SpringDoc
(Swagger 3.x
)强制依赖 Jakarta EE 9+
,导致旧项目升级时需全局替换包名。
安全风险:SpringFox
已停止维护,存在未修复漏洞(如 CVE-2021-28170)。
功能需求:SpringDoc
支持 OpenAPI 3.0
规范,提供更灵活的文档定义和响应示例。
生态兼容:微服务、云原生场景下,JDK 17 的容器化支持更优。
特性 | JDK 8 + SpringFox (Swagger 2.x) | JDK 17 + SpringDoc/Knife4j (Swagger 3.x) |
---|---|---|
核心框架 | SpringFox 2.x (已停止维护) |
SpringDoc OpenAPI 3.x (官方推荐) |
JDK 兼容性 | 仅支持 JDK 8~11 | 支持 JDK 17+ 的模块化特性 |
Spring Boot 支持 | Spring Boot 2.x |
Spring Boot 3.x (兼容 2.7.x ) |
Servlet 规范 | 基于 javax.servlet |
迁移至 jakarta.servlet (Jakarta EE 9+ ) |
注解库 | io.swagger.annotations |
io.swagger.v3.oas.annotations |
注解风格 | @Api , @ApiOperation |
@Tag , @Operation (更符合 OpenAPI 3.0 ) |
依赖管理 | 需手动管理版本,易冲突 | 通过 Spring Boot Starter 简化依赖 |
文档生成 | 需配置 Docket |
自动扫描,通过 OpenAPI Bean 全局配置 |
文档规范 | OpenAPI 2.0 |
OpenAPI 3.0 |
UI 工具 | Swagger UI(基础功能) | Knife4j(增强功能,支持离线文档、权限控制、接口分组等) |
维护状态 | 停止维护(最后版本 3.0.0 ) |
活跃维护(SpringDoc 2.x + Knife4j 4.x ) |
技术栈 | JDK 8 | JDK 11 | JDK 17 | Spring Boot 2.7.x | Spring Boot 3.x |
---|---|---|---|---|---|
SpringFox 2.x | ✅ | ⚠️ 部分兼容 | ❌ | ✅ | ❌ |
SpringDoc 1.x | ✅ | ✅ | ❌ | ✅ | ❌ |
SpringDoc 2.x | ❌ | ✅ | ✅ | ✅ | ✅ |
移除旧依赖
<!-- 删除 SpringFox 相关依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
添加新依赖
<!-- SpringDoc 核心 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<!-- 最新版本见:https://springdoc.org/ -->
<version>2.5.0</version>
</dependency>
<!-- Knife4j 增强 UI(可选) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<!-- 最新版本见:https://doc.xiaominfo.com/ -->
<version>4.5.0</version>
</dependency>
排除冲突依赖
<!-- 检查并排除其他库中的 javax.servlet 依赖 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
控制器注解迁移:
SpringFox (Swagger 2.x) | SpringDoc (OpenAPI 3.x) | 用途 | 示例 |
---|---|---|---|
@Api |
@Tag |
标记控制器类的作用 | @Tag(name = "用户管理", description = "用户接口") |
@ApiOperation |
@Operation |
描述接口方法的功能 | @Operation(summary = "创建用户", description = "根据DTO创建用户") |
@ApiParam |
@Parameter |
描述接口参数(路径、查询、表单参数等) | @Parameter(name = "id", description = "用户ID", required = true) |
@ApiResponse |
@ApiResponse |
定义接口的响应状态码和描述 | @ApiResponse(responseCode = "404", description = "用户不存在") |
@ApiIgnore |
@Hidden 或 @Parameter(hidden = true) |
隐藏接口或参数 | @Hidden // 隐藏整个接口方法 |
@ApiImplicitParams |
@Parameters + @Parameter |
描述非直接声明的参数(如 Header 参数) | @Parameters({ @Parameter(name = "token", in = HEADER, description = "认证令牌") }) |
@ApiImplicitParam |
@Parameter |
单个隐式参数定义 | 同上 |
// JDK 8(SpringFox)
@Api(tags = "用户管理", description = "用户接口")
@RestController
public class UserController {
@ApiOperation("创建用户")
@PostMapping("/users")
public User createUser(@ApiParam("用户DTO") @RequestBody UserDTO dto) {
// ...
}
@ApiImplicitParams({
@ApiImplicitParam(name = "token", value = "认证令牌", paramType = "header")
})
@GetMapping("/profile")
public UserProfile getProfile() {
//...
}
}
// JDK 17(SpringDoc)
@Tag(name = "用户管理", description = "用户接口")
@RestController
public class UserController {
@Operation(summary = "创建用户", description = "根据DTO创建用户")
@PostMapping("/users")
public User createUser(@Parameter(description = "用户DTO", required = true)
@RequestBody UserDTO dto) {
// ...
}
@Parameters({
@Parameter(name = "token", description = "认证令牌", in = ParameterIn.HEADER)
})
@GetMapping("/profile")
public UserProfile getProfile() {
// ...
}
}
模型类注解迁移:
SpringFox (Swagger 2.x) | SpringDoc (OpenAPI 3.x) | 用途 | 示例 |
---|---|---|---|
@ApiModel |
@Schema |
描述数据模型类 | @Schema(name = "UserDTO", description = "用户传输对象") |
@ApiModelProperty |
@Schema |
描述模型字段的详细信息 | @Schema(description = "用户名", example = "张三", requiredMode = REQUIRED) |
// JDK 8(SpringFox)
@ApiModel(value = "User", description = "用户实体")
public class User {
@ApiModelProperty(value = "用户名", required = true, example = "张三")
private String name;
}
// JDK 17(SpringDoc)
@Schema(name = "User", description = "用户实体")
public class User {
@Schema(description = "用户名", example = "张三", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
}
是否还需要传统 SwaggerConfig?
不需要:Knife4j OpenAPI3
基于 SpringDoc
,无需配置 Docket
或 Swagger2Markup
。
必要配置:仅需定义 OpenAPI Bean
(如上文的 OpenApiConfig)即可。
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("API 文档")
.version("1.0")
.description("JDK 17 迁移示例")
.contact(new Contact().name("xcbeyond技术支持").email("support@example.com"))
.license(new License().name("Apache 2.0").url("https://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("详细文档")
.url("https://xcbeyond.com"))
.components(new Components()
.addSecuritySchemes("BearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
在微服务架构中,API 文档分组配置的核心管理需求是:
模块化展示:将不同业务域(用户/订单/支付)分离展示。
权限隔离:区分公共 API 和管理 API。
版本管理:同时维护 v1 和 v2 接口。
依赖解耦:避免单个文档过大导致加载性能问题。
分组配置参数详解:
配置方法 | 参数说明 | 默认值 | 示例 |
---|---|---|---|
.group(String group) |
分组唯一标识(显示在 UI 中) | 必填 | .group("用户管理") |
.pathsToMatch(String... paths) |
路径匹配规则(支持 Ant 风格) | 可选 | .pathsToMatch("/api/user/**") |
.packagesToScan(String... pkgs) |
扫描的包路径 | 可选 | .packagesToScan("com.example") |
.pathsToExclude(String... paths) |
排除的路径 | 空数组 | .pathsToExclude("/internal/**") |
.addOpenApiMethodFilter(Predicate) |
自定义方法过滤逻辑 | 无 | 见下面示例 |
.displayName(String name) |
显示名称(覆盖 group 的显示) | 同 group 值 | .displayName("用户模块") |
.addOperationCustomizer(...) |
自定义操作处理器 | 无 | 见下面示例 |
通过合理的分组配置,可在 JDK 17 环境下构建清晰、安全、易维护的 API 文档体系,充分发挥 SpringDoc
和 Knife4j
的现代化文档能力。
基础分组配置:
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiGroupConfig {
// 用户管理分组
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("用户管理") // 分组显示名称
.pathsToMatch("/api/user/**") // 路径匹配规则
.packagesToScan("com.example.user") // 包扫描路径
.build();
}
// 订单管理分组
@Bean
public GroupedOpenApi orderApi() {
return GroupedOpenApi.builder()
.group("订单管理")
.pathsToMatch("/api/order/**")
.packagesToScan("com.example.order")
.build();
}
// 分组自定义排序
@Bean
public GroupedOpenApi firstGroup() {
return GroupedOpenApi.builder()
.group("01-核心接口")
.order(1) // 分组排序(数值越小越靠前)
.pathsToMatch("/core/**")
.build();
}
@Bean
public GroupedOpenApi secondGroup() {
return GroupedOpenApi.builder()
.group("02-辅助接口")
.order(2)
.pathsToMatch("/support/**")
.build();
}
}
按安全权限分组:
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("管理员接口")
.pathsToMatch("/api/admin/**")
// 只包含带有 @PreAuthorize("hasRole('ADMIN')") 的接口
.addOpenApiMethodFilter(method ->
method.isAnnotationPresent(PreAuthorize.class) &&
method.getAnnotation(PreAuthorize.class).value().contains("ADMIN")
)
.build();
}
多版本 API 分组:
@Bean
public GroupedOpenApi v1Api() {
return GroupedOpenApi.builder()
.group("API-v1")
.pathsToMatch("/api/v1/**")
.displayName("版本 1.0 (已弃用)")
.build();
}
@Bean
public GroupedOpenApi v2Api() {
return GroupedOpenApi.builder()
.group("API-v2")
.pathsToMatch("/api/v2/**")
.displayName("版本 2.0 (最新)")
.build();
}
第三方接口分组:
@Bean
public GroupedOpenApi paymentApi() {
return GroupedOpenApi.builder()
.group("支付网关")
.pathsToMatch("/payment/**")
// 排除内部实现类
.packagesToExclude("com.example.internal.payment")
.build();
}
IDE 操作:
使用 IntelliJ/Eclipse 的全局替换功能(Ctrl+Shift+R
),将以下包名替换:
javax.servlet
→ jakarta.servlet
javax.validation
→ jakarta.validation
javax.persistence
→ jakarta.persistence
Maven 插件辅助:
使用 maven-replacer-plugin
自动化替换:
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>1.5.3</version>
<executions>
<execution>
<phase>process-sources</phase>
<goals><goal>replace</goal></goals>
</execution>
</executions>
<configuration>
<includes>**/*.java</includes>
<replacements>
<replacement>
<token>javax.servlet</token>
<value>jakarta.servlet</value>
</replacement>
</replacements>
</configuration>
</plugin>
// src/main/java/module-info.java
open module com.example.api {
requires spring.boot;
requires spring.boot.autoconfigure;
requires spring.web;
requires springdoc.openapi.common;
requires com.fasterxml.jackson.databind;
exports com.example.api.controller;
exports com.example.api.model;
}
启动应用后,访问以下地址:
Knife4j UI 文档:http://localhost:8080/doc.html
OpenAPI JSON:http://localhost:8080/v3/api-docs
响应示例:
@Operation(summary = "创建用户")
@ApiResponses({
@ApiResponse(
responseCode = "201",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = User.class),
examples = @ExampleObject(
name = "successExample",
value = """
{
"id": 1,
"name": "张三"
}
"""
)
)
),
@ApiResponse(
responseCode = "400",
content = @Content(
examples = @ExampleObject(
name = "errorExample",
value = """
{
"code": "INVALID_REQUEST",
"message": "用户名不能为空"
}
"""
)
)
)
})
public ResponseEntity<User> createUser(@RequestBody User user) { ... }
离线文档导出:
Knife4j
导出:访问 http://localhost:8080/doc.html#/home
,点击“下载 Markdown”或“下载 OpenAPI JSON”。
生产环境禁用 UI:
springdoc:
swagger-ui:
enabled: false # 禁用 UI
api-docs:
enabled: true # 保留 JSON 生成(供内部系统使用)
启用 OAuth2 支持:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/v3/api-docs/**").hasRole("DEVOPS")
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
Type javax.servlet.http.HttpServletRequest not present
错误日志:java.lang.TypeNotPresentException: Type javax.servlet.http.HttpServletRequest not present
原因:未迁移到 Jakarta EE 包名。
解决步骤:
javax.servlet
为 jakarta.servlet
。mvn dependency:tree | grep javax.servlet
确认无冲突依赖。/doc.html
报 404原因:静态资源被拦截或未正确映射。
解决步骤:
确认使用的是 Knife4j OpenAPI3 的 Spring Boot Starter(knife4j-openapi3-jakarta-spring-boot-starter
),而非旧版 Knife4j
或 SpringFox
。
检查静态资源路径(若自定义了 WebMvcConfigurer):
Knife4j 的静态资源默认位于 classpath:/META-INF/resources/webjars/knife4j-openapi3-ui/
,需确保资源未被拦截或覆盖。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 添加 Knife4j 的静态资源映射
registry.addResourceHandler("/doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
检查 Spring Security
配置是否放行 /doc.html
和 /webjars/**
:
如果项目集成了 Spring Security
,需放行 Knife4j
的静态资源和 API 文档接口。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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
.authorizeHttpRequests(auth -> auth
// 放行 Knife4j 相关路径
.requestMatchers(
"/doc.html",
"/webjars/**",
"/v3/api-docs/**",
"/favicon.ico"
).permitAll()
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.disable()); // 如果不需要 CSRF 防护
return http.build();
}
}
检查项:
确保控制器添加 @Tag
,方法添加 @Operation
。
模型类字段未标注 @Schema
。
包扫描路径未覆盖(通过 @ComponentScan
或 springdoc.packagesToScan
配置)。
排除旧版 Swagger 依赖冲突:
mvn dependency:tree -Dincludes=io.springfox
从 JDK 8 迁移到 JDK 17 不仅是版本的升级,更是技术栈向现代 Java 生态的过渡。通过 依赖替换、注解迁移、包名调整、模块化适配 四步核心操作,可高效完成 Swagger 升级。Knife4j 和 SpringDoc 的组合,不仅解决了兼容性问题,还提供了更强大的 API 文档管理能力。升级后,建议通过自动化测试和持续监控,确保系统的稳定性和可维护性。