1. 当Spring Boot 3.0遇上Shiro:ClassNotFoundException背后的故事
最近在升级Spring Boot 3.0项目时,我发现一个有趣的现象:原本运行良好的Shiro权限框架突然罢工了,控制台赫然抛出ClassNotFoundException: javax.servlet.Filter。这让我想起小时候玩积木,明明是按照图纸搭建的,却因为少了一个关键零件导致整个建筑垮掉。Java生态中的依赖管理也是如此微妙。
问题的根源要从Jakarta EE的命名空间迁移说起。Spring Boot 3.0基于Servlet 5.0规范,而Servlet 5.0已经将javax.servlet包全面迁移到jakarta.servlet。这就好比小区换了新门牌号,但快递员(Shiro)还拿着旧地址送货,自然找不到目的地。更复杂的是,Shiro的部分子模块仍然依赖旧版javax包,这就形成了"新旧地址混用"的尴尬局面。
我遇到这个错误时的第一反应是检查Maven依赖树:
mvn dependency:tree -Dincludes=javax.servlet结果发现shiro-spring间接引入了javax.servlet-api,而Spring Boot 3.0运行时环境提供的却是jakarta.servlet-api。这种"一个系统两套标准"的情况,就像同时使用公制和英制单位造飞机,不出问题才怪。
2. 精准手术:Maven依赖的排除与引入
解决这个问题的关键在于给Maven依赖做"精准手术"。我们需要完成三个关键操作:
2.1 使用classifier选择适配版本
Shiro官方很贴心地为Jakarta EE准备了特殊版本,就像餐厅为素食者准备了特别菜单。通过<classifier>标签可以指定使用jakarta适配包:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.11.0</version> <classifier>jakarta</classifier> </dependency>但事情没那么简单,就像点了个套餐发现里面还有你不吃的配菜。
2.2 排除顽固的javax依赖
即使使用了jakarta分类器,某些Shiro模块仍然会偷偷引入javax包。这时候就需要<exclusions>标签来精确"排雷":
<exclusions> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> </exclusion> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> </exclusion> </exclusions>这就像在点外卖时特别备注"不要香菜",确保送到手的都是你能吃的。
2.3 引入对应的jakarta版本
排除旧版本后,还需要显式引入适配jakarta的新版本:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.11.0</version> <classifier>jakarta</classifier> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.11.0</version> <classifier>jakarta</classifier> </dependency>这个过程就像给汽车换零件,不能简单地拆掉旧零件就完事,还得装上适配的新零件才能继续行驶。
3. 版本兼容性检查:避免隐藏的坑
解决了主要依赖问题后,还需要注意一些潜在的版本陷阱:
3.1 检查传递性依赖
即使我们处理了Shiro的直接依赖,它的间接依赖也可能带来问题。建议使用以下命令全面检查:
mvn dependency:tree -Dverbose -Dincludes=javax.servlet,jakarta.servlet我曾经遇到过spring-boot-starter-web引入的Tomcat版本与Shiro不兼容的情况,这时候可能需要通过<properties>统一版本号:
<properties> <tomcat.version>10.1.7</tomcat.version> </properties>3.2 测试关键功能点
配置完成后,务必测试以下核心功能:
- 登录认证流程
- 权限注解(@RequiresPermissions)
- 会话管理
- 记住我功能
特别是涉及过滤器链的配置,建议编写单元测试验证:
@Test void testShiroFilterChain() { Map<String, String> filterChain = shiroFilterFactoryBean.getFilterChainDefinitionMap(); assertThat(filterChain).containsKey("/login"); }4. 实战经验:我踩过的那些坑
在实际项目中,我还遇到过几个不太明显但很致命的问题:
4.1 缓存框架的兼容性
Shiro默认使用Ehcache作为缓存实现,但Ehcache 3.x也需要jakarta适配。如果使用Redis等外部缓存,记得检查客户端是否兼容Jakarta EE:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.11.0</version> <classifier>jakarta</classifier> <exclusions> <exclusion> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> </exclusion> </exclusions> </dependency>4.2 与Spring Security的冲突
如果项目同时使用了Spring Security和Shiro(虽然不推荐),需要特别注意自动配置的排除:
@SpringBootApplication(exclude = { SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class })4.3 开发工具的热部署问题
使用DevTools热部署时,可能会遇到类加载器问题导致Shiro失效。可以在application.properties中添加:
spring.devtools.restart.enabled=false或者在Shiro配置中显式设置类加载器:
@Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setClassLoader(getClass().getClassLoader()); return manager; }5. 升级后的性能优化建议
成功解决问题后,不妨趁热打铁做些性能优化:
5.1 启用Shiro的注解支持
确保在配置类上添加这些注解以启用完整功能:
@Configuration @EnableWebSecurity @EnableAspectJAutoProxy(proxyTargetClass = true) public class ShiroConfig { // 配置内容 }5.2 调整会话超时设置
根据业务需求优化会话参数:
# 会话超时时间(毫秒) shiro.session.globalSessionTimeout=1800000 # 会话验证间隔 shiro.session.validationInterval=3000005.3 使用更高效的密码哈希算法
替换默认的MD5算法为更安全的BCrypt:
@Bean public Hasher hasher() { return new BCryptHasher(); }记得在项目初期就处理好这些依赖关系,比后期修修补补要省心得多。就像装修房子,水电改造阶段多花点时间,总比入住后拆墙挖地要好。