Spring Boot 3.0与Shiro集成:解决Jakarta EE迁移中的ClassNotFoundException

Spring Boot 3.0与Shiro集成:解决Jakarta EE迁移中的ClassNotFoundException

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=300000

5.3 使用更高效的密码哈希算法

替换默认的MD5算法为更安全的BCrypt:

@Bean public Hasher hasher() { return new BCryptHasher(); }

记得在项目初期就处理好这些依赖关系,比后期修修补补要省心得多。就像装修房子,水电改造阶段多花点时间,总比入住后拆墙挖地要好。