Spring Boot 3.0 + Java 17 升级实战:解决 Shiro 的 javax.servlet.Filter 兼容性问题
最近在将项目从 Spring Boot 2.7 升级到 3.0 的过程中,遇到了一个典型的兼容性问题:Shiro 报错java.lang.ClassNotFoundException: javax.servlet.Filter。这个问题看似简单,但背后却隐藏着 Java EE 到 Jakarta EE 的重大变迁。本文将详细记录我的解决过程,并分享一些升级中的实用技巧。
1. 问题现象与初步分析
当我在 Spring Boot 3.0.6 + Java 17 环境下运行集成了 Shiro 的项目时,控制台抛出了以下异常:
java.lang.ClassNotFoundException: javax.servlet.Filter at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)这个错误看起来像是缺少 Servlet API 的依赖,但实际上问题更为复杂。经过排查,我发现:
- 项目确实已经包含了
javax.servlet:javax.servlet-api依赖 - 同样的配置在 Spring Boot 2.7 中运行正常
- 错误仅在升级到 Spring Boot 3.0 后出现
这让我意识到,问题可能与 Spring Boot 3.0 引入的重大变更有关。
2. 深入理解 Jakarta EE 的命名空间变更
Spring Boot 3.0 的一个重大变化是全面转向 Jakarta EE 9+。这意味着:
- 所有
javax.*包名已迁移到jakarta.* - Servlet API 从
javax.servlet变为jakarta.servlet - 这一变更影响了所有依赖 Servlet API 的库
关键时间线:
| 版本 | 变化 | 影响 |
|---|---|---|
| Java EE 8 | 使用javax.*命名空间 | 传统标准 |
| Jakarta EE 9 | 迁移到jakarta.* | 需要适配 |
| Spring Boot 3.0 | 基于 Jakarta EE 9+ | 强制使用新命名空间 |
这种命名空间的变更导致了许多依赖javax.servlet的库在 Spring Boot 3.0 环境下无法正常工作,Shiro 就是其中之一。
3. Shiro 的 Jakarta 适配方案
幸运的是,Shiro 团队已经为 Jakarta EE 提供了适配版本。解决方案的核心是使用 Maven 的classifier标签来指定 Jakarta 适配版本。
3.1 基础配置
首先,我们需要修改 Shiro 的依赖声明:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <classifier>jakarta</classifier> <version>1.11.0</version> </dependency>这个简单的改动就能解决大部分问题,但实际情况往往更复杂。
3.2 处理传递依赖问题
Shiro 的一些子模块可能仍然依赖javax.servlet版本。为了彻底解决问题,我们需要:
- 排除有问题的传递依赖
- 显式引入 Jakarta 适配版本
完整配置如下:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <classifier>jakarta</classifier> <version>1.11.0</version> <!-- 排除仍使用了javax.servlet的依赖 --> <exclusions> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> </exclusion> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> </exclusion> </exclusions> </dependency> <!-- 引入适配jakarta的依赖包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <classifier>jakarta</classifier> <version>1.11.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <classifier>jakarta</classifier> <version>1.11.0</version> <exclusions> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> </exclusion> </exclusions> </dependency>3.3 Gradle 配置方案
对于使用 Gradle 的项目,可以这样配置:
implementation('org.apache.shiro:shiro-spring:1.11.0:jakarta') { exclude group: 'org.apache.shiro', module: 'shiro-core' exclude group: 'org.apache.shiro', module: 'shiro-web' } implementation 'org.apache.shiro:shiro-core:1.11.0:jakarta' implementation('org.apache.shiro:shiro-web:1.11.0:jakarta') { exclude group: 'org.apache.shiro', module: 'shiro-core' }4. 验证与测试
配置完成后,建议进行以下验证步骤:
- 依赖树检查:运行
mvn dependency:tree或gradle dependencies,确认没有javax.servlet相关的依赖 - 启动测试:确保应用能正常启动,没有
ClassNotFoundException - 功能测试:验证 Shiro 的核心功能(认证、授权等)是否正常工作
提示:如果仍然遇到问题,可以尝试清理 Maven/Gradle 缓存,有时旧的依赖会被缓存导致问题。
5. 升级中的其他常见问题
除了 Shiro 的问题,升级到 Spring Boot 3.0 + Java 17 还可能会遇到:
- Hibernate 兼容性:确保使用 Hibernate 6.0+ 版本
- 第三方库适配:检查其他依赖是否有 Jakarta EE 适配版本
- 构建工具配置:Maven/Gradle 可能需要更新插件版本
推荐升级检查清单:
- 确认所有核心依赖都有 Jakarta EE 适配版本
- 检查构建工具和插件的兼容性
- 准备回滚方案,以防遇到无法解决的问题
- 分阶段升级,先解决编译问题,再处理运行时问题
6. 深入理解 Maven classifier 机制
这次解决问题的关键是通过classifier指定了 Jakarta 适配版本。classifier是 Maven 依赖管理中的一个强大但常被忽视的特性。
classifier 的常见用途:
- 指定不同构建版本(如
jakarta、javax) - 区分不同平台(如
linux-x86_64、windows-x86_64) - 提供附加功能版本(如
sources、javadoc)
使用建议:
- 优先查看官方文档,了解是否有特定 classifier 可用
- 在解决兼容性问题时,classifier 往往是简单有效的方案
- 注意 classifier 可能会影响依赖解析,需要全面测试
7. 长期维护建议
随着 Jakarta EE 的普及,未来可能会有更多库需要类似处理。建议:
- 保持依赖更新:定期检查依赖的 Jakarta 适配状态
- 建立兼容性矩阵:记录各库的兼容版本信息
- 自动化测试:增加版本兼容性相关的测试用例
在实际项目中,我们建立了一个简单的兼容性检查工具,可以在构建时自动检测潜在的javax.*引用,这大大减少了升级过程中的问题排查时间。