0

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

Community
  • 1
  • 1
Alvaro
  • 65
  • 7
  • The web listener probably has only the base Tomcat classpath since it's created long before your application gets loaded. You probably need to put the DTO classes into a jar that is deployed into Tomcat itself. – Jim Garrison Apr 19 '16 at 20:11
  • Can you show the exception stack trace? How are you creating the background thread? Have you verified that `Thread.currentThread().getContextClassLoader()` on the background thread is the right value as suggested by the other post? It should be inherited if you created a new thread, but without more detail, it's unclear if that's the problem. If so, you can use the `setContextClassLoader` to set the context class loader. – Brett Kail Apr 20 '16 at 14:04
  • Hi @BrettKail, I've added more info to my question. Setting context classloader didn't worked.. As a work-around, I've implemented serialization as a JSON string, and at deserialization just parse the JSON object to get the data.. Regards.. – Alvaro Apr 20 '16 at 20:36
  • Can you show the exception stack trace? – Brett Kail Apr 20 '16 at 23:10
  • Hi @BrettKail, thanks for comment. I've added the stack trace.. – Alvaro Apr 21 '16 at 12:50
  • Where is the org.apache.commons.lang.SerializationUtils class packaged? It appears to use ObjectInputStream, which means the SerializationUtils class must have visibility to the InfoDTO class (i.e., both packaged in WEB-INF/lib or something). – Brett Kail Apr 21 '16 at 14:24
  • @BrettKail, you got it man.. That was the problem.. I had commons-lang-2.3.jar on TOMCAT/lib, so, in that space of class loading there's no reference or visibility to InfoDTO class.. I've included that JAR on the WAR WEB-INF/lib, and now it works very well.. Post it as a solution.. :) – Alvaro Apr 21 '16 at 15:19
  • Added an answer. Glad it helped. – Brett Kail Apr 21 '16 at 17:31

1 Answers1

1

The SerializationUtils class uses ObjectInputStream, which requires that SerializationUtils must have visibility to the InfoDTO class it is deserializing. You must either move the SerializationUtils class into the WAR alongside the InfoDTO class (recommended) or move the InfoDTD class into the same classpath as SerializationUtils (not recommended).

Brett Kail
  • 33,593
  • 2
  • 85
  • 90