I'm having an issue with a micro-service deployed on Tomcat 9 with Java 8. Explaining better, I have a REST service webapp (WAR) deployed on Tomcat 9, the webapp has two parts:
- One is the REST service itself, once it's called, receive some parameters and creates an object from MyPackageDTO (serializable) class with them, so it calls a RabbitMQ queue and publish the object to a queue, this part works very well.
- The second part is conformed by a WebListener which creates a background Thread which connects with the queue on RabbitMQ as a consumer. This part is failing when throws ClassNotFoundException when dequeue an object (MyPackageDTO) queued by the first part.
The thing is, everything is packed on the same JAR. There are the classes of WAR packed on a JAR located on its WEB-INF/lib, they are not unpacked on WEB-INF/classes. (I'm using maven "archiveClasses" to true)
So, after reading a while, I found this post which talk something like this, and I'm thinking the problem is related with the Class-Loader used by the thread I created in the WebListener at application deploy.
It's a little hard to understand this, because everything is inside the same JAR and the same WAR, there's only one Tomcat server (only one JVM).
Is there a possibility to setting the class-loader to the new Thread? Or how could create the background thread on the same class-loader than WebContainer threads?
I'll be doing some test when I get to home, if I find the root of this issue or at least a work-around, I'll post it.
Thanks a lot.
===============
Adding information:
I'm creating the background thread in this way:
@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();
}
}
}
I've added the line where I set the ClassLoader, but I'm getting the same error. ClassNotFoundException.
This is the thread 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;
}
}
Method called disconnectQueue is for disconnect "gracefully" from the queue.
InfoDTO is just a POJO but serializable:
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; }
}
============
This is the stack trace:
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)
Thanks in advance.
Álvaro