1

ClassNotFoundException: org.apache.commons.io.IOUtils在以下情况下,我面临着一个奇怪的问题:

我有一个在 Websphere Application Server 8.0 中运行的 Web 应用程序,它使用 commons-io 2.0.1(这个 jar 正确放置在类路径中)。

我正在使用org.apache.commons.io.input.Tailer尾随日志文件并在屏幕上显示其内容。Tailer 实现了 Runnable,因此它在自己的线程中运行。

在应用程序关闭时,我调用 tailer.stop() 来停止线程。然后 Tailer 执行的最后一件事是关闭日志阅读器的 finally 块。

} finally {
    IOUtils.closeQuietly(reader);
}

当它执行该行时,会有一个ClassNotFoundException: org.apache.commons.io.IOUtils.

我做了一些测试,将一些代码添加到 Tailer 类中(并在 jar 中替换它)以确定它是否是 ClassLoader 问题:

1.确定Tailer的资源:

Tailer.class.getClassLoader().getResource("org/apache/commons/io/input/Tailer.class");

输出wsjar:file:/home/myuser/.m2/repository/commons-io/commons-io/2.0.1/commons-io-2.0.1.jar!/org/apache/commons/io/input/Tailer.class 这是正确的罐子

2. 它使用什么类加载器?: Tailer.class.getClassLoader();

com.ibm.ws.classloader.CompoundClassLoader@8d336acd[war:application/myApp.war]这又是正确的。

3.尝试直接从之前的ClassLoader中加载IOUtils: Tailer.class.getClassLoader().loadClass("org.apache.commons.io.IOUtils");

再次出现 ClassNotFoundException

4. 现在,最令人惊讶的是:如果我在 run 方法的开头添加如下所示的 Class.forName(),则该类已正确加载,现在 finally 块中的 IOUtils 调用可以正常工作。怎么来的??

public void run() {
     RandomAccessFile reader = null;
     try {
         Class.forName("org.apache.commons.io.IOUtils");
     }catch(Exception e){
         e.printStackTrace();
     }         
     try {
         ...
         while (run) {
            ...
         }
     } catch (Exception e) {
         listener.handle(e);
     } finally {
         IOUtils.closeQuietly(reader);
     }
 }

 /**
 * Allows the tailer to complete its current loop and return.
 */
 public void stop() {
     this.run = false;
 }

这没有任何意义!它使用来自 commons-io-2.0.1.jar 的 Tailer,但它不能使用位于同一个 jar 中的类。但是如果你在 run() 开始时强制加载类,那么它就不会再失败了。有任何想法吗?

这是整个堆栈跟踪:

Exception in thread "Thread-88" java.lang.NoClassDefFoundError: org.apache.commons.io.IOUtils
    at org.apache.commons.io.input.Tailer.run(Tailer.java:319)
    at java.lang.Thread.run(Thread.java:784)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.io.IOUtils
    at java.net.URLClassLoader.findClass(URLClassLoader.java:434)
    at com.ibm.ws.bootstrap.ExtClassLoader.findClass(ExtClassLoader.java:230)
    at java.lang.ClassLoader.loadClassHelper(ClassLoader.java:703)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:682)
    at com.ibm.ws.bootstrap.ExtClassLoader.loadClass(ExtClassLoader.java:123)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:665)
    at com.ibm.ws.classloader.ProtectionClassLoader.loadClass(ProtectionClassLoader.java:62)
    at com.ibm.ws.classloader.ProtectionClassLoader.loadClass(ProtectionClassLoader.java:58)
    at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:566)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:665)
    at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:566)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:665)
    ... 2 more
4

1 回答 1

0

项目#1 和#2 只是表明常见问题不是原因的数据点。它们不一定表明问题的根源。

项目#3 表明了这个问题。似乎基于该项目,此时类已经从内存中卸载。我查看了一些文档,这些文档显示了 ClassLoader 在什么情况下会抛出异常,但找不到任何异常。所以,我猜类已经被卸载并且应用程序的状态是这样的,warfile本身已经被释放了。进一步的猜测是无关紧要的。

项目#4 确实很好奇。Class.forName 上的文档说“Class 类的实例表示正在运行的 Java 应用程序中的类和接口”。和“类没有公共构造函数。相反,类对象由 Java 虚拟机在加载类时自动构造,并通过调用类加载器中的 defineClass 方法。”。这似乎意味着即使 Class 已从内存中卸载 Class.forName 也应该能够加载它。

您的示例中的第 4 项,在 finally 块中没有 Class.forName 。我认为这只是粘贴代码的产物,而实际上您确实将其放在那里并且引发了错误。

更新:在与 OP 讨论后,以下建议可以完成。在调用tailer.stop() 时,在调用后放置一个thread.sleep() 几秒钟。在从 ClassLoader 卸载应用程序之前,这应该留出足够的时间让 finally 调用完成 (closeQuietly(reader))。

于 2015-03-04T14:37:29.527 回答