我的 Tomcat 服务器上有三个 Web 应用程序。我使用 ActiveMQ 在它们之间发送消息。这已经有效,但现在我正在尝试从 5.14 更新到 5.18,并且遇到了类加载问题。
为了节省 WAR 大小,我在全局 tomcat /lib 目录中放置了 activemq-all jar,这在我运行 5.14 时有效。
但是现在在 5.18 中发生的情况是,我在所有 war lib 文件中都有一个 jar 文件中的类,但出现了 ClassNotFound 错误:
原因:java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper 位于 org.apache.activemq.broker.jmx.PersistenceAdapterView。(PersistenceAdapterView.java:31) 在 org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter.doStart(KahaDBPersistenceAdapter.java:235)
我不擅长类加载,但由于某种原因,当 ActiveMQ 在我的一个 Web 应用程序中设置持久性时,该类似乎是从“中央”lib 文件夹加载的,这意味着它不能使用我的战争中存在什么阶级?
我不明白,因为加载试图加载ObjectMapper的activeMQ类的类加载器必须是我的应用程序类加载器,那么为什么它找不到ObjectMapper类?
所以,我的问题:
为什么我的应用程序不能使用我的 war 和 tomcat/lib 中的所有类?难道不是同一个类加载器,因为是我的应用程序触发了加载?
我是用 activeMQ 类发送每场战争的唯一解决方案吗?它有效,我尝试过,但是 WAR 文件变得更大......
非常感谢指点。
您的情况涉及 Java 类加载的一些基本方面,特别是在像支持多个应用程序的 servlet 容器 (Tomcat) 这样的复杂环境中。让我们一次解决一个问题:
在 Tomcat 中,通常有三个类加载器在起作用:
当您将 JAR 放入 TOMCAT_HOME/lib 时,它会由系统类加载器加载并在所有应用程序之间共享。这对于不依赖于 webapp 类加载器中的类的库来说效果很好。但是,如果这些库需要使用 webapp 类加载器加载的类,则可能会出现
ClassNotFoundException
或 NoClassDefFoundError
等问题。
在您的例子中,
activemq-all.jar
位于Tomcat全局lib目录中。这意味着它是由系统类加载器加载的。但是,Jackson 库中的 ObjectMapper
类可能位于您的 web 应用程序的 WEB-INF/lib
中,由 web 应用程序类加载器加载。 Java 类加载器使用委托模型,其中类加载器将要求其父级加载一个类,然后再尝试加载它自己。但是较高级别的类加载器(如系统类加载器)无法看到较低级别的类加载器(如 webapp 类加载器)加载的类。
因此,当 ActiveMQ 尝试加载和使用
ObjectMapper
时,它无法找到它,因为系统类加载器无法查看 ObjectMapper
所在的 webapp 类加载器。
解决此问题的一种方法是确保 ActiveMQ 所需的所有依赖项与 ActiveMQ 本身处于相同的类加载级别。您可以将 Jackson 库移至 TOMCAT_HOME/lib 目录。这应该可以解决类加载问题,因为 ActiveMQ 和 Jackson 将由同一个类加载器加载。
正如您所指出的,另一种解决方案是将
activemq-all.jar
及其依赖项捆绑在每个应用程序的 WEB-INF/lib
目录中。这种方法之所以有效,是因为 ActiveMQ 所需的所有内容都是由同一个类加载器加载的。但是,这会增加每个 WAR 文件的大小并在应用程序之间重复库。
更复杂的解决方案涉及在 Tomcat 中配置自定义类加载器,这可以帮助更精细地管理类可见性。这是相当先进的,通常仅在标准解决方案不够时才推荐。
我建议从第一个解决方案开始:将所有必需的库放在 TOMCAT_HOME/lib 目录中。这通常是解决此类问题的最简单、最有效的方法,而无需对每个应用程序进行大量修改。它确保系统类加载器可以使用所有必需的类并简化维护。
确保在生产环境中部署此配置之前,在临时环境中对其进行测试,以验证所有组件是否正确交互并且不会引入新问题。