Spring消息——JMS发送消息

JMS概述

Java Message Service (JMS)
The Java Message Service (JMS) API is a messaging standard that allows application components based on the Java Platform Enterprise Edition (Java EE) to create, send, receive, and read messages. It enables distributed communication that is loosely coupled, reliable, and asynchronous.

Java消息服务(Java Message Service, JMS)是一个Java标准,定义了使用消息代理的通用API。在JMS出现之前,每个消息代理都有私有的API,这就使得不同代理之间的消息代码很难通用。借助JMS,所有遵从规范的实现都使用通用的接口,这就类似于JDBC为数据库操作提供了通用的接口一样。

关于JMS更详细的内容参见:JMS(Java消息服务)入门教程(一) - 一支会记忆的笔 - 博客园

ActiveMQ

概述

Apache ActiveMQ是一款非常流行的多协议开源java消息服务器。它支持行业标准协议,开发人员可以选择跨语言、跨平台的客户端,如C,C++,Python,.Net等。除此之外,支持JMS。(ActiveMQ

Spring消息——异步消息中,我们介绍了消息模型。我们知道一个消息系统要有发送者,接收者,代理和目的地。ActiveMQ就是能提供JMS的代理服务器。

安装

ActiveMQ官网下载页中下载ActiveMQ
下载页面
根据操作系统下载相应的资源包
下载包
把压缩包解压到自定义的目录:
解压后

启动

在bin目录下,我们可以看到为各种操作系统所创建的对应子目录。在这些子目录下,我们可以找到用于启动ActiveMQ的脚本。例如,在Windows64位系统下启动ActiveMQ,需要在“bin/win64”目录下运行activemq.bat。运行脚本后,ActiveMQ就准备好了。目录
运行日志
我们可以看到ActiveMQ监听了tcp://aodi-PC:61616,amqp://aodi-PC:5672,mqtt://aodi-PC:1883,ws://aodi-PC:61614,证明ActiveMQ支持多种消息协议。有用信息

jvm 1    |  INFO | ActiveMQ WebConsole available at http://0.0.0.0:8161/
jvm 1    |  INFO | ActiveMQ Jolokia REST API available at http://0.0.0.0:8161/api/jolokia/

说明ActiveMQ还提供了Web控制台和REST API接口。在浏览器中输入:http://localhost:8161(端口号根据控制台显示输入),就可以看到ActiveMQ的Web控制台。
web控制台
至此,ActiveMQ就准备好了,这时可以使用它作为消息代理。

管理页面

在ActiveMQ的web控制台首页点“Mange ActiveMQ broker”,在浏览器弹出框中输入用户名和密码,用户名和密码保存在“/conf/users.properties”文件中,默认用户和密码都是admin,输入用户和密码登录后,就进入了ActiveMQ的控制台首页。
console首页
在横向导航栏中我们看到有"Queues",“Topics”,“Subscribers”,“Connections”,“Network”,“Scheduled”,“Send”,点击导航栏可以进入到相应各项列表页。ActiveMQ控制台有很强大的功能,允许我们手动创建Queue,Sender,Topics等。
管理页面

使用传统JMS发送消息

在我们编写一个异步消息示例之前,我们可以想象可能需要如下几步:

  1. 创建/连接消息代理
  2. 创建消息的发送者,接收者;
  3. 创建消息的目的地:队列或者主题;
  4. 通过代理把发送者,目的地,接收者关联起来;
  5. 发送者发送消息;
  6. 接收者接收消息;

编码

新建工程

在eclipse中新建java工程
新建java工程
输入工程名称为mq-java
工程配置
点击“Finish”,这样就创建了一个空的java工程

目录结构

我们先引入ActiveMQ提供的jar包
鼠标右键点击项目的“JRE System Library”,选择“Build Path”——“Configure Build Path”
配置Path
在“Java Build Path”面板中点击"Add External JARs"按钮,选择apache-activemq包中的activemq.jar包
导入jar包
在工程src中新建me.aodi,me.aodi.service包
目录结构
me.aodi包::应用程序跟目录;
me.aodi.service:业务类;

编码

  • 新建`me.aodi.service.SmsService类来模拟耗时业务代码
package me.aodi.service;

public class SmsService {
	/**
	 * 模拟耗时任务
	 * @param message
	 */
	public void send(String message) {
		System.out.println("开始发送短信:"+message);
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("短息发送完成");
	}
}
  • 新建me.aodi.MqSender发送消息类
package me.aodi;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTextMessage;

/**
 * MQ发送者
 * @author audi
 *
 */
public class MqSender {
	public static void main(String[] args) {
		Connection connection = null;
		Session session = null;
		try {
			//1、创建连接
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://aodi-PC:61616");
			connection = connectionFactory.createConnection();
			session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
			//2、创建目的地
			Destination destination = new ActiveMQQueue("sms_queue");
			//3、创建发送者(=消息的生产者)
			MessageProducer producer = session.createProducer(destination);
			//4、发送消息
			TextMessage message = new ActiveMQTextMessage();
			message.setText("test ActiveMQ");
			producer.send(message);
			
			System.out.println("发送完成!");
		} catch (JMSException e) {
			System.out.println(e.getMessage());
		}finally {
			try {
				//关闭连接
				if (session != null) {
					session.close();
				}
				if (connection != null) {
					connection.close();
				}
			} catch (JMSException e) {
				System.out.println(e.getMessage());
			}
		}
	}
}
  • 新建me.aodi.MqReceiver消息接收类
package me.aodi;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;

import me.aodi.service.SmsService;

/**
 * MQ接收者
 * @author audi
 *
 */
public class MqReceiver {
	public static void main(String[] args) {
		Connection connection = null;
		Session session = null;
		
		try {
			//1、连接ActiveMQ代理
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://aodi-PC:61616");
			connection = connectionFactory.createConnection();
			connection.start();
			//创建会话
			session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
			//2、创建目的地
			Destination destination = session.createQueue("sms_queue");
			//3、创建接收者(=消息的消费者)
			MessageConsumer consumer = session.createConsumer(destination);
			//4、接收消息
			TextMessage message = (TextMessage) consumer.receive();
			String text = message.getText();
			
			System.out.println("接收到消息:"+text);
			//调用业务方法
			SmsService smsService = new SmsService();
			smsService.send(text);
		} catch (JMSException e) {
			System.out.println(e.getMessage());
		}catch (Exception e) {
			System.out.println(e.getMessage());
		}finally {
			try {
				//关闭连接
				if (session != null) {
					session.close();
				}
				if (connection != null) {
					connection.close();
				}
			} catch (JMSException e) {
				System.out.println(e.getMessage());
			}
		}
	}
}

运行实例

  • 编辑后的工程目录结构
    编辑后目录结构
  • 运行MqSender.java,可以看到顺序马上执行完退出,console中打印出我们写的输出
    消息发送者日志
    在ActiveMQ的Web控制台中,查看队列列表,我可以看到我们创建的队列以及发送的消息
    sms_queue队列
    此时,等待消息数(Number Of Pending Messages)为1,消息入队数(Messages Enqueued)为1
  • 运行MqReceiver.java,程序运行结束就退出了,控制台留下了运行时我们要输出的信息
    接收者日志
    在ActiveMQ的Web控制台中可以看到原来处于待定的消息数变为0,消息出队数变为1
    web控制台
    如果我们再次运行MqReceiver.java,因为队列中没有消息,程序会阻塞在TextMessage message = (TextMessage) consumer.receive();处,而控制台中,消息消费者数则变为1
    控制台
    我们再次运行MqSender.java,消息发送后,会被阻塞的MqReceiver接收,MqReceive执行结束后退出,web控制台中消息消费者数再次变为0,而入队出队消息数将+1
    控制台通过观察MQ的发送和接收过程以及ActiveMQ的运行模式,是否觉得这跟数据库的数据的插入,查询过程非常相似,在数据库编程中,Spring提供JdbcTemplate类消除了jdbc中的样板式代码,在异步消息中,Spring提供了JmsTemplate简化发送接收异步消息的过程。

使用Spring的JMS模板

编码

新建工程

在eclipse中新建maven工程,设置工程名为mq-spring
工程配置
点击“Finish”,这样就创建了一个空的maven工程

目录结构

-在pom.xml文件中引入Spring和ActiveMQ所需的包

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>me.aodi</groupId>
  <artifactId>mq-spring</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>
  	<org.springframework.version>4.3.17.RELEASE</org.springframework.version>
  </properties>
  <dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${org.springframework.version}</version>
	</dependency>
	<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
	<dependency>
	    <groupId>org.apache.activemq</groupId>
	    <artifactId>activemq-all</artifactId>
	    <version>5.10.0</version>
	</dependency>
	<!-- https://mvnrepository.com/artifact/org.springframework/spring-jms -->
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-jms</artifactId>
	    <version>5.2.4.RELEASE</version>
	</dependency>
  </dependencies>
</project>

在工程src中新建me.aodi,me.aodi.service包
目录结构
me.aodi包::应用程序根目录;
me.aodi.service:业务类;

编码

  • 在src/main/resources中新建spring配置文件application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
	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">
	<!-- 业务类 -->
	<bean id="smsService" class="me.aodi.service.SmsService"></bean>
	<!-- ActiveMQ连接工厂 -->
	<bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"></bean>
	<!-- 目的地——队列 -->
	<bean class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="sms_queue"/>
	</bean>
	<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
		<property name="connectionFactory" ref="connectionFactory"/>
	</bean>
</beans>
  • 新建`me.aodi.service.SmsService类来模拟耗时业务代码
package me.aodi.service;

public class SmsService {
	/**
	 * 模拟耗时任务
	 * @param message
	 */
	public void send(String message) {
		System.out.println("开始发送短信:"+message);
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("短息发送完成");
	}
}
  • 新建me.aodi.MqSender发送消息类
package me.aodi;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

/**
 * MQ发送者
 * @author audi
 *
 */
public class MqSender {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
		/**
		 * 获取jmsTemplate
		 */
		JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class);
		/**
		 * 发送消息
		 */
		jmsTemplate.send("sms_queue", new MessageCreator() {
			
			public Message createMessage(Session session) throws JMSException {
				//发送文本消息
				return session.createTextMessage("test ActiveMQ");
			}
			
		});
		
		System.out.println("发送完成!");
		
		((AbstractApplicationContext) applicationContext).close();
	}
}
  • 新建me.aodi.MqReceiver消息接收类
ppackage me.aodi;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;

import me.aodi.service.SmsService;

/**
 * 消息接收者
 * @author audi
 *
 */
public class MqReceiver {

	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
		//获取
		JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class);
		String text = (String) jmsTemplate.receiveAndConvert("sms_queue");
		
		System.out.println("接收到消息:"+text);
		//调用业务方法
		SmsService smsService = new SmsService();
		smsService.send(text);
		
		((AbstractApplicationContext) applicationContext).close();
	}
}

运行实例

  • 编辑后的工程目录结构
    编辑后目录结构
  • 运行MqSender.java,可以看到顺序马上执行完退出,console中打印出我们写的输出
    发送者日志
  • 运行MqReceiver.java,可以看到顺序马上执行完退出,console中打印出我们写的输出
    接收者日志

JmsTemplate可以创建连接、获得会话以及发送和接收消息,使得我们可以专注于构建要发送的消息或者处理接收到的消息。

以上的接收消息最大的确定在于receive()方法都是同步的。接收者必须一直等待消息的到来,否者会一直被阻塞。接收者接收到消息后继续执行最后程序结束退出,这样我们要使用while语句让receive()一直调用。
消息驱动提供了更友好的消息处理方式。

消息驱动

EJB2规范引入了一个重要的内容是消息驱动bean(message-driven bean,MDB)。MDB是可以异步处理消息的EJB。MDB将JMS目的地中的消息作为事件,并对这些事件进行响应。
在EJB3规范中,MDB进一步简化,使其更像POJO,并提供了@MessageDriven注解标注MDB。

使用消息驱动bean(MDB)

我们对mq-java工程进行改造,使用MDB接收消息

  • 创建消息监听器
    com.aodi.mq.listener包中添加SmsMessageListener类
package me.aodi.mq.listener;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

import me.aodi.service.SmsService;

public class SmsMessageListener implements MessageListener {

	@Override
	public void onMessage(Message message) {
		TextMessage textMessage = (TextMessage) message;
		String text = null;
		try {
			text = textMessage.getText();
		} catch (JMSException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		}
		System.out.println("接收到消息:"+text);
		
		SmsService smsService = new SmsService();
		smsService.send(text);
	}

}
  • 配置消息监听器
    对SmsReceiver类中接收消息进行改造,去掉receiver()方法接收消息,添加消息监听器
		try {
			//1、连接ActiveMQ代理
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://aodi-PC:61616");
			connection = connectionFactory.createConnection();
			connection.start();
			//创建会话
			session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
			//2、创建目的地
			Destination destination = session.createQueue("sms_queue");
			//3、创建接收者(=消息的消费者)
			MessageConsumer consumer = session.createConsumer(destination);
			//4、接收消息
			SmsMessageListener messageListener = new SmsMessageListener();
			consumer.setMessageListener(messageListener);
			
			System.out.println("MQ接收者执行结束");
		}
  • System.out.println("MQ接收者执行结束");中打个断点,使程序不立即技术。调试模式运行MqReceiver
    调试运行MqReceiver后,查看ActiveMQ控制台,可以发现消费者数量一直维持为1
    在这里插入图片描述
  • 直接运行MqSender
    刷新ActiveMQ控制台可以发现等待的消息数增加1
    在这里插入图片描述
    过几秒后,刷新页面,等待的消息数会变为0。这是因为SmsService类中的消息处理方法我们让线程挂起了3秒。多执行几次MqSender,上面的现象会更明显。消息监听器会维持一个消息消费者,一直尝试接收消息。

Spring提供了与EJB3的MDB很相似的消息驱动bean,下面使用Spring的消息驱动POJO(简称MDP)来支持异步接收消息。

使用消息驱动POJO(MDP)

我们对mq-spring工程进行改造,使用Spring的MDP接收消息

  • src/main/resources目录下新建application-receiver.xml文件,在文件中配置消息监听器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
	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">
	<!-- 业务类 -->
	<bean id="smsService" class="me.aodi.service.SmsService"></bean>
	
	<!-- ActiveMQ连接工厂 -->
	<bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"></bean>
	<!-- 目的地——队列 -->
	<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="sms_queue"/>
	</bean>
	
	<!-- 定义消息监听器 -->
	<bean id="smsMessageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
		<!-- 配置消息处理类 -->
		<constructor-arg ref="smsService"/>
		<!-- 设置消息处理方法 -->
		<property name="defaultListenerMethod" value="send"/>
	</bean>
	<!-- 配置消息监听容器 -->
	<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
		<property name="connectionFactory" ref="connectionFactory"/>
		<property name="destination" ref="queue"/>
		<property name="messageListener" ref="smsMessageListener"/>
	</bean>
</beans>
  • 删除在MqReceiver中的消息接受。
package me.aodi;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;

/**
 * 消息接收者
 * @author audi
 *
 */
public class MqReceiver {
	MessageListenerAdapter container;
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application-receiver.xml");
		
		System.out.println("加载Spring");
		((AbstractApplicationContext) applicationContext).close();
	}
}

-分别运行消息接收者和发送者程序
程序运行结果与使用MDB一致

Spring的JMS注解

Spring提供的注解免去了我们写xml代码的麻烦,Spring提供的JMS注解有:@JmsListener,@EnableJms,@SendTo等,可以参见:《Spring 5 官方文档》26. JMS(三) / 水缘泡泡

  • 我们修改application-receiver.xml文件,添加jms:annotation-driven/来支持jms注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context" 
	xmlns:jms="http://www.springframework.org/schema/jms" 
	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/jms 
    http://www.springframework.org/schema/jms/spring-jms.xsd
    ">
    
    <!-- 开启spring注解扫描 -->
    <context:component-scan base-package="me.aodi"/>
	
	<!-- ActiveMQ连接工厂 -->
	<bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"></bean>
	<!-- 目的地——队列 -->
	<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="sms_queue"/>
	</bean>
	
	<!-- 开启jms注解支持 -->
	<jms:annotation-driven/>
	
	<!-- 配置消息监听容器工厂 -->
	<bean id="jmsListenerContainerFactory"
           class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
       <property name="connectionFactory" ref="connectionFactory"/>
       <property name="concurrency" value="3-10"/>
   </bean>
</beans>
  • 在SmsService中添加注解
package me.aodi.service;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;

@Service
public class SmsService {
	/**
	 * 模拟耗时任务
	 * @param message
	 */
	@JmsListener(destination = "sms_queue")
	public void send(String message) {
		System.out.println("开始发送短信:"+message);
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("短息发送完成");
	}
}
  • 重新运行
    @JmsListener注解要求spring版本在5之上,我们修改pom.xml文件中spring版本号为5.2.0.RELEASE
  <properties>
  	<org.springframework.version>5.2.0.RELEASE</org.springframework.version>
  </properties>

注:在spring4中,程序会报没有AnnotationUtils.isCandidateClass方法的错误
右键点击项目,选择“Maven”——“Update Project”,打开Update Maven Project弹出框
在这里插入图片描述
选中项目和强制更新,点击“OK”,eclipse会下载spring5相关包,重新构建工程
在这里插入图片描述
运行MqReceiver。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值