为什么我在 Tomcat 8 上创建的后台线程中会收到 ClassNotFoundException?

Why could I receive a ClassNotFoundException in a background thread created by me on Tomcat 8?

我在 Tomcat 9 和 Java 8 上部署的微服务有问题。 更好地解释一下,我在 Tomcat 9 上部署了一个 REST 服务 webapp (WAR),该 webapp 有两个部分:

事实是,所有内容都打包在同一个 JAR 中。 WAR 的 classes 打包在位于其 WEB-INF/lib 的 JAR 中,它们未在 WEB-INF/classes 上解压。 (我正在使用 maven "archiveClasses" 为真)

所以,看了一会儿之后,我发现 this post 说的是这样的,我认为这个问题与我创建的线程使用的 Class-Loader 有关部署应用程序时的 WebListener。

这有点难以理解,因为所有东西都在同一个 JAR 和同一个 WAR 中,只有一个 Tomcat 服务器(只有一个 JVM)。

是否可以将 class-loader 设置为新线程?或者如何在与 WebContainer 线程相同的 class-loader 上创建后台线程?

我回家后会做一些测试,如果我找到这个问题的根源或至少有解决方法,我会 post 它。

非常感谢。

===============

添加信息:

我是这样创建后台线程的:

@WebListener
public class MainWebListener implements ServletContextListener {

    private Thread threadQueueListener;
    private QueueThreadListener queueListener;

    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        try {
            queueListener.disconnectQueue();
            Thread.sleep(QueueThreadListener.QUEUE_DELIVERY_TIMEOUT + 20);
        } catch (Throwable tt) {
            tt.printStackTrace();
        }
    }

    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        try {
            queueListener = new QueueThreadListener();
            threadQueueListener = new Thread(queueListener);
            threadQueueListener.setContextClassLoader(Thread.currentThread().getContextClassLoader());
            threadQueueListener.start();
        } catch (Throwable tt) {
            tt.printStackTrace();
        }
    }
}

我在设置 ClassLoader 的地方添加了行,但我遇到了同样的错误。 ClassNotFoundException。

这是话题 class:

public class QueueThreadListener implements Runnable {
    
    public static final String QUEUE_NAME = "MQ_TEST_REST_QUEUE";
    public static final long QUEUE_DELIVERY_TIMEOUT = 2000;
    private boolean toBeContinued = true;
    private Connection connection;
    
    @Override
    public void run() {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        try {
            connection = factory.newConnection();
            Channel channel = connection.createChannel();
            QueueingConsumer consumer = new QueueingConsumer(channel);
            channel.basicConsume(QUEUE_NAME, true, consumer);
            while (toBeContinued) {
                try {
                    QueueingConsumer.Delivery delivery = consumer.nextDelivery(QUEUE_DELIVERY_TIMEOUT);
                    if (delivery != null) {
                        InfoDTO to = (InfoDTO)SerializationUtils.deserialize(delivery.getBody());
                        System.out.println("--> Package received.. [Name: " + to.getName() + " | Age: " + to.getAge()+"]");
                    }
                } catch (Exception ee) {
                    ee.printStackTrace();
                }
            }
            connection.close();
        } catch (TimeoutException t) {
            t.printStackTrace();
        } catch (IOException x) {
            x.printStackTrace();
        } catch (ShutdownSignalException e) {
            e.printStackTrace();
        } catch (ConsumerCancelledException e) {
            e.printStackTrace();
        }
    }
    
    public void disconnectQueue() {
        toBeContinued = false;
    }
}

名为 disconnectQueue 的方法用于从队列中“正常”断开连接。

InfoDTO 只是一个 POJO 但可序列化:

public class InfoDTO implements Serializable {

    private static final long serialVersionUID = 5274775183934376052L;
    private String name;
    private int age;
    
    public InfoDTO(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

============

这是堆栈跟踪:

org.apache.commons.lang.SerializationException: java.lang.ClassNotFoundException: com.guambo.test.rest.rabbit.dto.InfoDTO
        at org.apache.commons.lang.SerializationUtils.deserialize(SerializationUtils.java:165)
        at org.apache.commons.lang.SerializationUtils.deserialize(SerializationUtils.java:192)
        at com.guambo.test.rest.rabbit.listener.QueueThreadListener.run(QueueThreadListener.java:58)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassNotFoundException: com.guambo.test.rest.rabbit.dto.InfoDTO
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:348)
        at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:626)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
        at org.apache.commons.lang.SerializationUtils.deserialize(SerializationUtils.java:162)

提前致谢。

阿尔瓦罗

SerializationUtils class 使用 ObjectInputStream,这要求 SerializationUtils 必须对其反序列化的 InfoDTO class 具有可见性。您必须将 SerializationUtils class 移动到 WAR 旁边的 InfoDTO class (推荐)或将 InfoDTD class 移动到与 SerializationUtils 相同的 class 路径(不推荐)。