为什么我在 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 有两个部分:
- 一个是REST服务本身,一旦被调用,接收一些
参数并从 MyPackageDTO 创建一个对象(可序列化)
class 与他们一起,所以它调用 RabbitMQ 队列并发布对象
到一个队列,这部分工作得很好。
- 第二部分由创建一个
与 RabbitMQ 上的队列连接的后台线程作为
消费者。这部分在抛出 ClassNotFoundException 时失败
使第一部分排队的对象 (MyPackageDTO) 出队。
事实是,所有内容都打包在同一个 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 路径(不推荐)。
我在 Tomcat 9 和 Java 8 上部署的微服务有问题。 更好地解释一下,我在 Tomcat 9 上部署了一个 REST 服务 webapp (WAR),该 webapp 有两个部分:
- 一个是REST服务本身,一旦被调用,接收一些 参数并从 MyPackageDTO 创建一个对象(可序列化) class 与他们一起,所以它调用 RabbitMQ 队列并发布对象 到一个队列,这部分工作得很好。
- 第二部分由创建一个 与 RabbitMQ 上的队列连接的后台线程作为 消费者。这部分在抛出 ClassNotFoundException 时失败 使第一部分排队的对象 (MyPackageDTO) 出队。
事实是,所有内容都打包在同一个 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 路径(不推荐)。