Solve the problem of JMSException: ClassNotFoundException when consuming ActiveMQ queue

The requirement development was completed and tested. During the test, it was found that when the listener of activemq was consuming queue messages, the program caught an exception. See the log below.
The exception information is obvious, the class com.cn.yft.ora.entity.TAccReviewRecord does not exist. Looking at this ClassNotFoundException exception, I thought that it had also appeared when redis was used to store hot data.

2021-01-12 20:14:14,734 [ERROR] [ListenCommonSyncMQService-1] [com.yft.busi.mq.CommonSyncMQService:40] [Queue Send] Synchronous landing service company recharge information failed to be sent
javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: com.cn.yft.ora.entity.TAccReviewRecord
    at org.apache.activemq.util.JMSExceptionSupport.create(JMSExceptionSupport.java:36)
    at org.apache.activemq.command.ActiveMQObjectMessage.getObject(ActiveMQObjectMessage.java:193)
    at com.yft.busi.mq.CommonSyncMQService.onMessage(CommonSyncMQService.java:31)
    at org.springframework.jms.listener.adapter.MessageListenerAdapter.onMessage(MessageListenerAdapter.java:341)
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:537)
    at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:497)
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:468)
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:325)
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:263)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1102)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1094)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:991)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassNotFoundException: com.cn.yft.ora.entity.TAccReviewRecord
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1928)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1771)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at org.apache.activemq.util.ClassLoadingAwareObjectInputStream.load(ClassLoadingAwareObjectInputStream.java:95)
    at org.apache.activemq.util.ClassLoadingAwareObjectInputStream.resolveClass(ClassLoadingAwareObjectInputStream.java:43)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1868)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at org.apache.activemq.command.ActiveMQObjectMessage.getObject(ActiveMQObjectMessage.java:191)
    ... 11 more

Why does such an exception occur? This usually occurs when the system entity class is adjusted, or when the system program structure changes.

Let’s talk about this case first, the reason is exactly the latter. It turns out that the message producer and consumer of the specified ActiveMQ queue are in the same application service. The message that the producer puts into the queue is a TAccReviewRecord object. Then, obviously, after the consumer MessageListener in the same application listens to the message, the data carried by the message is still the TAccReviewRecord object in the program.

The reason why the ClassNotFoundException mentioned at the beginning occurred is because when developing new requirements, another application service also needs to produce messages for the specified ActiveMQ queue. The package of TAccReviewRecord of another service is different from the package of TAccReviewRecord of the original service, which resulted in the above ClassNotFoundException exception.

Let’s talk about the exception encountered before when redis accessed hot data. The reason is the former. The situation is that the redis.set(String key, Object value) method is called to set the cache, which is the VO object directly specified by value. Later, the program adjusted the directory structure, and the package of the VO object changed. Then, the program is re-published to the server, and when the old uninvalidated cache value of the key is type-converted, this ClassNotFoundException occurs.

[Solution]
In order to avoid similar problems, after evaluation, a strategy of sacrificing performance to ensure usability was adopted, that is, instead of directly storing the data object, the data object was serialized into a json string, and deserialization was also performed when reading.

[Attachment] Code where ClassNotFoundException occurred before transformation
activemq producer code (Application A)

public int sendToQueue(String conFactory,String userName, String pwd, String tCPUrl,
        final Object message, String qMName) {
    . . .
    Message testmessage = session.createObjectMessage((Serializable) message);
    //Send message to destination
    producer.send(testmessage);
    session.commit();
    . . .
}

activemq consumer code (Application B) —-The exception is thrown by objectMessage.getObject() on line 10

 1 @Component("commonSyncMQService")
 2 public class CommonSyncMQService implements MessageListener {
 3 private final static Logger logger = LoggerFactory.getLogger(CommonSyncMQService.class);
 4
 5 @Override
 6 public void onMessage(Message message) {
 7 if (message instanceof ObjectMessage) {
 8 final ObjectMessage objectMessage = (ObjectMessage) message;
 9 try {
10 logger.info("Synchronization landing service company recharge information interface message queue receiving parameters [{}]", objectMessage.getObject());
11 TAccReviewRecord reviewRecord = (TAccReviewRecord) objectMessage.getObject();
12. . .
13 } catch (final Exception e) {
14 logger.error("[Queue sending] Synchronous landing service company recharge information failed to send", e);
15}
16}
17}
18}

[Attachment] Code that was transformed and replaced with serialized strings

After activemq producer (application A) stores the json string, activemq consumer (application B) transforms:

 1 @Component("commonSyncMQService")
 2 public class CommonSyncMQService implements MessageListener {
 3 private final static Logger logger = LoggerFactory.getLogger(CommonSyncMQService.class);
 4 @Autowired
 5 private SyncAccReviewRecordService syncAccReviewRecordService;
 6
 7 @Override
 8 public void onMessage(Message message) {
 9 if (message instanceof ObjectMessage) {
10 final ObjectMessage objectMessage = (ObjectMessage) message;
11 try {
12 logger.info("Synchronization landing service company recharge information interface message queue receiving parameters [{}]", objectMessage.getObject());
13 TAccReviewRecord reviewRecord = JSONObject.parseObject(String.valueOf(objectMessage.getObject()),TAccReviewRecord.class);
14. . .
15 } catch (final Exception e) {
16 logger.error("[Queue sending] Synchronous landing service company recharge information failed to send", e);
17}
18}
19}
20}