本文主要展示如何使用activeMQ进行程序设计,可以作为代码实例参考;此后会继续补充有关JMS 或ActiveMQ的优化和架构部分。
本实例主要展示如何使用Queue。
一.pom.xml
- <dependencies>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>3.2.3.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-jms</artifactId>
- <version>3.2.3.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.4</version>
- </dependency>
- <dependency>
- <groupId>javax.jms</groupId>
- <artifactId>jms</artifactId>
- <version>1.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.qpid</groupId>
- <artifactId>proton-jms</artifactId>
- <version>0.3</version>
- </dependency>
- <dependency>
- <groupId>org.apache.activemq</groupId>
- <artifactId>activemq-all</artifactId>
- <version>5.8.0</version>
- </dependency>
- </dependencies>
- <build>
- <finalName>test-jms-1.0</finalName>
- <resources>
- <resource>
- <directory>src/main/resources</directory>
- <filtering>true</filtering>
- </resource>
- </resources>
- </build>
二.Java实例(非spring环境)
对JMS的程序部分,推荐使用JNDI + 异步listener方式;所以接下来的例子将采取此方式。
1) jndi.properties----[/src/main/resources/jndi.properties]
- ###contextFactory
- java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory
- ###brokerUrl,any protocol
- java.naming.provider.url = tcp://localhost:61616
- ##username
- ##java.naming.security.principal=
- ##password
- ##java.naming.security.credentials=
- ##connectionFactory,for building sessions
- connectionFactoryNames = QueueCF,TopicCF
- ##topic.<topicName> = <physicalName-of-topic>
- ##your application should use <topicName>,such as:
- ## context.lookup("topic1");
- ##It can be more than once
- topic.topic1 = jms.topic1
- ##queue.<topicName> = <physicalName-of-queue>
- queue.queue1 = jms.queue1
- public class QueueMessageListener implements MessageListener{
- public void onMessage(Message message) {
- if(message == null){
- return;
- }
- try{
- if(message instanceof TextMessage){
- String text = ((TextMessage)message).getText();
- System.out.println("-----JMS Message header-----");
- //message的关联id,可以在发送消息时指定,用于描述消息的关联性
- System.out.println("CorrelationID :" + message.getJMSCorrelationID());
- //消息的“传送模式”,1:非持久,2:持久
- System.out.println("DeliveryMode :" + message.getJMSDeliveryMode());
- //消息的过期时间,毫秒数;如果在发送消息时指定了timeToLive,此值为timestap + timeToLive
- System.out.println("Expiration :" + message.getJMSExpiration());
- //消息ID,全局唯一
- System.out.println("MessageID :" + message.getJMSMessageID());
- //消息权重,参考属性
- System.out.println("Priority :" + message.getJMSPriority());
- //是否为“重发”;当一个消息发送给消费者之后,未能收到“确认”,将会导致消息重发
- System.out.println("Redelivered :" +message.getJMSRedelivered());
- //消息创建的时间戳,当消息发送时被赋值。
- System.out.println("Timestamp :" + message.getJMSTimestamp());
- //消息的类型
- System.out.println("Type :" + message.getJMSType());
- System.out.println("-----Message Properties-----");
- Enumeration<String> names = message.getPropertyNames();
- if(names != null){
- while(names.hasMoreElements()){
- String key = names.nextElement();
- System.out.println(key + ":" + message.getStringProperty(key));
- }
- }
- System.out.println(">>>>" + text);
- }
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
因为我们已经使用了“MessageListener”来异步接受消息,事实上JMS的实现中已经开启了单独的线程用来从网络中接受消息,并逐次调用onMessage方法;此处我们没有必要再次额外的创建线程。
- public class SimpleConsumer {
- private Connection connection;
- private Session session;
- private MessageConsumer consumer;
- private boolean isStarted;
- public SimpleConsumer(MessageListener listener) throws Exception{
- Context context = new InitialContext();
- ConnectionFactory connectionFactory = (QueueConnectionFactory)context.lookup("QueueCF");
- connection = connectionFactory.createConnection();
- session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
- Destination queue = (Queue)context.lookup("queue1");
- consumer = session.createConsumer(queue);
- consumer.setMessageListener(listener);
- }
- public synchronized boolean start(){
- if(isStarted){
- return true;
- }
- try{
- connection.start();//very important
- isStarted = true;
- return true;
- }catch(Exception e){
- return false;
- }
- }
- public synchronized void close(){
- isStarted = false;
- try{
- session.close();
- connection.close();
- }catch(Exception e){
- //
- }
- }
- }
- public class SimpleProductor {
- private MessageProducer producer;
- private Session session;
- private Connection connection;
- private boolean isOpen = true;
- public SimpleProductor() throws Exception{
- Context context = new InitialContext();
- ConnectionFactory connectionFactory = (QueueConnectionFactory)context.lookup("QueueCF");
- connection = connectionFactory.createConnection();
- session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
- Destination queue = (Queue)context.lookup("queue1");
- producer = session.createProducer(queue);
- producer.setDeliveryMode(DeliveryMode.PERSISTENT);
- }
- public boolean send(String message) {
- if(!isOpen){
- throw new RuntimeException("session has been closed!");
- }
- try{
- producer.send(session.createTextMessage(message));
- return true;
- }catch(Exception e){
- return false;
- }
- }
- public synchronized void close(){
- try{
- if(isOpen){
- isOpen = false;
- }
- session.close();
- connection.close();
- }catch (Exception e) {
- //
- }
- }
- }
上面的程序,基本上可以完成简单的消息发送和接受,此外,还有一种不常用的方式:
- ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
- Connection connection = connectionFactory.createConnection();
- Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
- Destination queue = session.createQueue(QUEUE);
- MessageProducer producer = session.createProducer(queue);
- //MessageConsumer consumer = session.createConsumer(queue)
- //同步接收消息
- MessageConsumer consumer = session.createConsumer(queue);
- connection.start();
- while(true){
- Message message = consumer.receive(10000);
- if(message == null){
- continue;
- }
- if(message instanceof TextMessage){
- //...
- }
- }
5) 测试方法:
- SimpleProductor productor = new SimpleProductor();
- productor.start();
- for(int i=0; i<10; i++){
- productor.send("message content:" + i);
- }
- productor.close();
- SimpleConsumer consumer = new SimpleConsumer(new QueueMessageListener());
- consumer.start();
- //consumer.close();
三.Spring-jms实例
1) 配置文件:
- <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
- <property name="brokerURL" value="tcp://localhost:61616"></property>
- </bean>
- <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg index="0" value="queue1"></constructor-arg>
- </bean>
- <!-- productor -->
- <bean id="queueTemplate" class="org.springframework.jms.core.JmsTemplate">
- <property name="connectionFactory" ref="connectionFactory"></property>
- <property name="defaultDestination" ref="queueDestination"></property>
- <!-- 是否在message中开启timestamp属性 -->
- <property name="messageTimestampEnabled" value="true"></property>
- <!-- 是否开启deliveryMode,priority,timeToLive消息附属属性 ,否则上述3个属性将采用默认值-->
- <property name="explicitQosEnabled" value="true"></property>
- <!-- NON_PERSISTENT = 1,PERSISTENT = 2,默认值为2-->
- <property name="deliveryMode" value="2"></property>
- <!-- pubSubNoLocal,对于topic而言,还需要注意此选项:是否接受本地消息,当消费者和生产者公用一个connection时 -->
- </bean>
- <bean id="productor" class="com.test.jms.spring.impl.ProductorImpl">
- <property name="jmsTemplate" ref="queueTemplate"></property>
- </bean>
- <!-- MDB -->
- <bean id="queueMessageListener" class="com.test.jms.object.QueueMessageListener"/>
- <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
- <property name="connectionFactory" ref="connectionFactory"></property>
- <property name="destination" ref="queueDestination"></property>
- <property name="messageListener" ref="queueMessageListener"></property>
- <!-- 如果消息的接收速率,大于消息处理的速率时,可以采取线程池方式
- <property name="taskExecutor" ref="queueMessageExecutor"></property>
- -->
- <!-- -->
- <property name="concurrentConsumers" value="1"></property>
- <!-- [concurrentConsumers]-[maxConcurrentConsumers] -->
- <!--
- <property name="concurrency" value="1-5"></property>
- -->
- </bean>
- <bean id="queueMessageExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
- <property name="corePoolSize" value="2" />
- <property name="maxPoolSize" value="5" />
- <property name="daemon" value="true" />
- <property name="keepAliveSeconds" value="120" />
- </bean>
我们采用了spring推荐的方式:消息生产者基于jmsTemplate,消息消费者基于MDB(pojo的消息驱动bean);为了提升消息的接收者的吞吐能力,我们可以采取多线程的方式,其中有几个重要的参考配置:
A) taskExecutor:消息接受端使用线程池的方式处理消息;当消息的网络输入速率大于消息的处理速率时,可以考虑采用此方式;在消息消费者的与JMS-Server的网络链接中,每收到一条消息,将会立即交付给线程池中的线程去执行,执行时仍然调用messageListener的方法;此处需要注意,线程池中的所有线程仍然共享一个messageListener实例,在采用线程池模式中,请注意线程安全问题。
B) concurrentConsumers:并发运行的消费者个数,在默认情况下为一个“消息消费者”;事实上,一个consumer即为一个Session,多个consumer即为多个Session;不过它们底层仍然共享一个“tcp链接”;此配置项仍然是适用于“消息的网络输入速率大于消息的处理速率”的场景;每一个consumer都将会在单独的线程中运行,但是它们仍然共享一个messageListener实例;在此场景下,你无法绝对的保证,原本“有序”的消息在交付给多个consumer时被实际执行的顺序也是严格的。
taskExecutor是一种额外的优化策略,concurrentConsumers则是采用了JMS原生的特性;在实际场景中,我们选择一种即可。如下为Spring-JMS是如何使用线程池处理消息的原理(基于封装的思想):
- if (this.taskExecutor != null) {
- consumer.setMessageListener(new MessageListener() {
- public void onMessage(final Message message) {
- taskExecutor.execute(new Runnable() {
- public void run() {
- processMessage(message, session);
- }
- });
- }
- });
- }
- else {
- consumer.setMessageListener(new MessageListener() {
- public void onMessage(Message message) {
- processMessage(message, session);
- }
- });
- }
2) ProductorImpl.java
- public class ProductorImpl implements Productor {
- private JmsTemplate jmsTemplate;
- public void setJmsTemplate(JmsTemplate jmsTemplate) {
- this.jmsTemplate = jmsTemplate;
- }
- public void send(final String message) {
- if(message == null){
- return;
- }
- jmsTemplate.send(new MessageCreator() {
- public Message createMessage(Session session) throws JMSException {
- return session.createTextMessage(message);
- }
- });
- }
- public void send(final Map<String, String> message) {
- if(message == null || message.isEmpty()){
- return;
- }
- jmsTemplate.send(new MessageCreator() {
- public Message createMessage(Session session) throws JMSException {
- MapMessage mm = session.createMapMessage();
- for(Entry<String, String> entry : message.entrySet()){
- mm.setString(entry.getKey(), entry.getValue());
- }
- return mm;
- }
- });
- }
- }
非常的简单,我们不用书写和consumer有关的代码。一切就结束了。[备注,本文中的实例命名为Productor,只是为了避免和JMS中producer混淆]