Tomcat升级ActiveMQ至5.18引发类加载问题求助
问题背景
Tomcat部署3个Web应用,通过ActiveMQ实现跨应用消息通信。此前用5.14版本时,将activemq-all.jar放在Tomcat全局lib目录以减小WAR体积,运行正常。升级到5.18版本后,出现NoClassDefFoundError,找不到WAR包lib中存在的com.fasterxml.jackson.databind.ObjectMapper类,报错栈如下:
Caused by: java.lang.NoClassDefFoundError:
com/fasterxml/jackson/databind/ObjectMapper at
org.apache.activemq.broker.jmx.PersistenceAdapterView.(PersistenceAdapterView.java:31)
at
org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter.doStart(KahaDBPersistenceAdapter.java:235)
1. 为何应用无法同时使用WAR包和tomcat/lib中的所有类?触发加载的是应用,类加载器难道不是同一个吗?
Tomcat的类加载机制是双亲委派模型的变种,不同层级的类由不同类加载器负责:
- Tomcat全局
lib目录下的类由Common类加载器加载,它是所有Web应用类加载器的父加载器。 - WAR包内部
WEB-INF/lib的类由当前Web应用的类加载器加载,属于Common类加载器的子加载器。
双亲委派规则的核心限制是:父加载器无法访问子加载器加载的类。
你遇到的问题是:ActiveMQ 5.18的PersistenceAdapterView类由Common类加载器加载(因为activemq-all.jar在全局lib),它初始化时需要依赖ObjectMapper,但这个类只存在于WAR包的WEB-INF/lib中,由Web应用类加载器加载。此时Common类加载器会从自己的加载路径(全局lib、JRE核心类)找ObjectMapper,找不到就直接报错,不会去子加载器的路径里找——哪怕触发加载的是应用,核心依赖类的加载者是父类加载器,它看不到子加载器的类。
2. 唯一解决方案是每个WAR包都包含ActiveMQ类吗?
不是,有几个更优的替代方案:
- 方案一:将Jackson依赖也放到全局lib
把jackson-databind.jar、jackson-core.jar、jackson-annotations.jar(对应ActiveMQ 5.18依赖的版本)复制到Tomcat的lib目录,让Common类加载器能加载到ObjectMapper,和ActiveMQ类处于同一加载层级,就能解决依赖问题。 - 方案二:使用Tomcat共享类加载器
在Tomcat目录下创建shared/lib文件夹,把activemq-all.jar和Jackson相关jar都放进去,然后修改conf/catalina.properties里的shared.loader配置,添加${catalina.base}/shared/lib/*.jar。这样所有Web应用都能共享这些类,既不会污染全局lib,也能避免WAR包增大。 - 方案三:统一管理依赖版本
确保所有Web应用使用的Jackson版本和ActiveMQ 5.18依赖的版本一致,避免版本冲突导致的间接类加载问题。
内容的提问来源于stack exchange,提问作者Mathias

