迁移至Jakarta Mail时遇StreamProvider类型不匹配问题求助
我正在将一个基于Java 1.8、Spring 3、SpringMVC、Spring Security且部署在Tomcat 9上的非Spring Boot应用,迁移至Java 17、Spring 6、Spring Security 6并部署在Tomcat 11环境。目前已完成Tomcat9+Spring5+Security5+Java11的迁移,但升级到目标环境后,在从javax.mail迁移至jakarta.mail时遇到问题:先解决了“找不到jakarta.mail.util.StreamProvider实现”的问题,现在又出现错误jakarta.mail.util.StreamProvider: org.eclipse.angus.mail.util.MailStreamProvider not a subtype。
相关配置文件
context.xml
<Context> <Resource auth="Container" mail.debug="true" mail.from="email" mail.smtp.host="email" mail.smtp.port="25" mail.user="user" name="mail/Session" type="jakarta.mail.Session" /> </Context>
pom.xml(关键依赖部分)
<dependency> <groupId>jakarta.mail</groupId> <artifactId>jakarta.mail-api</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>org.eclipse.angus</groupId> <artifactId>angus-mail</artifactId> <version>2.0.3</version> </dependency> <dependency> <groupId>jakarta.activation</groupId> <artifactId>jakarta.activation-api</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>org.eclipse.angus</groupId> <artifactId>angus-activation</artifactId> <version>2.0.2</version> <scope>runtime</scope> </dependency>
applicationContext.xml
<bean id="javaMailSession" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/mail/Session"/> </bean> <bean id="javaMailSenderImpl" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="session" ref="javaMailSession"/> </bean>
堆栈跟踪信息
Caused by: java.util.ServiceConfigurationError: jakarta.mail.util.StreamProvider: org.eclipse.angus.mail.util.MailStreamProvider not a subtype at java.util.ServiceLoader.fail(ServiceLoader.java:593) ~[?:?] at java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1244) ~[?:?] at java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273) ~[?:?] at java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309) ~[?:?] at java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393) ~[?:?] at jakarta.mail.util.FactoryFinder.factoryFromServiceLoader(FactoryFinder.java:161) ~[jakarta.mail-api-2.1.3.jar:?] at jakarta.mail.util.FactoryFinder.find(FactoryFinder.java:75) ~[jakarta.mail-api-2.1.3.jar:?] at jakarta.mail.util.FactoryFinder.find(FactoryFinder.java:40) ~[jakarta.mail-api-2.1.3.jar:?] at jakarta.mail.util.StreamProvider.provider(StreamProvider.java:199) ~[jakarta.mail-api-2.1.3.jar:?] at jakarta.mail.Session.<init>(Session.java:257) ~[jakarta.mail-api-2.1.3.jar:?] at jakarta.mail.Session.getInstance(Session.java:310) ~[jakarta.mail-api-2.1.3.jar:?] at org.apache.naming.factory.MailSessionFactory.getObjectInstance(MailSessionFactory.java:125) ~[catalina.jar:11.0.1] at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:88) ~[catalina.jar:11.0.1] at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:342) ~[?:?] at org.apache.naming.NamingContext.lookup(NamingContext.java:544) ~[catalina.jar:11.0.1] at org.apache.naming.NamingContext.lookup(NamingContext.java:148) ~[catalina.jar:11.0.1] at org.apache.naming.NamingContext.lookup(NamingContext.java:530) ~[catalina.jar:11.0.1] at org.apache.naming.NamingContext.lookup(NamingContext.java:148) ~[catalina.jar:11.0.1] at org.apache.naming.NamingContext.lookup(NamingContext.java:530) ~[catalina.jar:11.0.1] at org.apache.naming.NamingContext.lookup(NamingContext.java:148) ~[catalina.jar:11.0.1] at org.apache.naming.NamingContext.lookup(NamingContext.java:530) ~[catalina.jar:11.0.1] at org.apache.naming.NamingContext.lookup(NamingContext.java:155) ~[catalina.jar:11.0.1] at org.apache.naming.SelectorContext.lookup(SelectorContext.java:144) ~[catalina.jar:11.0.1] at javax.naming.InitialContext.lookup(InitialContext.java:409) ~[?:?] at org.springframework.jndi.JndiTemplate.lambda$lookup$0(JndiTemplate.java:157) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:92) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:157) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:114) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.jndi.JndiObjectFactoryBean.lookupWithFallback(JndiObjectFactoryBean.java:239) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:225) ~[spring-context-6.2.0.jar:6.2.0] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1849) ~[spring-beans-6.2.0.jar:6.2.0] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1798) ~[spring-beans-6.2.0.jar:6.2.0] ... 53 more
解决方案
这个错误的核心是类加载器冲突:Tomcat的系统类加载器加载了Jakarta Mail API,而应用类加载器加载了Angus Mail的实现类,导致两者的StreamProvider接口不是同一个类实例。
1. 排除Tomcat自带的Jakarta Mail相关依赖
修改Tomcat的catalina.properties文件,添加以下配置,让Tomcat跳过扫描Jakarta Mail和Angus Mail的JAR包,避免系统类加载器加载它们:
tomcat.util.scan.StandardJarScanFilter.jarsToSkip=jakarta.mail-api*.jar,angus-mail*.jar,jakarta.activation-api*.jar,angus-activation*.jar
2. 统一依赖版本并清理重复依赖
运行mvn dependency:tree检查是否有其他依赖引入了旧版本的javax.mail或不兼容的Jakarta Mail版本,通过<exclusions>标签排除这些重复依赖。同时确保angus-mail与jakarta.mail-api版本兼容(当前使用的2.0.3与2.1.3是匹配的,可保留)。
3. 改用Spring直接配置Mail Session(可选)
如果上述步骤无效,可以放弃Tomcat JNDI创建Mail Session的方式,直接在Spring中配置:
<bean id="javaMailSenderImpl" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="host" value="你的SMTP主机地址"/> <property name="port" value="25"/> <property name="username" value="user"/> <property name="from" value="email"/> <property name="javaMailProperties"> <props> <prop key="mail.debug">true</prop> </props> </property> </bean>
这种方式由应用类加载器完全控制Mail相关类的加载,彻底避免类加载器冲突。
内容的提问来源于stack exchange,提问作者Tyler Jaacks

