1.什么是Jms
jms(java消息服务)是一个java的标准,就是使用消息代理的API。
名词解析:
消息代理:就是一个应用给另一个应用发送消息时,会将消息先给消息代理,消息会确保消息到达目得地。这个消息代理类似于邮局,会把我们的邮件寄到想要寄的人。
目得地:就是接收消息的地方。目得地只关注消息应该从哪里获得。而不关心消息是谁取走的。
activeMQ:是支持jms标准的工具。
2.ActiveMQ环境的搭建
首先肯定得需要java环境。
去 http://activemq.apache.org/activemq-5111-release.html 地址下载开发包及源码。
在下载的包中bin中activemq是ActiveMQ服务器启动的文件。
启动activemq文件,结果:
点击地址:
http://localhost:8161/admin/
这里直接用的别人的配置,activemq原始的端口号是61616。
已配置好的开发环境: http://download.csdn.net/detail/ya_1249463314/9752773
进入activemq服务启动的主页面:
这里面Queues就是队列的内容的相关数据的显示(点对点模型稍后会介绍)。Topics就是主题的内容的相关数据的显示(发布订阅模型稍后会介绍)。
activemq-all-5.11.1.jar包,是开发需要的jar包,如果开发需要导入。
3.点对点模型的解析
在jms中会有两种不同的消息路由方式。对应的两种通用的目的地分别是队列和主题。这两种模型分别是点对点模型和发布订阅模型。
什么是点对点模型?
就是意味着每一条消息都有一个发送者和一个接受者(是一个一对一的关系)。当消息代理得到消息时,它将消息收入一个队列中。当接收者请求队列中的下一条消息时,消息会从队列中取出,并将消息传递给接收者。注意一点是虽然消息队列中的每一条消息只传给一个接收者,但是这不意味着只能使用一个接收者从队列中获取消息。一般我们都是使用几个接收者来处理队列中的消息。只是每个接收者都会处理自己所接收的消息。
举例:
在银行中办理业务时,当银行柜员业务完成后,他就闲着没啥事了,然后排队的队伍中最前面的人会被叫到,然后去办业务。所以我们会被哪个柜员叫到是不确定,也可以说随机的。这样就充分保证了柜员人员利用率的提高。如果是每个人专门负责什么业务的话,柜员完成任务就会闲着,不能保证充分利用。(尽管现在银行去办理业务都是给张带号的纸不采用排队了,实际上就是相当于使用号码排队了)。
java实现点对点模型
(1)方式1:不使用监听器
消息生产者:
/*消息生产者*/
public class QueueProducer {
//默认的连接用户名
private static final String USERNAME=ActiveMQConnectionFactory.DEFAULT_USER;
//默认的连接密码
private static final String PASSWORD=ActiveMQConnectionFactory.DEFAULT_PASSWORD;
//默认的连接地址
private static final String URL=ActiveMQConnectionFactory.DEFAULT_BROKER_URL;
//发送消息的条数
private static final int MessageNum=10;
public static void main(String[] args) {
ConnectionFactory connectionFactory;//连接工厂生产Connection
Connection connection = null;
//发送或者接收的线程
Session session;
//消息的目得地
Destination destination;
//消息生产者
MessageProducer messageProducer;
//实例化连接工厂
connectionFactory =new ActiveMQConnectionFactory(QueueProducer.USERNAME, QueueProducer.PASSWORD, QueueProducer.URL);
//通过连接工厂获取连接
try {
connection =connectionFactory.createConnection();
connection.start();
session =connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
destination =session.createQueue("FirstQueue");
messageProducer =session.createProducer(destination);
sendMessage(session,messageProducer);
session.commit();
} catch (Exception e) {
e.printStackTrace();
}finally{
if(connection !=null){
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
//发送消息
public static void sendMessage(Session session,MessageProducer messageProducer) throws Exception{
for(int i=0;i<QueueProducer.MessageNum;i++){
TextMessage message =session.createTextMessage("ActiveMQ发送的消息 "+i);
System.out.println("发送消息:ActiveMq发送的消息"+i);
messageProducer.send(message);
}
}
}
解释:生产者自然是消息的发送者,也就是消息传递的源头。至于连接姓名、密码和地址和JDBC创建连接的配置方式是类似的。
使用连接工厂创建连接。使用连接创建会话线程用来做发送或接收的。使用会话创建带名字的队列,然后将此目得地给消息生产者。然后发送消息,注意要提交会话,要不消息发不出去的。
运行结果:
开始没有生产者发送消息,状态:
消息数量0、消费者数量0、消息进队列0、消息出队列0。
运行程序,生产者发送了消息,状态:
消息数量10、消息消费者数量0,、消息进队列10条、消息出队列0条。
消息接收者:
/*消息消费者*/
public class QueueConsumer {
private static final String USERNAME=ActiveMQConnectionFactory.DEFAULT_USER;
private static final String PASSWORD=ActiveMQConnectionFactory.DEFAULT_PASSWORD;
private static final String URL=ActiveMQConnectionFactory.DEFAULT_BROKER_URL;
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
//消息的消费者
MessageConsumer messageConsumer;
connectionFactory =new ActiveMQConnectionFactory(QueueConsumer.USERNAME, QueueConsumer.PASSWORD, QueueConsumer.URL);
try {
connection =connectionFactory.createConnection();
connection.start();
session =connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
destination =session.createQueue("FirstQueue");
messageConsumer =session.createConsumer(destination);
while(true){
TextMessage message =(TextMessage) messageConsumer.receive(100000);
if(message !=null){
System.out.println("收到的消息:"+message.getText());
}else{
break;
}
}
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
解释:连接配置和前面一样。这里创建消息消费者。目得地必须和前面的消息生产者一样,要不收不到消息代理所存的消息。然后使用循环,只要有数据都遍历出来。
运行结果:
消息数量0、消息消费者1位、消息进队还是原来的10条、消息消费出队列10条。
(2)方式2:使用监听器
生产者和上例一样。主要是消费者
队列监听器:
public class QueueListener implements MessageListener{
@Override
public void onMessage(Message message) {
// TODO Auto-generated method stub
try {
System.out.println("收到的消息:"+((TextMessage)message).getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
消息消费者:
public class QueueConsumer2 {
private static final String USERNAME=ActiveMQConnectionFactory.DEFAULT_USER;
private static final String PASSWORD=ActiveMQConnectionFactory.DEFAULT_PASSWORD;
private static final String URL=ActiveMQConnectionFactory.DEFAULT_BROKER_URL;
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageConsumer messageConsumer;
connectionFactory =new ActiveMQConnectionFactory(QueueConsumer2.USERNAME, QueueConsumer2.PASSWORD, QueueConsumer2.URL);
try {
connection =connectionFactory.createConnection();
connection.start();
session =connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
destination =session.createQueue("FirstQueue");
messageConsumer =session.createConsumer(destination);
messageConsumer.setMessageListener(new QueueListener());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
解释:直接给消息消费者传递队列监听器就可以了。
4.发布订阅模型解析
什么是发布订阅模型?
就是消息会发送一个主题,与队列类似,多个接收者都可以监听一个主题。消息不再是只投递给一个接收者,而是主题的所有订阅者都会接收到此消息的副本。(一对多的关系)。
举例:
就是我们平时都会买书,提前预购的人,相当于订阅了这本书,书一出版,这些人都会收到书籍。相当于一本书,很多人都会收到书。
java实现发布订阅模型
消息发布者:
/*消息发布者*/
public class TopicProducer {
//默认的连接用户名
private static final String USERNAME=ActiveMQConnectionFactory.DEFAULT_USER;
//默认的连接密码
private static final String PASSWORD=ActiveMQConnectionFactory.DEFAULT_PASSWORD;
//默认的连接地址
private static final String URL=ActiveMQConnectionFactory.DEFAULT_BROKER_URL;
//发送消息的条数
private static final int MessageNum=10;
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageProducer messageProducer;
//实例化连接工厂
connectionFactory =new ActiveMQConnectionFactory(TopicProducer.USERNAME, TopicProducer.PASSWORD, TopicProducer.URL);
//通过连接工厂获取连接
try {
connection =connectionFactory.createConnection();
connection.start();
session =connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
destination =session.createTopic("FirstTopic");
//创建消息生产者
messageProducer =session.createProducer(destination);
sendMessage(session,messageProducer);
session.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(connection !=null){
try {
connection.close();
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//发送消息
public static void sendMessage(Session session,MessageProducer messageProducer) throws Exception{
for(int i=0;i<TopicProducer.MessageNum;i++){
TextMessage message =session.createTextMessage("ActiveMQ发布的消息 "+i);
System.out.println("发布消息:ActiveMq发布的消息"+i);
messageProducer.send(message);
}
}
}
消息订阅者1监听器:
public class TopicListener implements MessageListener{
@Override
public void onMessage(Message message) {
// TODO Auto-generated method stub
try {
System.out.println("订阅者1收到的消息:"+((TextMessage)message).getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
消息订阅者1:
/*消息订阅者1*/
public class TopicConsumer1 {
private static final String USERNAME=ActiveMQConnectionFactory.DEFAULT_USER;
private static final String PASSWORD=ActiveMQConnectionFactory.DEFAULT_PASSWORD;
private static final String URL=ActiveMQConnectionFactory.DEFAULT_BROKER_URL;
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageConsumer messageConsumer;
connectionFactory =new ActiveMQConnectionFactory(TopicConsumer1.USERNAME, TopicConsumer1.PASSWORD, TopicConsumer1.URL);
try {
connection =connectionFactory.createConnection();
connection.start();
session =connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
destination =session.createTopic("FirstTopic");
messageConsumer =session.createConsumer(destination);
messageConsumer.setMessageListener(new TopicListener());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
消息订阅者2监听器:
public class TopicListener2 implements MessageListener{
@Override
public void onMessage(Message message) {
// TODO Auto-generated method stub
try {
System.out.println("订阅者2收到的消息:"+((TextMessage)message).getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
消息订阅者2:
/*消息订阅者2*/
public class TopicConsumer2 {
private static final String USERNAME=ActiveMQConnectionFactory.DEFAULT_USER;
private static final String PASSWORD=ActiveMQConnectionFactory.DEFAULT_PASSWORD;
private static final String URL=ActiveMQConnectionFactory.DEFAULT_BROKER_URL;
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageConsumer messageConsumer;
connectionFactory =new ActiveMQConnectionFactory(TopicConsumer2.USERNAME, TopicConsumer2.PASSWORD, TopicConsumer2.URL);
try {
connection =connectionFactory.createConnection();
connection.start();
session =connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
destination =session.createTopic("FirstTopic");
messageConsumer =session.createConsumer(destination);
messageConsumer.setMessageListener(new TopicListener2());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
注意:一定是先运行消息订阅者,然后再运行消息发布者(在生活中我们先预定书再等别人出书的过程)。
运行结果:
运行两个消息订阅者结果:
消息主题:FirstTopic、订阅者数量2、消息进队列0、消息出队列0。
运行消息发布者结果:
消息主题FirstTopic、消息订阅者数量2、消息进队列10条(因为消息发布者就发布10条)、消息出队列20条(由于是两个订阅者,每人取了10条,这里大概可以猜到这个出队列数是使用计数器的方式做的)。
补充:
消息接收的方式:
Session.AUTO_ACKNOWLEDGE : 当消费者成功的从receive方法返回的时候,或者从Listener中onMessage方法成功返回时候,会话自动确认客户收到的消息。
Session.CLIENT_ACKNOWLEDGE : 客户通过消息的acknowledge方法确认消息。但是需要注意的是,在这种模式中,确认是在会话层上进行的。确认一个被消费的消息将自动确认所有已被会话消费的消息。例如:如果一个消息消费者已经消费了10条消息。然后确认消费第5个信息时,那么所有10个消息都被确认。
Session.DUPS_OK_ACKNOWLEDGE : 这种方式是使会话迟钝地确认消息地提交。如果Provider失败,那么可能会导致一些重复的消息。如果是重复的消息,那么Provider必须把消息头的JMSRedelivered字段设置为true。