【熟能生巧】JMS吃消息小组件
在开发或测试中,我们有时候会遇到Queue/Topic被多余的消息堵塞的问题,这时候就需要把队列里的消息清理掉,然后新的消息才能进来。
有些JMS Provider有GUI可以查看,修改队列中的消息。有些则没有提供这样的功能。这就需要我们自己手动写程序做这件事了。
原理很简单,就是open a connection,然后正常查看Queue/Topic里面的消息,这些就被“吃”掉了。
核心部分是messageListenerContainer.start()
,然后onMessage()
就会收到信息。
Code
PART 1
import javax.jms.Message;
import javax.jms.MessageListener;
public class JmsHelperMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
if (message != null) {
JmsHelper.printInfo("Consumed message: " + message.toString());
} else {
JmsHelper.printInfo("Message is null");
}
return;
}
}
PART 2
import javax.jms.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.BrowserCallback;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import java.util.Date;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class JmsHelper {
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private Queue datasetQueueCUS;
@Autowired
private Topic datasetTopicCUS;
int CONSUME_MSG_TIME_IN_SECOND = 5;
public static void printInfo(String m) {
System.out.println(m);
}
private void simpleSendToQueue(Queue queue, String msg) {
this.jmsTemplate.send(queue, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
MapMessage mapMessage = session.createMapMessage();
mapMessage.setJMSCorrelationID( UUID.randomUUID().toString() ) ;
mapMessage.setStringProperty( "DATASET_ID", msg); ;
mapMessage.setStringProperty( "NAME", msg) ;
mapMessage.setStringProperty( "VALUE_DATE", "2019") ;
mapMessage.setStringProperty( "STATUS", "T") ;
return mapMessage;
}
});
printInfo("Sent one msg, DESTINATION: " + queue + ", NAME: " + msg + ".");
}
private void simpleSendToTopic(Topic topic, String msg) {
this.jmsTemplate.send(topic, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
MapMessage mapMessage = session.createMapMessage();
mapMessage.setJMSCorrelationID( UUID.randomUUID().toString() ) ;
mapMessage.setIntProperty( "DATASET_ID", 376480); ;
mapMessage.setStringProperty( "STATUS", "P") ;
return mapMessage;
}
});
printInfo("Sent one msg, DESTINATION: " + topic + ", NAME: " + msg + ".");
}
private void simpleBrowseQueue(Queue queue) {
this.jmsTemplate.browse(queue, new BrowserCallback<Object>() {
@Override
public Object doInJms(Session session, QueueBrowser queueBrowser) throws JMSException {
printInfo(queueBrowser.getQueue().toString());
Enumeration e = queueBrowser.getEnumeration();
int count = 0;
if (e.hasMoreElements()) {
printInfo(formatMsgWithDes((Message) e.nextElement()));
count++;
}
while (e.hasMoreElements()) {
printInfo(formatMsgWithoutDes((Message) e.nextElement()));
count++;
}
printInfo("INFO - message count: " + count);
queueBrowser.close();
session.close();
return null;
}
});
}
private void consumeAllMessagesInQueue(ApplicationContext context, Queue queue) {
DefaultMessageListenerContainer messageListenerContainer = (DefaultMessageListenerContainer) context.getBean("jmsContainer");
messageListenerContainer.setDestination(queue);
messageListenerContainer.initialize();
if (!messageListenerContainer.isRunning())
messageListenerContainer.start();
try {
printInfo("start consuming "+ messageListenerContainer.getDestination() +" for " + CONSUME_MSG_TIME_IN_SECOND + " seconds...");
TimeUnit.SECONDS.sleep(CONSUME_MSG_TIME_IN_SECOND);
} catch(Exception e) {
printInfo(e.getMessage());
}
printInfo("end consuming...");
messageListenerContainer.stop();
}
private void consumeAllMessagesInTopic(ApplicationContext context, Topic topic) {
DefaultMessageListenerContainer messageListenerContainer = (DefaultMessageListenerContainer) context.getBean("jmsContainer2");
messageListenerContainer.setDestination(topic);
messageListenerContainer.initialize();
if (!messageListenerContainer.isRunning())
messageListenerContainer.start();
try {
printInfo("start consuming "+ messageListenerContainer.getDestination());
} catch(Exception e) {
printInfo(e.getMessage());
}
}
private String formatMsgWithDes(Message message) {
StringBuilder outputMessage = new StringBuilder("");
try {
String JMSDestination = message.getJMSDestination().toString();
outputMessage.append("JMSDestination: " + JMSDestination + "\n");
outputMessage.append(formatMsgWithoutDes(message));
} catch (JMSException e) {
e.printStackTrace();
}
return outputMessage.toString();
}
private String formatMsgWithoutDes(Message message) {
String outputMessage="";
try {
String JMSMessageID = message.getJMSMessageID();
String JMSTimestamp = new Date(message.getJMSTimestamp()).toString();
String JMSPriority = Integer.toString(message.getJMSPriority());
String DATASET_ID = message.getStringProperty("DATASET_ID");
String VALUE_DATE = message.getStringProperty("VALUE_DATE");
String NAME = message.getStringProperty("NAME");
String STATUS = message.getStringProperty("STATUS");
String MKT = message.getStringProperty("MKT");
outputMessage = "DATASET_ID: " + DATASET_ID + ", " +
"VALUE_DATE: " + VALUE_DATE + ", " +
"NAME: " + NAME + ", " +
"STATUS: " + STATUS + ", " +
"JMSMessageID: " + JMSMessageID + ", " +
"JMSTimestamp: " + JMSTimestamp + ", " +
"MKT: " + MKT + ", ";
} catch (JMSException e) {
e.printStackTrace();
}
return outputMessage;
}
private static void printInstruction() {
printInfo("********************"
+ "\n" + "Enter the instruction number: ");
}
private static void printInstructionDetails() {
printInfo("\n" + "0 - EXIT"
+ "\n" + "--------------------"
+ "\n" + "1 - [CUS][Queue] check all msg"
+ "\n" + "2 - [CUS][Queue] try consume msg (if msg is not received, try send one msg OR retry the action)"
+ "\n" + "3 - [CUS][Queue] send one test msg"
+ "\n" + "4 - [CUS][Topic] try check/consume all msg (if msg is not received, try send one msg OR retry the action)"
+ "\n" + "5 - [CUS][Topic] send one test msg"
);
}
public static void main(String[] args) {
boolean isContinue = true;
printInfo("INFO - start app...");
printInstructionDetails();
while (isContinue) {
ApplicationContext context = new ClassPathXmlApplicationContext("helper.jms.xml");
JmsHelper client = (JmsHelper) context.getBean("client");
Queue cusQueue = client.getDatasetQueueCUS();
Topic cusTopic = client.getDatasetTopicCUS();
Scanner scanner = new Scanner(System.in);
int inputNumber;
printInstruction();
inputNumber = scanner.nextInt();
switch (inputNumber) {
case 0: isContinue = false; break;
//
case 1: client.simpleBrowseQueue(cusQueue); break;
case 2: client.consumeAllMessagesInQueue(context, cusQueue); break;
case 3: client.simpleSendToQueue(cusQueue, "test_123_cus"); break;
case 4: client.consumeAllMessagesInTopic(context, cusTopic); break;
case 5: client.simpleSendToTopic(cusTopic, "test_123_cus"); break;
default: printInfo("INFO - input error..."); break;
}
}
printInfo("INFO - end app...");
System.exit(1);
}
public JmsTemplate getJmsTemplate() {
return jmsTemplate;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public Queue getDatasetQueueCUS() {
return datasetQueueCUS;
}
public void setDatasetQueueCUS(Queue datasetQueueCUS) {
this.datasetQueueCUS = datasetQueueCUS;
}
public Topic getDatasetTopicCUS() {
return datasetTopicCUS;
}
public void setDatasetTopicCUS(Topic datasetTopicCUS) {
this.datasetTopicCUS = datasetTopicCUS;
}
}
XML Configuration
说明:这里的Connection Factory用了TibjmsConnectionFactory
,Queue和Topic分别用了TibjmsQueue
和TibjmsTopic
。可以根据自身需求改用其它的,比如RabbitMQ。
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<context:annotation-config />
<context:property-placeholder location="classpath:helper.jms.properties" />
<bean id="client" class="com.nomura.unity.udw.util.JmsHelper" scope="prototype"></bean>
<!-- Common Components -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
<bean id="jmsConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<constructor-arg>
<bean class="com.tibco.tibjms.TibjmsConnectionFactory">
<property name="userPassword" value="${jms.userPassword}"/>
<property name="userName" value="${jms.userName}"/>
<property name="serverUrl" value="${jms.serverUrl},${jms.serverUrl}"/>
<property name="reconnAttemptCount" value="30"/>
<property name="reconnAttemptDelay" value="1000" />
</bean>
</constructor-arg>
<property name="reconnectOnException" value="true"/>
</bean>
<!-- Message Listener Container 2, used for Topic, set defalut destination to dummy -->
<bean id="jmsContainer2" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<property name="messageListener" ref="messageListener" />
<property name="destination" ref="datasetTopicDUMMY"/>
<property name="sessionTransacted" value="true" /><!-- whether JMS Sessions are transacted-->
<property name="pubSubDomain" value="true"/><!--use Publish/Subscribe domain (Topics)-->
<property name="subscriptionDurable" value="true" /><!--make the subscription durable-->
<property name="durableSubscriptionName" value="${static.durableSubscriptionName}" />
<property name="autoStartup" value="false" />
</bean>
<!-- Message Listener Container 1, used for Queue, set defalut destination to dummy -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<property name="messageListener" ref="messageListener" />
<property name="destination" ref="datasetQueueDUMMY"/>
<property name="sessionTransacted" value="false" />
<property name="pubSubDomain" value="false"/>
<property name="autoStartup" value="false" />
</bean>
<!-- Message Driven POJO -->
<bean id="messageListener" class="com.nomura.unity.udw.util.JmsHelperMessageListener" />
<!-- Destinations in different env -->
<!-- CUS -->
<bean id="datasetQueueCUS" class="com.tibco.tibjms.TibjmsQueue">
<constructor-arg value="${cus.jms.dataset.queue}" />
</bean>
<bean id='datasetTopicCUS' class="com.tibco.tibjms.TibjmsTopic">
<constructor-arg value="${cus.jms.dataset.topic}" />
</bean>
<!-- DUMMY -->
<bean id="datasetQueueDUMMY" class="com.tibco.tibjms.TibjmsQueue">
<constructor-arg value="dummy1" />
</bean>
<bean id='datasetTopicDUMMY' class="com.tibco.tibjms.TibjmsTopic">
<constructor-arg value="dummy2" />
</bean>
</beans>
Property File
jms.userName=user1
jms.userPassword=123456
jms.serverUrl=tcp://some_address
# JMS Destination for CUS
cus.jms.dataset.queue=my-queue-1
cus.jms.dataset.topic=my-topic-1
# Topic durableSubscriptionName
static.durableSubscriptionName=my-subscription-1
Dependency
1. springframework
2. jms provider (tibjms)
3. javax.jms lib
Result
下面是运行之后的一些输入和输出: