是什么
JavaEE
JavaEE是一套使用Java进行企业级应用开发的大家一致遵循的13个核心规范工业标准。JavaEE平台提供了一个基于组件的方法来加快设计,开发。装配及部署企业应用程序。
1,JDBC(Java Databease)数据库连接
2,JNDI(Java Naming and Directory Interfaces)Java的命令和目录接口
3,EJB(Enterprise JavaBean)
4,RMI(Remote Method Invoke)远程方法调用
5,Java IDL(Interface Description Language)/CORBA(Common Object Broker Architecture)接口定义语言/共用对象请求代理程序体系结构
6,JSP(Java Server Page)
7,Servlet
8,XML(Extensible Markup Language)可标记白标记语言
9,JMS(Java Message Service)Java消息服务
10,JTA(Java Transaction API)Java事务API
11,JTS(Java Transaction Service)Java事务服务
12,JavaMail
13,JAF(JavaBean Activation Framework)
JMS
Java Message Service(Java消息服务是JavaEE中的一个技术
什么是Java消息服务
Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。
JMS的组成结构和特点
JMS Provider
实现JMS接口和规范的消息中间件,也就是我们说的MQ服务器
JMS Producer
消息生产者,创建和发送JMS消息的客户端应用
JMS Consumer
消息消费者,接收和处理JMS消息的客户端应用
JSM Message
消息头
JMSDestination
消息发送的目的地,主要是指Queue和Topic
JMSDeliveryMode持久模式和非持久模式。一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。一条非持久的消息:最多会传递一次,这意味着服务器出现故障,该消息将会永远丢失。
JMSExpiration可以设置消息在一定时间后过期,默认是永不过期消息过期时间,等于Destination的send方法中的timeToLive值加上发送时刻的GMT时间值。如果timeToLive值等于0,则JMSExpiration被设为0,表示该消息永不过期。如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清除。
JMSPriority消息优先级,从0-9十个级别,0-4是普通消息5-9是加急消息。JMS不要求MQ严格按照这十个优先级发送消息但必须保证加急消息要先于普通消息到达。默认是4级。
JMSMessageID
唯一标识每个消息的标识由MQ产生。
消息体
封装具体的消息数据
5种消息格式
TxtMessage
普通字符串消息,包含一个String
MapMessage
一个Map类型的消息,key为Strng类型,而值为Java基本类型
BytesMessage
二进制数组消息,包含一个byte[]
StreamMessage
Java数据流消息,用标准流操作来顺序填充和读取
ObjectMessage
对象消息,包含一个可序列化的Java对象
发送和接收的消息体类型必须一致对应
消息属性
如果需要除消息字段以外的值,那么可以使用消息属性
识别/去重/重点标注等操作非常有用的方法
是什么
JMS的可靠性
PERSISTENT:持久性
参数设置说明
非持久
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
非持久化:当服务器宕机,消息不存在。
持久
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT)
持久化:当服务器宕机,消息依然存在。
Queue默认是持久持久的Queue
演示MessageProducer messageProducer = session.createProducer(queue); //设置通过session创建出来的生产者生产的Queue消息为持久性 messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
结论持久化消息这是队列的默认传递模式,此模式保证这些消息只被传送一次和成功使用一次。对于这些消息,可靠性是优先考虑的因素。可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送它们之前不会丢失这些消息。
持久的Topic
先启动定阅消费者再启动定阅生产者
持久的发布主题生产者
代码 import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class JmsProducer_Topic_Persist { private static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; private static final String ACTIVEMQ_TOPIC_NAME = "Topic-Persist"; public static void main(String[] args) throws JMSException { //1.创建连接工厂,按照给定的URL,采用默认的用户名密码 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通过连接工厂,持久化的topic必须在生产者创建并设置持久化完成后调用start Connection connection = activeMQConnectionFactory.createConnection(); //3.创建会话session //两个参数transacted=事务,acknowledgeMode=确认模式(签收) Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //4.创建目的地(具体是队列queue还是主题topic) Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME); //5.创建消息的生产者 MessageProducer messageProducer = session.createProducer(topic); //6.设置生产者生产持久化的Topic messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); //7.启动连接必须在设置持久化topic之后才能启动连接 connection.start(); //8.通过使用持久化Topic消息生产者,生产三条消息,发送到MQ的队列里面 for (int i = 0; i < 3; i++) { //7.通过session创建消息 TextMessage textMessage = session.createTextMessage("msg-persist" + i); //8.使用指定好目的地的消息生产者发送消息 messageProducer.send(textMessage); } //9.关闭资源 messageProducer.close(); session.close(); connection.close(); System.out.println("****TOPIC_NAME消息发布到MQ完成"); } }
控制台
订阅者在线
订阅者不在线
持久的定阅主题消费者
代码import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; public class Jms_Topic_Consumer_Persist { private static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; private static final String ACTIVEMQ_TOPIC_NAME = "Topic-Persist"; public static void main(String[] args) throws JMSException, IOException { System.out.println("我是3号消费者王留"); //1.创建连接工厂,按照给定的URL,采用默认的用户名密码 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通过连接工厂,获得connection,设置connectionID Connection connection = activeMQConnectionFactory.createConnection(); //clientid相当于订阅者的名字 connection.setClientID("王留"); //3.创建会话session //两个参数transacted=事务,acknowledgeMode=确认模式(签收) Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //4.创建目的地(具体是队列queue还是主题topic) Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME); //5.通过session创建持久化订阅用户 //相当于你订阅了微信公众号,不管你在不在线公众号端都会留有你的信息 TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "我是王留"); //6.启动连接 connection.start(); //7.接收消息 topicSubscriber.setMessageListener(message -> { if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; try { System.out.println("收到的持久化订阅消息: " + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }); /** * 一定要先运行一次消费者,类似于像MQ注册,我订阅了这个主题 * 然后再运行主题生产者 * 无论消费着是否在线,都会接收到,在线的立即接收到,不在线的等下次上线把没接收到的接收 */ } }
控制台
订阅者在线
订阅者不在线
控制台
类似微信公众号订阅发布,无论是订阅的用户还是信息都被持久化,用户下线上线后会自动接收未接收的信息需要完善发布者和订阅者的信息
发布者需要告诉mq服务器发布的是一个持久的公众号
//设置生产者生产持久化的Topic messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); //7.启动连接必须在设置持久化topic之后才能启动连接 connection.start();
订阅者需要告诉mq是谁在订阅,
//clientid相当于订阅者的名字 connection.setClientID("王留"); //5.通过session创建持久化订阅用户 //相当于你订阅了微信公众号,不管你在不在线公众号端都会留有你的信息,是一种持久的MessageConsumer TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "我是王留"); //6.启动连接 connection.start();
其他代码和普通topic一样
Transaction:事务
producer提交时的事务
false
只要执行send,就进入到队列中
关闭事务,那第2个签收参数的设置需要有效
true
先执行send再执行commit,消息才被真正提交到队列中
消息需要需要批量提交,需要缓冲处理
事务偏生产者/签收偏消费者
代码
生产者,生产者不commit消息不会send到队列中import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class Jms_TX_Producer { private static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616"; private static final String ACTIVEMQ_QUEUE_NAME = "Queue-TX"; public static void main(String[] args) throws JMSException { //1.创建连接工厂,按照给定的URL,采用默认的用户名密码 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通过连接工厂,获得connection并启动访问 Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //3.创建会话session //两个参数transacted=事务,acknowledgeMode=确认模式(签收) //开启事务需要commit Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); //4.创建目的地(具体是队列queue还是主题topic) Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME); //5.创建消息的生产者,并设置不持久化消息 MessageProducer producer = session.createProducer(queue); //6.通过使用消息生产者,生产三条消息,发送到MQ的队列里面 try { for (int i = 0; i < 3; i++) { TextMessage textMessage = session.createTextMessage("tx msg--" + i); producer.send(textMessage); } //7.提交事务 session.commit(); System.out.println("消息发送完成"); } catch (Exception e) { System.out.println("出现异常,消息回滚"); session.rollback(); } finally { //8.关闭资源 producer.close(); session.close(); connection.close(); } } }
消费者不commit则会重复接收消息,因为消息不会从队列中出来,取消息的时候会一个一个的从队列中取然后一个一个的commit,不是一次性全部取出,此程序设置的是取两个数据后会报错,然后回滚再次取import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class Jms_TX_Consumer { private static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; private static final String ACTIVEMQ_QUEUE_NAME = "Queue-TX"; public static void main(String[] args) throws JMSException { //1.创建连接工厂,按照给定的URL,采用默认的用户名密码 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通过连接工厂,获得connection并启动访问 Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //3.创建会话session //两个参数transacted=事务,acknowledgeMode=确认模式(签收) //消费者开启了事务就必须手动提交,不然会重复消费消息 Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); //4.创建目的地(具体是队列queue还是主题topic) Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME); //5.创建消息的消费者,指定消费哪一个队列里面的消息 MessageConsumer messageConsumer = session.createConsumer(queue); //6.通过监听的方式消费消息 messageConsumer.setMessageListener(new MessageListener() { int a = 0; @Override public void onMessage(Message message) { if (message instanceof TextMessage) { try { if (a == 2) { System.out.println(1 / 0); } TextMessage textMessage = (TextMessage) message; System.out.println("***消费者接收到的消息: " + textMessage.getText()); session.commit(); a = a + 1; } catch (Exception e) { System.out.println("出现异常,消费失败,放弃消费"); try { session.rollback(); a=0; } catch (JMSException ex) { ex.printStackTrace(); } } } } }); //7.关闭资源 } } ***消费者接收到的消息: tx msg--0 ***消费者接收到的消息: tx msg--1 出现异常,消费失败,放弃消费 ***消费者接收到的消息: tx msg--2
Acknowledge:签收
非事务签收
自动签收(默认)Session.AUTO_ACKNOWLEDGE
手动签收Session.CLIENT_ACKNOWLEDGE
客户端调用TextMessage.acknowledge()方法手动签收
事务签收
生产事务开启,只有commit后才能将全部消息变为已消费,开了事务就没必要签收了会变为自动签收
消息生产者
代码import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class Jms_Transaction_AUTOACK_Producer { private static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; private static final String ACTIVEMQ_QUEUE_NAME = "Queue-ACK-NoTransaction"; public static void main(String[] args) throws JMSException { ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME); MessageProducer producer = session.createProducer(queue); for (int i = 0; i < 3; i++) { TextMessage textMessage = session.createTextMessage("Transaction_AUTOACK-msg: " + i); producer.send(textMessage); } session.commit(); System.out.println("发送完成"); producer.close(); session.close(); connection.close(); } }
控制台
消息消费者
代码import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class Jms_Transaction_CLIENTACK_Consumer { private static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; private static final String ACTIVEMQ_QUEUE_NAME = "Queue-ACK-Transaction"; public static void main(String[] args) throws JMSException { ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //消费者设置了手动签收,就必须自己签收,向服务器发送我已经收到消息了 //开启事务如果不提交,就算手动签收,也是无效的 Session session = connection.createSession(true, Session.CLIENT_ACKNOWLEDGE); Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME); MessageConsumer messageConsumer = session.createConsumer(queue); messageConsumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; try { textMessage.acknowledge(); System.out.println(textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } } }); } }
由于消费者开启了事务,没有提交事务(就算手动签收也没用),服务器认为,消费者没有收到消息
控制台
JMS的点对点总结
点对点模型是基于队列的,生产者发送消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。和我们平时给朋友发送短信类似。
1:如果在Session关闭时有部分消息被收到但还没有被签收(acknowledge),那当消费者下次连接到相同的队列时,这些消息还会被再次接收
2:队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的链接状态,充分体现了异步传输模式的优势
JMS的发布订阅总结
JMS Pub/Sub 模型定义了如何向一个内容节点发布和订阅消息,这些节点被称作topic
主题可以被认为是消息的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息。
主题使得消息订阅者和消息发布者保持互相独立不需要解除即可保证消息的传送
非持久订阅非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能收发到某个主题的消息。如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会收到。一句话:先订阅注册才能接受到发布,只给订阅者发布消息。
持久订阅客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ的时候,会根据消费者的ID得到所有当自己处于离线时发送到主题的消息当持久订阅状态下,不能恢复或重新派送一个未签收的消息。持久订阅才能恢复或重新派送一个未签收的消息。
用哪个?
当所有的消息必须被接收,则用持久订阅。当消息丢失能够被容忍,则用非持久订阅