1.JMS思想:
SUN公司给我们提供了一组标准被Java API用于企业级的消息处理, 通过JMS可以在Java程序之间发送和接受消息以达到交换数据的目的,
异步通信实现了程序之间的松耦合的关系.
在异步处理的世界,我们可以把消息的发送比作一个邮局系统。比如我们要给某个人发送信件,我们只需准备好信件,把它投入邮局的邮箱即可,我们不必关心邮件如何送出、能否到达,邮局系统会保证信件最终送达到我们希望的接收者手中。和邮局系统类似,当一个应用向另一个应用发送消息,两个应用之间没有直接的关联,而是发送消息的应用把消息交给一个消息系统,由消息系统确保把消息传递给接收消息的应用。
在异步消息系统中有两个重要的角色:消息broker和destination。当一个应用发送一条消息,它会直接把它发送给消息broker,消息broker扮演的就是邮局,它会确保消息被传递到特定的destination。当我们邮寄信件时,信件的地址尤为重要,消息系统中的地址就是destination。不过与信件中的地址不同,destination中定义的不是接收者是谁,而是消息被放在消息broker的什么地方(具体指queue或者topic),destination其实更像邮局系统中的邮筒。
尽管存在各种各样的消息系统,每个消息系统都有各自的消息路由方式,但总体上有两种类型的destination:queue和topic,它们也各自关联着一种特定的消息处理模型:点对点(point-to-point/queue)和发布/订阅(publish/subscribe/topic)
1. 连接工厂(ConnectionFactory): 用来创建消息服务器的connection对象.
2. 连接(Connection): 代表一个与JMS提供者的活动连接.
3. 目的(Destination): 标识消息的发送和接收方式, 分为队列(Queue)和主题(Topic)两种.
4. 会话(Session): 接收和发送消息的会话线程.
5. 为了实现JMS独立于不同供应商MS的专有技术, weblogic JMS采用了受管对象(administratored object)的机制. 受管对象就是由消息服务器通过管理界面创建, 程序通过JNDI接口取得这些对象.weblogic 中的两种受管对象: connection factory, distination.
3.消息类型:
1. StreamMessage: 消息由串行化的Java对象组成, 必须按照设置时的顺序读取对象.
2. MapMessage: 消息由key/value对组成, 其中名称为string类型, 值为Java数据类型. 可以使用列举顺序读取该消息的值, 也可以通过名称无序地获取值。
3. TextMessage: 消息的主体为字符串, 这是最常用的消息类型.
4. ObjectMessage: 消息的主体为串行化的Java对象, 可以是自己定义的串行化的Java对象.
5. BytesMessage: 消息的主体是二进制数据.
4.代码实现:
1.PTP(point to point)模式
在点对点模型中,每个消息只有一个发送者和一个接收者。如下图所示:
在点对点模型中, 消息broker会把消息放入一个queue。当一个接收者请求下一个消息时,消息会被从queue中取出并传递给接收者。因为消息从queue中取出便会被移除,所以这保证了一个消息只能有一个接收者。
尽管消息队列中的每个消息只有一个接收者,但这并不意味着只能有一个接收者从队列获取消息,可以同时有多个接收者从队列获取消息,只不过它们只能处理各自接收到的消息。其实这就像在银行排队一样,排队的人可以看做一个个消息,而银行工作窗口便是消息的接收者,每个窗口服务完一个客户之后都会让队列中的“下一个”到窗口办理业务。
还有,如果多个接收者监听一个队列,我们是很难确定到底哪个接收者处理哪个消息的。不过这也不一定不好,因为这样就使得我们很方便的通过增加接收者来拓展应用处理能力了。
生产者QueueMsgSender
public class QueueMsgSender {
// Defines the JNDI context factory.
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
// Defines the JNDI provider url.
public final static String PROVIDER_URL = "t3://localhost:7001/";
// Defines the JMS connection factory for the queue.
public final static String CONNECTION_FACTORY_JNDI_NAME = "myJMSConnectionFactoryJNDIName";
// Defines the queue, use the queue JNDI name
public final static String QUEUE_JNDI_NAME = "myJMSQueueJNDIName";
private QueueConnectionFactory qconFactory;
private QueueConnection queueConnection;
private QueueSession queueSession;
private QueueSender queueSender;
private Queue queue;
private TextMessage textMessage;
private StreamMessage streamMessage;
private BytesMessage bytesMessage;
private MapMessage mapMessage;
private ObjectMessage objectMessage;
/**
* get the context object.
*
* @return context object
* @throws NamingException if operation cannot be performed
*/
private static InitialContext getInitialContext() throws NamingException {
Hashtable table = new Hashtable();
table.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
table.put(Context.PROVIDER_URL, PROVIDER_URL);
InitialContext context = new InitialContext(table);
return context;
}
/**
* Creates all the necessary objects for sending messages to a JMS queue.
*
* @param ctx JNDI initial context
* @param queueName name of queue
* @exception NamingException if operation cannot be performed
* @exception JMSException if JMS fails to initialize due to internal error
*/
public void init(Context ctx, String queueName) throws NamingException, JMSException {
qconFactory = (QueueConnectionFactory) ctx.lookup(CONNECTION_FACTORY_JNDI_NAME);
queueConnection = qconFactory.createQueueConnection();
queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
queue = (Queue) ctx.lookup(queueName);
queueSender = queueSession.createSender(queue);
textMessage = queueSession.createTextMessage();
streamMessage = queueSession.createStreamMessage();
bytesMessage = queueSession.createBytesMessage();
mapMessage = queueSession.createMapMessage();
objectMessage = queueSession.createObjectMessage();
queueConnection.start();
}
/**
* Sends a message to a JMS queue.
*
* @param message message to be sent
* @exception JMSException if JMS fails to send message due to internal error
*/
public void send(String message) throws JMSException {
// type1: set TextMessage
textMessage.setText(message);
// type2: set StreamMessage
streamMessage.writeString(message);
streamMessage.writeInt(20);
// type3: set BytesMessage
byte[] block = message.getBytes();
bytesMessage.writeBytes(block);
// type4: set MapMessage
mapMessage.setString("name", message);
// type5: set ObjectMessage
User user = new User();
user.setName(message);
user.setAge(30);
objectMessage.setObject(user);
queueSender.send(objectMessage);
}
/**
* read the msg from the console, then send it.
*
* @param msgSender
* @throws IOException if IO fails to send message due to internal error
* @throws JMSException if JMS fails to send message due to internal error
*/
private static void readAndSend(QueueMsgSender msgSender) throws IOException, JMSException {
BufferedReader msgStream = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter message(input quit to quit):");
String line = null;
boolean quit = false;
do {
line = msgStream.readLine();
if (line != null && line.trim().length() != 0) {
msgSender.send(line);
System.out.println("JMS Message Sent: " + line + "\n");
quit = line.equalsIgnoreCase("quit");
}
} while (!quit);
}
/**
* release resources.
*
* @exception JMSException if JMS fails to close objects due to internal error
*/
public void close() throws JMSException {
queueSender.close();
queueSession.close();
queueConnection.close();
}
/**
* test client.
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
InitialContext ctx = getInitialContext();
QueueMsgSender sender = new QueueMsgSender();
sender.init(ctx, QUEUE_JNDI_NAME);
readAndSend(sender);
sender.close();
}
}
消费者QueueMsgPublisher
public class QueueMsgReceiver implements MessageListener {
// Defines the JNDI context factory.
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
// Defines the JNDI provider url.
public final static String PROVIDER_URL = "t3://localhost:7001";
// Defines the JMS connection factory for the queue.
public final static String CONNECTION_FACTORY_JNDI_NAME = "myJMSConnectionFactoryJNDIName";
// Defines the queue, use the queue JNDI name
public final static String QUEUE_JNDI_NAME = "myJMSQueueJNDIName";
private QueueConnectionFactory qconFactory;
private QueueConnection queueConnection;
private QueueSession queueSession;
private QueueReceiver queueReceiver;
private Queue queue;
private boolean quit = false;
/**
* get the context object.
*
* @return context object
* @throws NamingException if operation cannot be performed
*/
private static InitialContext getInitialContext() throws NamingException {
Hashtable table = new Hashtable();
table.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
table.put(Context.PROVIDER_URL, PROVIDER_URL);
InitialContext context = new InitialContext(table);
return context;
}
/**
* Creates all the necessary objects for receiving messages from a JMS queue.
*
* @param ctx JNDI initial context
* @param queueName name of queue
* @exception NamingException if operation cannot be performed
* @exception JMSException if JMS fails to initialize due to internal error
*/
public void init(Context ctx, String queueName) throws NamingException, JMSException {
qconFactory = (QueueConnectionFactory) ctx.lookup(CONNECTION_FACTORY_JNDI_NAME);
queueConnection = qconFactory.createQueueConnection();
queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
queue = (Queue) ctx.lookup(queueName);
queueReceiver = queueSession.createReceiver(queue);
queueReceiver.setMessageListener(this);
// second thread: message reveive thread.
queueConnection.start();
}
/**
* implement from MessageListener.
* when a message arrived, it will be invoked.
*
* @param message message
*/
public void onMessage(Message message) {
try {
String msgStr = "";
int age = 0;
if (message instanceof TextMessage) {
msgStr = ((TextMessage) message).getText();
} else if (message instanceof StreamMessage) {
msgStr = ((StreamMessage) message).readString();
age = ((StreamMessage) message).readInt();
} else if (message instanceof BytesMessage) {
byte[] block = new byte[1024];
((BytesMessage) message).readBytes(block);
msgStr = String.valueOf(block);
} else if (message instanceof MapMessage) {
msgStr = ((MapMessage) message).getString("name");
} else if (message instanceof ObjectMessage) {
User user = (User) ((ObjectMessage) message).getObject();
msgStr = user.getName();
age = user.getAge();
}
System.out.println("Message Received: " + msgStr + ", " + age);
if (msgStr.equalsIgnoreCase("quit")) {
synchronized (this) {
quit = true;
this.notifyAll(); // Notify main thread to quit
}
}
} catch (JMSException e) {
throw new RuntimeException("error happens", e);
}
}
/**
* release resources.
*
* @exception JMSException if JMS fails to close objects due to internal error
*/
public void close() throws JMSException {
queueReceiver.close();
queueSession.close();
queueConnection.close();
}
/**
* test client.
* first thread(main thread)
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
InitialContext ctx = getInitialContext();
QueueMsgReceiver receiver = new QueueMsgReceiver();
receiver.init(ctx, QUEUE_JNDI_NAME);
// Wait until a "quit" message has been received.
synchronized (receiver) {
while (!receiver.quit) {
try {
receiver.wait();
} catch (InterruptedException e) {
throw new RuntimeException("error happens", e);
}
}
}
receiver.close();
}
}
2.publisher and subscriber - 发布订阅模式.
在发布/订阅模式中,消息是被发送到topic中的。就像queue一样,很多接收者可以监听同一个topic,但是与queue每个消息只传递给一个接收者不同,订阅了同一个topic的所有接收者都会收到消息的拷贝,如下图所示:
从发布/订阅的名字中我们也可看出,发布者发布一条消息,所有订阅者都能收到,这就是发布订阅模式最大的特性。对于发布者来说,它只知道将消息发布到了一个特定的topic,它不关心谁监听这个topic,这也就意味着它并不知道这些消息是被如何处理的。
生产者TopicMsgPublisher
public class TopicMsgPublisher {
// Defines the JNDI context factory.
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
// Defines the JNDI provider url.
public final static String PROVIDER_URL = "t3://localhost:7001/";
// Defines the JMS connection factory for the topic.
public final static String CONNECTION_FACTORY_JNDI_NAME = "myJMSConnectionFactoryJNDIName";
// Defines the topic, use the topic JNDI name
public final static String TOPIC_JNDI_NAME = "myJMSTopicJNDIName";
private TopicConnectionFactory tconFactory;
private TopicConnection topicConnection;
private TopicSession topicSession;
private TopicPublisher topicPublisher;
private Topic topic;
private TextMessage textMessage;
private StreamMessage streamMessage;
private BytesMessage bytesMessage;
private MapMessage mapMessage;
private ObjectMessage objectMessage;
/**
* get the context object.
*
* @return context object
* @throws NamingException if operation cannot be performed
*/
private static InitialContext getInitialContext() throws NamingException {
Hashtable table = new Hashtable();
table.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
table.put(Context.PROVIDER_URL, PROVIDER_URL);
InitialContext context = new InitialContext(table);
return context;
}
/**
* Creates all the necessary objects for sending messages to a JMS topic.
*
* @param ctx JNDI initial context
* @param topicName name of topic
* @exception NamingException if operation cannot be performed
* @exception JMSException if JMS fails to initialize due to internal error
*/
public void init(Context ctx, String topicName) throws NamingException, JMSException {
tconFactory = (TopicConnectionFactory) ctx.lookup(CONNECTION_FACTORY_JNDI_NAME);
topicConnection = tconFactory.createTopicConnection();
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
topic = (Topic) ctx.lookup(topicName);
topicPublisher = topicSession.createPublisher(topic);
textMessage = topicSession.createTextMessage();
streamMessage = topicSession.createStreamMessage();
bytesMessage = topicSession.createBytesMessage();
mapMessage = topicSession.createMapMessage();
objectMessage = topicSession.createObjectMessage();
topicConnection.start();
}
/**
* Sends a message to a JMS topic.
*
* @param message message to be sent
* @exception JMSException if JMS fails to send message due to internal error
*/
public void send(String message) throws JMSException {
// type1: set TextMessage
textMessage.setText(message);
// type2: set StreamMessage
streamMessage.writeString(message);
streamMessage.writeInt(20);
// type3: set BytesMessage
byte[] block = message.getBytes();
bytesMessage.writeBytes(block);
// type4: set MapMessage
mapMessage.setString("name", message);
// type5: set ObjectMessage
User user = new User();
user.setName(message);
user.setAge(30);
objectMessage.setObject(user);
topicPublisher.publish(objectMessage);
}
/**
* read the msg from the console, then send it.
*
* @param msgPublisher
* @throws IOException if IO fails to send message due to internal error
* @throws JMSException if JMS fails to send message due to internal error
*/
private static void readAndSend(TopicMsgPublisher msgPublisher) throws IOException, JMSException {
BufferedReader msgStream = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter message(input quit to quit):");
String line = null;
boolean quit = false;
do {
line = msgStream.readLine();
if (line != null && line.trim().length() != 0) {
msgPublisher.send(line);
System.out.println("JMS Message Sent: " + line + "\n");
quit = line.equalsIgnoreCase("quit");
}
} while (!quit);
}
/**
* release resources.
*
* @exception JMSException if JMS fails to close objects due to internal error
*/
public void close() throws JMSException {
topicPublisher.close();
topicSession.close();
topicConnection.close();
}
/**
* test client.
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
InitialContext ctx = getInitialContext();
TopicMsgPublisher publisher = new TopicMsgPublisher();
publisher.init(ctx, TOPIC_JNDI_NAME);
readAndSend(publisher);
publisher.close();
}
}
消费者TopicMsgSubscrier
public class TopicMsgSubscriber implements MessageListener {
// Defines the JNDI context factory.
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
// Defines the JNDI provider url.
public final static String PROVIDER_URL = "t3://localhost:7001";
// Defines the JMS connection factory for the topic.
public final static String CONNECTION_FACTORY_JNDI_NAME = "myJMSConnectionFactoryJNDIName";
// Defines the topic, use the topic JNDI name
public final static String TOPIC_JNDI_NAME = "myJMSTopicJNDIName";
private TopicConnectionFactory tconFactory;
private TopicConnection topicConnection;
private TopicSession topicSession;
private TopicSubscriber topicSubscriber;
private Topic topic;
private boolean quit = false;
/**
* get the context object.
*
* @return context object
* @throws NamingException if operation cannot be performed
*/
private static InitialContext getInitialContext() throws NamingException {
Hashtable table = new Hashtable();
table.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
table.put(Context.PROVIDER_URL, PROVIDER_URL);
InitialContext context = new InitialContext(table);
return context;
}
/**
* Creates all the necessary objects for receiving messages from a JMS topic.
*
* @param ctx JNDI initial context
* @param topicName name of topic
* @exception NamingException if operation cannot be performed
* @exception JMSException if JMS fails to initialize due to internal error
*/
public void init(Context ctx, String topicName) throws NamingException, JMSException {
tconFactory = (TopicConnectionFactory) ctx.lookup(CONNECTION_FACTORY_JNDI_NAME);
topicConnection = tconFactory.createTopicConnection();
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
topic = (Topic) ctx.lookup(topicName);
topicSubscriber = topicSession.createSubscriber(topic);
topicSubscriber.setMessageListener(this);
// second thread: message reveive thread.
topicConnection.start();
}
/**
* implement from MessageListener.
* when a message arrived, it will be invoked.
*
* @param message message
*/
public void onMessage(Message message) {
try {
String msgStr = "";
int age = 0;
if (message instanceof TextMessage) {
msgStr = ((TextMessage) message).getText();
} else if (message instanceof StreamMessage) {
msgStr = ((StreamMessage) message).readString();
age = ((StreamMessage) message).readInt();
} else if (message instanceof BytesMessage) {
byte[] block = new byte[1024];
((BytesMessage) message).readBytes(block);
msgStr = String.valueOf(block);
} else if (message instanceof MapMessage) {
msgStr = ((MapMessage) message).getString("name");
} else if (message instanceof ObjectMessage) {
User user = (User) ((ObjectMessage) message).getObject();
msgStr = user.getName();
age = user.getAge();
}
System.out.println("Message subscribed: " + msgStr + ", " + age);
if (msgStr.equalsIgnoreCase("quit")) {
synchronized (this) {
quit = true;
this.notifyAll(); // Notify main thread to quit
}
}
} catch (JMSException e) {
throw new RuntimeException("error happens", e);
}
}
/**
* release resources.
*
* @exception JMSException if JMS fails to close objects due to internal error
*/
public void close() throws JMSException {
topicSubscriber.close();
topicSession.close();
topicConnection.close();
}
/**
* test client.
* first thread(main thread)
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
InitialContext ctx = getInitialContext();
TopicMsgSubscriber subscriber = new TopicMsgSubscriber();
subscriber.init(ctx, TOPIC_JNDI_NAME);
// Wait until a "quit" message has been subscribed.
synchronized (subscriber) {
while (!subscriber.quit) {
try {
subscriber.wait();
} catch (InterruptedException e) {
throw new RuntimeException("error happens", e);
}
}
}
subscriber.close();
}
}