一.介绍
JMS Java 消息服务(Java Message Service,简称JMS)是企业级消息传递系统,紧密集成于Jboss Server 平台之中。企业消息传递系统使得应用程序能够通过消息的交换与其他系统之间进行通信。
JMS 支持两种消息传递模型:点对点(point-to-point,简称PTP)和发布/订阅(publish/subscribe,简称pub/sub)。这两种消息传递模型非常相似,只有以下区别:
PTP 消息传递模型规定了一条消息只能传递给一个接收方。Pub/sub 消息传递模型允许一条消息传递给多个接收方。
二.实例之一: Queue 消息的发送与接收(PTP 消息传递模型)
1.开发客户端发送消息的一般步骤:
(1) 得到一个JNDI 初始化上下文(Context);
例子对应代码:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
props.setProperty("java.naming.provider.url", "localhost:1099");
props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
InitialContext ctx = new InitialContext(props);
(2) 根据上下文来查找一个连接工厂TopicConnectFactory/ QueueConnectionFactory (有两种连接工厂,根据是topic/queue 来使用相应的类型);
例子对应代码:
QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory");
(3) 从连接工厂得到一个连接(Connect 有两种[TopicConnection/ QueueConnection]);
例子对应代码:conn = factory.createQueueConnection();
(4) 通过连接来建立一个会话(Session);
例子对应代码:session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
这句代码意思是:建立不需要事务的并且能自动接收消息收条的会话,在非事务Session 中,消息传递的方式有三种:
Session.AUTO_ACKNOWLEDGE :当客户机调用的receive 方法成功返回,或当MessageListenser 成功处理了消息,session 将会自动接收消息的收条。
Session.CLIENT_ACKNOWLEDGE :Session 对象依赖于应用程序对已收到的消息调用确认方法。一旦调用该方法,会话将确认所有自上次确认后收到的消息。该方法允许应用程序通过一次调用接收、处理和确认一批消息。
Session. DUPS_OK_ACKNOWLEDGE :一旦消息处理中返回了应用程序接收方法,Session 对象即确认消息接收,允许重复确认。就资源利用情况而言,此模式最高效。
(5) 查找目的地(Topic/ Queue);
例子对应代码:Destination destination = (Queue) ctx.lookup("queue/foshanshop");
(6) 根据会话以及目的地来建立消息制造者MessageProducer (扩展了QueueSender 和TopicPublisher 这两个基本接口)
例子对应代码:
MessageProducer producer = session.createProducer(destination);
TextMessage msg = session.createTextMessage("佛山人您好,这是我的第一个消息驱动Bean");//发送文本
producer.send(msg);
整个例子代码:
package com; import java.util.Properties; import javax.jms.BytesMessage; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MapMessage; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.QueueConnection; import javax.jms.QueueConnectionFactory; import javax.jms.QueueSession; import javax.jms.StreamMessage; import javax.jms.TextMessage; import javax.naming.InitialContext; public class QueueSender { public static void main(String[] args) { QueueConnection conn = null; QueueSession session = null; try { Properties props = new Properties(); Properties properties = new Properties(); properties.put("java.naming.factory.initial", //提供JNDI 服务器 "org.jnp.interfaces.NamingContextFactory"); properties.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); properties.put("java.naming.provider.url", "localhost:1099"); InitialContext ctx = new InitialContext(props); QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("QueueConnectionFactory"); conn = factory.createQueueConnection(); session = conn.createQueueSession(false,QueueSession.AUTO_ACKNOWLEDGE); Destination destination = (Queue) ctx.lookup("queue/foshanshop"); MessageProducer producer = session.createProducer(destination); // 发送文本 TextMessage msg = session .createTextMessage("您好,这是我的第一个消息驱动Bean"); producer.send(msg); // 发送Ojbect(对像必须实现序列化,否则等着出错吧) producer.send(session.createObjectMessage(new Man("美女", "北京和平里一号"))); // 发送MapMessage MapMessage mapmsg = session.createMapMessage(); mapmsg.setObject("no1", "北京和平里一号"); producer.send(mapmsg); // 发送BytesMessage BytesMessage bmsg = session.createBytesMessage(); bmsg.writeBytes("我是一个兵,来自老百姓".getBytes()); producer.send(bmsg); // 发送StreamMessage StreamMessage smsg = session.createStreamMessage(); smsg.writeString("我就爱流读写"); producer.send(smsg); } catch (Exception e) { System.out.println(e.getMessage()); } finally { try { session.close(); conn.close(); } catch (JMSException e) { e.printStackTrace(); } } } }2.Queue消息接收方:
代码如下:
package com; import java.io.ByteArrayOutputStream; import javax.ejb.ActivationConfigProperty; import javax.ejb.MessageDriven; import javax.jms.BytesMessage; import javax.jms.MapMessage; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.ObjectMessage; import javax.jms.StreamMessage; import javax.jms.TextMessage; @MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), //中destinationType 属性指定消息的类型为queue @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/foshanshop") }) //。destination 属性指定消息路径(Destination),消息驱动Bean 在发布时,如果路径(Destination)不存在,容器会自动创建,当容器关闭时该路径将被删除。 public class PrintBean implements MessageListener { public void onMessage(Message msg) { try { if (msg instanceof TextMessage) { TextMessage tmsg = (TextMessage) msg; String content = tmsg.getText(); this.print(content); } else if (msg instanceof ObjectMessage) { ObjectMessage omsg = (ObjectMessage) msg; Man man = (Man) omsg.getObject(); String content = man.getName() + " 家住" + man.getAddress(); this.print(content); } else if (msg instanceof MapMessage) { MapMessage map = (MapMessage) msg; String content = map.getString("no1"); this.print(content); } else if (msg instanceof BytesMessage) { BytesMessage bmsg = (BytesMessage) msg; ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); byte[] buffer = new byte[256]; int length = 0; while ((length = bmsg.readBytes(buffer)) != -1) { byteStream.write(buffer, 0, length); } String content = new String(byteStream.toByteArray()); byteStream.close(); this.print(content); } else if (msg instanceof StreamMessage) { StreamMessage smsg = (StreamMessage) msg; String content = smsg.readString(); this.print(content); } } catch (Exception e) { e.printStackTrace(); } } private void print(String content) { System.out.println(content); } }当一个消息到达queue/foshanshop 队列,就会触发onMessage 方法,消息作为一个参数传入,在onMessage 方法里面得到消息体并调用print 方法把消息内容打印到控制台上。
三.实例之二——Topic 消息的发送与接收(Pub/sub 消息传递模型)
Topic 消息允许多个主题订阅者接收同一条消息。
1.发送方同上类似:不同之处在:
TopicConnectionFactory factory = (TopicConnectionFactory) ctx .lookup("ConnectionFactory"); //连接工厂类型不同,应该为//TopicConnectionFactory
conn = factory.createTopicConnection(); //创建的连接也不同
session = conn.createTopicSession(false,TopicSession.AUTO_ACKNOWLEDGE);//打开的//会话也不同
Destination destination = (Topic) ctx.lookup("topic/student");
//查找目的地,应强制性转化为Topic类型
2.接收方:为了模拟topic 消息允许多个主题订阅者接收同一条消息,就写两个MDB来接收,大致与的点对点模式下的接收方相似。只是MDB上的注释不同,代码如下:
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
// destinationType 属性指定消息的类型为javax.jms.Topic
@ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/student") })
// destination 属性指定消息路径,以topic为上层上下文
四.问题解决
1.问题:javax.jms.JMSException: Error creating the dlq connection: XAConnectionFactory n ot bound at org.jboss.ejb3.mdb.DLQHandler.createService(DLQHandler.java:153) at org.jboss.system.ServiceMBeanSupport.jbossInternalCreate(ServiceMBean Support.java:260) at org.jboss.system.ServiceMBeanSupport.create(ServiceMBeanSupport.java: 188) at org.jboss.ejb3.mdb.MDB.setupDLQ(MDB.ja
解决:你可能破坏了jms配置,因为jms 要用数据库中上的信息,默认的是deploy/jms/* 默认的数据库是hsqldb,确保hsqldb数据库中的 jms相关的表存在且正常。如果采用默认的情况,则deploy下的hsqldb-ds.xml也不要删除,否则就会出现下面的错误。
如果还是无法解决,就重装下Jboss,恢复到默认配置,就没问题了。