第四章 应用WebLogic JMS服务
研究人:滕勇
文档简介:本文档通过图解的形式介绍了EJB的JMS实现机制以及如何在WebLogic里配置JMS服务。
在分布式企业级应用程序中,异步消息机制用于有效地协调各个部分的工作。 J2EE为我们提供了JMS和消息驱动Bean(Message-Driven Bean),用来实现应用程序各个部件之间的异步消息传递。
一、什么是消息系统?
通常一个消息系统允许分开的未耦合的应用程序之间可靠地异步通信。在企业应用时,需要一种异步的,非阻塞的消息传递。比如,一个客户端可能希望给一个服务器发送一个请求后,不在乎是否马上能得到回应。这样,客户端没有理由必须等待服务器处理请求。客户端应用程序在递交一个请求之后,只需确保请求到达服务器端后,就可以处理其他任务。通常,这是很高效的。消息系统提供了许多其他分布式对象计算模型没有的优点。它鼓励在消息产生者和使用者之间的"松耦合",在它们之间有很高程度的事务处理。对于使用者,它不在乎谁产生了消息,产生者是否仍在网络上以及消息是什么时候产生的。这就允许建立动态的,可靠的和灵活的系统。整个的子系统能被修改而不会影响系统的其他部分。
另外的优点包括:系统的高度可扩展性,容易与其他系统进行集成,以及高度的可靠性。由于可靠性和可扩展性,使得它们用于解决许多商业和科学计算问题。比如,消息系统是许多应用程序的基础,这些应用程序可以是工作流,网络管理,通信服务或供应链管理程序。在JAVA技术中,处理异步消息的能力是通过JMS来实现的。JMS最初设计是为了给传统的消息对象中间件提供一个标准的JAVA接口。而这些产品是在一个企业级应用程序中必须的。现在出现了许多支持JMS的纯JAVA的产品。
消息系统通常有两种消息类型。
1、发布/订阅(publish/subscribe)
发布/订阅消息系统支持一个事件驱动模型,消息产生者和使用者都参与消息的传递。产生者发布事件,而使用者订阅感兴趣的事件,并使用事件。产生者将消息和一个特定的主题(Topic)连在一起,消息系统根据使用者注册的兴趣,将消息传给使用者。
(Publish/Subscribe (Pub/Sub) Messaging)
2、点对点(Peer to peer)
在点对点的消息系统中,消息分发给一个单独的使用者。它维持一个"进入"消息队列。消息应用程序发送消息到一个特定的队列,而客户端从一个队列中得到消息。
(Point-to-Point (PTP) Messaging)
二、JMS简介
JMS 是一个企业级的消息系统,也称为面向消息的中间件。它允许应用程序通过消息交流进行通信。一条消息可以是一个请求,一个报告,也可以(或者)是一个事件,这种事件中包含了用来在不同应用程序间协调通信所需的信息。消息提供了一个抽象级别,使你能从应用程序代码中分离出目的系统的详细资料。Java 消息服务是一套访问企业级消息系统的标准API。详细地,JMS:
l 授权Java 应用程序共享一个消息系统以便进行消息交流。
l 提供一套标准的接口来创建、发送并且接收消息,简化应用程序的开发。
下面是WebLogic JMS 通信的图解。
(WebLogic JMS 通信)
如图所示,WebLogic JMS 从消息生产者应用程序那里接收消息,并把消息传递给消息消费者应用程序。
下面是WebLogic JMS 架构的图解:
(WebLogic JMS 架构)
1、JMS对象模型
下图显示了JMS对象,用于提供JMS客户端与JMS服务提供者相连的对象。
(JMS对象模型)
ConnectionFactory是一个客户端用来创建一个Connection的管理对象。由于在Connection创建时有授权和通信建立过程,因此这个对象是比较大的。
Destination对象将一个消息的目的和服务提供者有关的地址及配置信息包装起来。
Session是JMS实体,用来支持事务处理和异步消息消费。JMS并不需要客户端的代码用于异步消息消费或能处理多个并发消息。通常,事务的复杂性都由一个Session来封装。 一个Session是一个原子单位的工作,与数据库的事务一样,要实现多线程事务比较困难。Session提供了在一个线程编程模式下的并发的优点。
MessageProducer和MessageConsumer对象由Session对象创建。用于发送和接受消息。为了确保消息的传递,JMS服务提供者处理的消息都要处于PERSISTENT模式。PERSISTENT模式使得JMS提供者出问题后,也能让消息保存下来。
( Session,MessageProducer和MessageConsumer都不支持并发,而ConnectionFactory,Destination和Connection都支持并发。 )
2、JMS应用程序开发
JMS消息系统中,应用程序之间通信的关键是消息。因此使用JMS必须要先理解消息。 在JMS中,消息由三部分组成:
MESSAGE HEADER 用于识别消息,比如用于判断一个给定的消息是否是一个"订阅者"
PROPERITIES 用于与应用程序相关的,提供者相关的和可选项的信息
BODY 是消息的内容,支持几种格式,包括TextMessage(对String一个简单的封装)和ObjectMessage(对任意对象的封装,但必须支持序列化),也支持其他格式。
n TextMessage
一个TextMessage是一个String对象的封装。在只有文本对象传递时,是很有用的。它假设许多消息系统是建立在XML上的。从而TextMessage就可以成为包装它们的容器。
创建一个TextMessage对象很简单,如下面的代码:
TextMessage message=session.createMessage();
message.setText("Hello, world!");
n ObjectMessage
如名字所示,它是对一个JAVA对象的封装的消息。任何可序列化的JAVA对象都能用于ObjectMessage,如果必须将多个对象封装在一个消息里传递,可以使用Collection对象,来包括多个序列化对象。
下面是创建一个ObjectMessage
ObjectMessage message=session.createObjectMessage();
message.setObject(myObject);
创建一个JMS客户端程序
一个典型的JMS客户端由下面的几个基本步骤来创建:
n 创建一个到消息系统提供者的连接(Connection)
n 创建一个Session,用于接收和发送消息
n 创建MessageProducer和MessageConsumer来创建和接收消息
当完成了上述步骤后,一个消息产生者客户端将创建并发布消息到一个主题,而消息使用者客户端会接收与一个主题相关的消息。
① 创建一个Connection
一个Connection提供客户端对底层的消息系统的访问。并实现资源的分配和管理。通过使用一个ConnectionFactory来创建一个Connection,通常用JDNI来指定:
Connection message=new initialContext();
TopicConnectionFactory topicConnectionFactory=(TopicConnectionFactory);
topic = (Topic) jndiContext.lookup(topicName);
topicConnection =topicConnectionFactory.createTopicConnection();
②创建一个Session
Session是一个比较大的JMS对象,他提供了生产和消费消息的手段。用于创建消息使用者和消息产生者。
topicSession = topicConnection.createTopicSession(false , Session.AUTO_ACKNOWLEDGE);
两个参数用于控制事务和消息确认。
③定位一个Topic
用JDNI来定位一个Topic,Topic用于识别发送或接收的消息,在发布/订阅系统中。订阅者订阅一个给定的Topic,而发布者将它发布的消息与一个Topic相连。
下面是创建一个Topic "WeatherReport"
Topic weatherTopic=messaging.lookup("WeatherReport");
④启动Connection
在上面的初始化步骤之后,消息流是禁止的,用于防止在初始化时发生不可预料的行为。一旦初始化结束,必须让Connection启动消息系统。
topicConnection.start();
⑤创建一个消息产生者
在发布/订阅里,一个产生者发布消息到一个指定的Topic。下面的代码显示创建一个产生者,以及后续的建立和发布一个简单文本消息。
TopicPublisher publisher=session.createPublisher(weatherTopic);
TexeMessage message=session.createMessage();
message.setText("ssss");
publisher.publish(message);
下面是一个消息使用者的代码
topicConnection =topicConnectionFactory.createTopicConnection();
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
topicSubscriber = topicSession.createSubscriber(topic);topicListener = new MsgListener();
topicSubscriber.setMessageListener(topicListener);topicConnection.start();
三、消息驱动Bean简介
异步消息也可以由消息驱动Bean来实现。在EJB1.1规范中,定义了两种类型的EJB。分别是实体Bean(EntityBean)和会话Bean(SessionBean)。客户端通常是以同步的,阻塞方式来调用Bean的方法。消息驱动Bean将EJB和JMS的功能结合在一起。
正如前述,会话Bean通常实现商务逻辑,客户端不能共享一个会话Bean。实体Bean通常和一些在永久存储中的一些实体条目相对应的。这两种Bean通常都有REMOTE和HOME接口,用来与客户端交互。并且,这些交互都是同步的,阻塞方式进行的。比如,一个请求发送给一个Bean,通过阻塞式方法调用,服务器返回一个相应。调用者在收到返回后,才能进行下一步处理。消息驱动Bean通常配置成是一个特别的主题(topic)或队列的客户端,作为消息的使用者。但消息驱动Bean没有HOME和REMOTE接口。一个消息产生者将消息写入TOPIC或队列时,并不知道使用者是一个消息驱动Bean。这就允许集成一个分布式的计算系统时,有很大的灵活性。消息驱动Bean没有会话性质的状态,所有的实例在不处理请求时是相同的,这与无状态会话Bean是类似的。将Bean的实例放在缓冲池里,也是高效处理消息驱动Bean的一种方法。一个消息驱动Bean必须间接或直接地从javax.ejb.MessageDrivenBean接口继承而来。这个接口是由javax.jms.MessageListener继承而来。这个方法的一个参数是javax.jms.Message。可以是任何有效的JMS消息类型。方法的申明中并不包含一个thrown语句。因此在消息处理中,不会仍出应用程序异常。当容器接收到消息时,它首先是从一个缓冲池里得到现成的一个消息驱动Bean,然后,如果配置文件需要的,容器还要设置一个和事务处理上下文的一个联系。当这些管理任务完成时,接收到的消息传递给onMessage()方法。一旦方法完成,事务确认或返回,Bean又被重新放回到缓冲池。
ejbRemove()在把消息驱动Bean从任何存储上删除时调用。并进行清楚操作和垃圾收集。必须在ejbRemove()方法中释放所有Bean的实例用到的资源。
setMessageDrivenConnection()方法只有一个参数-javax.ejb.MessageDrivenContext的实例。MessageDrivenContext类与在实体和会话Bean中的上下文类似。当一个Bean的实例创建时,容器传入Bean用的上下文。上下文中得到环境信息的方法,以及JTA UserTranscation类,用于Bean管理事务处理的场合。
另外,Bean提供者必须提供一个ejbCreate()方法(无参数),用于在EJB服务器对Bean进行设置。Bean实例可以在ejbCreate()方法中取得任何需要的资源。
消息驱动Bean大大地简化了创建一个JMS使用者,创建和配置一个JMS消息使用者这些功能都交由EJB容器来做了。开发人员只需简单地实现消息驱动Bean的接口,配置给EJB服务器,用来创建一个接收消息的商业逻辑部件。
四、一个JMS应用实例(代码来自试验项目)
功能描述
顾客在一个电子商务网站如果决定购买某一商品,则进入"展示购物篮商品"页面(ShowQuoteServlet.java),点击"Submit Order"按钮下订单,此项操作将调用
(下订单)
CartBean.java(购物篮对象)的purchase()方法,在该方法中将实例化一个订单对象(Order),产生一个Order ID,然后将Order ID通过sendMessage()方法(亦即MessageSender.java类的同名方法)作为一条"消息"发送给JMS服务器,然后,由"订单处理"(OrderProcessorBean.java)消息驱动Bean进行接受,处理完转帐等事宜后,将该订单的标志字段设为"approved",从而完成一次交易。顾客将看到如下的成功信息:
(购买成功)
代 码
1、"展示购物篮商品"页面(ShowQuoteServlet.java)
… …
/*
*如果顾客点击"Submit Order"按钮
*/
if (request.getParameter("Order") != null) {
/*
* 将购物篮转化为订单,完成付款功能(应用JMS)
*/
String orderId = cart.purchase();
/*
* 将orderID放入 request中以便JSP 能够得到
*/
request.setAttribute("orderID", orderId );
/*
* 清除购物篮,以便顾客能购继续购物
*/
cart.clear();
/*
* 转向成功页
*/
this.getServletContext().getRequestDispatcher("/receipt.jsp").forward(request, response);
return;
}
… …
2、CartBean.java(购物篮对象)的purchase()方法和sendMessage()方法
… …
/**
* 购买购物篮中的商品。
购物篮对象将在数据库里创建一个
* 订单。
*
* @返回 Order ID
*/
public String purchase() {
try {
/*
* 锁定 order home 对象
*/
OrderHome home=getOrderHome();
/*
* 创建order
*/
String orderID = makeUniqueID();
Order order=home.create(orderID, owner,"unverified", subTotal,taxes);
/*
* 创建 orderlineitems(订购商品列表)对象
*/
Vector orderItems=addOrderLineItems(order);
/*
* 将orderlineitems存入Order
*/
order.setLineItems(orderItems);
/*
* 发送JMS消息到topic
*/
sendMessage(orderID);
/*
* 返回 order ID
*/
return orderID;
} catch (Exception e) {
throw new EJBException(e);
}
}
/*
*
*发送JMS消息到topic
*@param JMS message, 在这里是 orderId
*/
private void sendMessage(String message) throws Exception{
try{
MessageSender sender=new MessageSender();
sender.sendMessage(message);
}catch(Exception ex){
throw ex;
}
}
… …
3、MessageSender.java类
package examples;
import java.io.*;
import java.util.*;
import javax.transaction.*;
import javax.naming.*;
import javax.jms.*;
/*
*
* 这个类发送JMS Text message 到topic
*/
public class MessageSender{
/*
* 定义 JNDI context factory。
*/
public final static String JNDI_FACTORY="WebLogic.jndi.WLInitialContextFactory";
/*
* 定义 JMS connection factory JNDI 名。
*/
public final static String JMS_FACTORY="jasmine.examples.jms.TopicConnectionFactory";
/*
* 定义JMS topic JNDI 名。
*/
public final static String TOPIC="jasmine.examples.jms.orderProcess";
/*
* 定义Server URL
*/
public final static String URL="t3://localhost:7001";
/*
* 定义JMS connection factory
*/
protected TopicConnectionFactory tconFactory;
/*
* 定义JMS topic connection
*/
protected TopicConnection tcon;
/*
* 定义 JMS topic session
*/
protected TopicSession tsession;
/*
* 定义 JMS topic publisher
*/
protected TopicPublisher tpublisher;
/*
* 定义 JMS topic
*/
protected Topic topic;
/*
* 定义 JMS TextMessage
*/
protected TextMessage msg;
/*
*
* 为发送messages 到一个JMS Topic
* 创建所有必要的对象。
*
* @param ctx JNDI initial context
* @param topicName name of topic
* @exception NamingExcpetion if problem occurred with the JNDI context interface
* @exception JMSException if JMS fails to initialize due to internal error
*
*/
public void init(Context ctx, String topicName)
throws NamingException, JMSException{
/*
* 锁定 Topic connection factory JNDI
*/
tconFactory = (TopicConnectionFactory) ctx.lookup(JMS_FACTORY);
/*
* 创建一个 topic connection factory
*/
tcon = tconFactory.createTopicConnection();
/*
* 创建一个 topic session
*/
tsession = tcon.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
/**
* 锁定Topic JNDI
*/
topic = (Topic) ctx.lookup(topicName);
/**
* 创建一个 publisher
*/
tpublisher = tsession.createPublisher(topic);
/**
* 创建Textmessage
*/
msg = tsession.createTextMessage();
tcon.start();
}
/**
* 创建所有必要的JMS 对象同时发送一个message 到一个JMS topic.
* @params message message to be sent
* @exception JMSException if JMS fails to send message due to internal error
*
*/
public void sendMessage(String message) throws JMSException {
try{
InitialContext ctx= getInitialContext(URL);
init(ctx,TOPIC);
send(message);
}catch(Exception ex){
throw new JMSException( ex.getMessage());
}finally{
close();
}
}
/**
* 发送一个message 到一个JMS topic。
*
* @params message message to be sent
*/
public void send(String value)
throws JMSException {
msg.setText(value);
tpublisher.publish(msg);
}
/**
* 关闭 JMS 对象。
*
* @exception JMSException if JMS fails to close objects due to internal error
*/
public void close()
throws JMSException {
tpublisher.close();
tsession.close();
tcon.close();
}
/**
* 返回应用服务器初始化环境上下文信息。
*/
protected static InitialContext getInitialContext(String url)
throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
env.put("WebLogic.jndi.createIntermediateContexts", "true");
return new InitialContext(env);
}
/**
* main() 方法。用于测试该类。
*
* @param args WebLogic Server URL
* @exception Exception if operation fails
*/
public static void main(String[] args)
throws Exception{
if (args.length != 1) {
System.out.println("Usage: java examples.jms.topic.TopicSend WebLogicURL");
return;
}
InitialContext ic = getInitialContext(args[0]);
MessageSender sender = new MessageSender();
sender.init(ic, TOPIC);
sender.send("123456");
sender.close();
}
}
4、"订单处理"(OrderProcessorBean.java)消息驱动Bean
package examples;import javax.ejb.*;
import javax.jms.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
/**
* 此消息驱动(message-driven) bean接受JMS messages
* 以便实现购买过程。届时将执行order processing。
*/
public class OrderProcessorBean implements MessageDrivenBean, MessageListener {
protected MessageDrivenContext ctx;
/**
* 这是消息驱动(message-driven)beans 拥有的一个商业方法。
* 在此我们执行真正的order processing
*/
public void onMessage(Message msg) {
TextMessage tm = (TextMessage) msg;
try {
String orderID = tm.getText();
Context ctx = new InitialContext();
OrderHome home = (OrderHome)
PortableRemoteObject.narrow(
ctx.lookup("OrderHome"), OrderHome.class);
Order order = home.findByPrimaryKey(orderID);
/*
* 在此我们可以执行其他的任务:
*
* - 检查用户的信用卡credit并进行划帐操作。
*
* - 检查库存确保商品可卖。
*
* - 检查送货的相关信息。
*
* - 发送email认证信息
*
* 在此例中,我们将仅仅把 order的 status 改变为
* "approved".
*/
order.setStatus("approved");
}
catch (Exception e) {
e.printStackTrace();
throw new EJBException(e);
}
}
/**
* 使该bean 实例与一个特别的context发生联系。
* 我们可以在此 Context 中查询到环境信息。
*/
public void setMessageDrivenContext(MessageDrivenContext ctx) {
this.ctx = ctx;
}
/**
* 在生命周期的初始服务器container将调用此方法。
* 我们将做一些初始化工作。
*/
public void ejbCreate() {
System.err.println("ejbCreate()");
}
/**
* 服务器container调用此方法清除Bean。
*/
public void ejbRemove() {
System.err.println("ejbRemove()");
}
}
5、"订单处理"(OrderProcessorBean.java)消息驱动Bean的配置文件代码
① ejb-jar.xml
… …
OrderProcessor
examples.OrderProcessorBean
Container
javax.jms.Topic
… …
… …
② WebLogic-ejb-jar.xml
… …
OrderProcessor
100 10
jasmine.examples.jms.orderProcess
OrderProcessorBean
… …
五、在WebLogic 7 中配置JMS服务
创建JMS专用JDBC Connection Pool *
默认状态下,WebLogic的JMS服务支持以下数据库:
n Pointbase
n Microsoft SQL (MSSQL) Server
n Oracle
n Sybase
n Cloudscape
n Informix
n IBM DB2
n Times Ten
本例中以Microsoft SQL (MSSQL) Server为例。创建空的数据库"JMSDB"。
① 启动服务器。打开IE,在地址栏中输入:
② 输入用户名和密码
③ 在左边的目录树中选中Services->JDBC->Connection Pools,单击右侧的Configure a new JDBC Connection Pool.,输入以下信息:
Configuration->General页:
Name = JMS JDBC Pool
URL = jdbc:WebLogic:mssqlserver4:JMSDB@localhost
Driver classname = WebLogic.jdbc.mssqlserver4.Driver
Properties : user = sa
password = <- sa的密码(此例为空)
单击Create建立连接池。
Targets->Server页:将ty(服务器名称)移至右侧的列表中,但击单击Apply。
(* 注:JMS服务不能与你的EJB组件共用一个连接池,否则发布EJB组件时会失败。)
创建JDBC Store (为了在数据库里存储持久稳定的messages )
① 点击JMS -> Stores 标记, 然后点击右边面板中的"Configure a new JMSJDBCStore"。新建一个JMSJDBCStore。
② 填写名称和选择数据连接池。
③ 创建成功后,左边会出现该Store。
创建JMS Template(以便定义相似属性设置的不同destinations )
① 点击左面面板的Templates选项,然后点击右面面板上的" Configure a new JMS Template"。
② 在General 页面,填写template 名称,然后点击"Create"。
③ Thresholds & Quotas,,Override和Redelivery选择默认值即可。
配置 JMS Server
① 点击左面面板的JMS Server选项,然后点击右面面板上的"Configure a new JMSServer"。
② 在General 页面,填写名称,选择一个 Store ,选择一个Paging Store (如果创建的话),选择一个Template ,然后点击"Create"。
③ Thresholds & Quotas 选择默认值即可。
④ 在Targets面板,从下拉列表中选择应用服务器"ty",点击"Apply"。
创建JMS Destinations,queues (Point-To-Point) 或 topics (Pub/Sub),此例为topics
① 点击左面面板的JMS Server选项下的刚刚建立的Server下的Destinations,然后点击右面面板上的"Configure a new JMSTopic"。
② 在General 页面,填写名称,JNDI名称等选项然后点击"Create"。
③ Thresholds & Quotas, Override, Redelivery, 和 Multicast (topics only) 选择默认值即可。创建完成后如图所示--
创建Connection Factory(以便客户端能够建立JMS连接)
① 点击左面面板的JMS Connection Factory选项,然后点击右面面板上的"Configure a new JMS Connection Factory"。
② 在General 页面,填写名称jasmine.examples.jms.TopicConnectionFactory,JNDI名称jasmine.examples.jms.TopicConnectionFactory,其他选项默认即可,然后点击"Create"。
③ 在Targets面板,选择应用服务器"ty",点击"Apply"。--
至此,JMS服务配置基本完成,如果你查看JMSDB数据库,会发现WebLogic已经建立了两个表用于存储JMS数据。
(MS SQL Server数据库)
本文地址:http://com.8s8s.com/it/it17940.htm