消息驱动 Bean 全面解析与实践
1. 消息驱动 Bean 概述
消息驱动 Bean(Message-Driven Beans,MDB)在 EJB 2.0 规范中被引入,它降低了构建基于消息的应用程序的复杂性。消息驱动 Bean 只需实现
onMessage()
方法,以对收到的消息做出适当响应,容器会处理实现和包装 Java 消息服务器(JMS)所需的所有底层工作。简单来说,消息驱动 Bean 将 JMS 功能封装成一个简单的 Bean 组件。
消息可以由客户端应用程序、网页或其他 Bean 发起,也可以被它们中的任何一个消费。消息驱动 Bean 本质上是一个消息消费者,实现了一些业务逻辑。它会向自己选择的 JMS 队列或主题进行注册,然后实现
MessageListener
接口,最后等待消息的到来。消息驱动 Bean 的工作方式与无状态会话 Bean 类似,一个实例可以被 Bean 的多个客户端共享。
消息驱动 Bean 实现的设计模型有三个基本规则来定义其交互:
1. 消息生产者创建消息并将其发送到指定的主题。
2. 消息消费者订阅主题以接收消息。
3. 消息从主题传递到请求订阅者。
2. 点对点与发布/订阅模型
2.1 点对点模型
点对点通信模型适用于消息仅由单个消费者消费的情况。例如,系统通知客户端已处理的订单,该订单只能由一个客户端接收和处理。在这个模型中,消息生产者将消息发送到给定的队列,每个队列在容器的命名服务中都有一个唯一的名称,消息由队列消费,最后队列将消息传递给一个已注册的客户端。
2.2 发布/订阅模型
发布/订阅模型通常用于一般广播。例如,库存管理系统定期向多个客户端传输当前库存水平。在这个模型中,消息服务器定义一个主题,每个主题都有一个用于处理消息的主题。消息由消费者产生并发送到给定的主题,主题消费消息,然后将消息传递给所有已注册接收该消息的各方。
需要注意的是,与标准 JMS 监听器相比,消息驱动 Bean 的一个限制是,给定的消息 Bean 部署只能与一个队列或主题关联。如果应用程序需要单个 JMS 消费者处理来自多个队列或主题的消息,则必须使用标准 JMS 消费者,或部署多个消息驱动 Bean 类。
3. 编写消息驱动 Bean
与 JMS 实现相比,编写消息驱动 Bean 的任务得到了简化,只需要创建一个 Bean 类。消息驱动 Bean 没有远程/本地主接口或远程/本地接口,而是由发布者通过消息进行通信。构建消息驱动 Bean 需要完成以下任务:
1. 创建一个实现
javax.ejb.MessageDrivenBean
接口的类。
2. 实现
javax.jms.MessageListener
接口。
3. 创建一个无参数的公共构造函数。
4. 实现无参数的
ejbCreate()
方法,该方法应声明为公共的,返回类型必须为
void
,并且必须声明任何应用程序异常。
3.1 实现接口
javax.ejb.MessageDrivenBean
接口只包含两个必须实现的方法:
package javax.ejb;
public abstract interface MessageDrivenBean extends EnterpriseBean {
// Methods
void ejbRemove() throws EJBException;
void setMessageDrivenContext(MessageDrivenContext messageDrivenContext) throws EJBException;
}
实现这个接口是为了让消息驱动 Bean 由容器管理。
MessageListener
接口只实现了处理传入消息的方法:
package javax.jms;
public abstract interface MessageListener {
// Methods
void onMessage(Message message);
}
3.2 实现必需的方法
容器在创建或移除 Bean 类的实例时会调用消息驱动 Bean 的
ejbCreate()
和
ejbRemove()
方法。与其他 EJB 类型一样,Bean 类中的
ejbCreate()
方法应该准备 Bean 操作所需的任何资源,
ejbRemove()
方法应该释放这些资源,以便在容器移除实例之前将其释放。消息驱动 Bean 还应该在
ejbRemove()
方法之外执行某种定期清理例程,因为不能保证在所有情况下都会调用
ejbRemove()
方法(例如,如果 EJB 抛出运行时异常)。
3.3 处理消息
消息驱动 Bean 的
onMessage()
方法执行 EJB 的所有业务逻辑。当 EJB 关联的 JMS 队列或主题收到消息时,WebLogic Server 会调用
onMessage()
方法,并将完整的 JMS 消息对象作为参数传递。消息驱动 EJB 有责任解析消息并在
onMessage()
方法中执行必要的业务逻辑。要确保业务逻辑考虑到异步消息处理,例如,不能假设 EJB 会按照客户端发送消息的顺序接收消息。
以下是一个消息驱动 Bean 的示例代码:
package messagebean;
import java.text.*;
import java.util.*;
import javax.ejb.*;
import javax.jms.*;
import javax.naming.*;
/**
* The MessageBean class is a message-driven bean. It implements
* the javax.ejb.MessageDrivenBean and javax.jms.MessageListener
* interfaces. It defines a constructor and the methods
* setMessageDrivenContext, ejbCreate, onMessage, and
* ejbRemove.
*/
public class EmployeeMDBBean implements MessageDrivenBean,
MessageListener {
private transient MessageDrivenContext mdc = null;
private Context context;
public EmployeeMDBBean() {
}
public void setMessageDrivenContext(MessageDrivenContext mdc) {
this.mdc = mdc;
}
public void ejbCreate() {
}
public void onMessage(Message inMessage) {
try {
if (inMessage instanceof MapMessage) {
MapMessage map = (MapMessage) inMessage;
System.out.println("Fire Notice");
System.out.println("Name:" + map.getString("name"));
sendNote(map.getString("Email"));
} else {
System.out.println("Wrong message type");
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
private void sendNote(String recipient) {
try {
Context initial = new InitialContext();
javax.mail.Session session = (javax.mail.Session)
initial.lookup("java:comp/env/MailSession");
javax.mail.Message msg = new javax.mail.internet.MimeMessage(session);
msg.setFrom();
msg.setRecipients(javax.mail.Message.RecipientType.TO,
javax.mail.internet.InternetAddress.parse
(recipient, false));
msg.setSubject("Just letting you know!");
DateFormat dateFormater = DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.SHORT);
Date timeStamp = new Date();
String messageText = "You need to pack up today is your last day!!!!" +
'\n' + "Your kind boss";
msg.setText(messageText);
msg.setSentDate(timeStamp);
javax.mail.Transport.send(msg);
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
public void ejbRemove() {
System.out.println("EmployeeMDBBean.ejbRemove() called.");
}
}
2.4 部署描述符
与会话 Bean 和实体 Bean 一样,部署描述符描述了 Bean 与容器之间的交互。它不仅描述了 Bean 与容器的关系,还描述了所需的资源。以下是消息驱动 Bean 的部署描述符示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.
//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<message-driven>
<display-name>EmployeeMDB</display-name>
<ejb-name>EmployeeMDB</ejb-name>
<ejb-class>messagebean.EmployeeMDBBean</ejb-class>
<transaction-type>Bean</transaction-type>
<acknowledge-mode>Auto-acknowledge</acknowledge-mode>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
<resource-ref>
<description />
<res-ref-name>MailSession</res-ref-name>
<res-type>javax.mail.Session</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-env-ref>
<description />
<resource-env-ref-name>FireQueue</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
</resource-env-ref>
</message-driven>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>EmployeeMDB</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
4. 测试消息驱动 Bean
测试消息驱动 Bean 与测试会话 Bean 或实体 Bean 类似,需要开发一个客户端,客户端需要向队列产生消息。以下是一个基于控制台的测试应用程序示例:
package messagebean;
import javax.jms.*;
import javax.naming.*;
/**
* <p>Title: </p>
* <p>Description: </p>
* <p>Copyright: Copyright (c) 2002</p>
* <p>Company: </p>
* @author unascribed
* @version 1.0
*/
public class MessageClient {
public static void main(String[] args) {
Context cntx = null;
QueueConnectionFactory qcf = null;
QueueConnection qcon = null;
QueueSession qses = null;
Queue q = null;
QueueSender qs = null;
MapMessage message = null;
final int MSG_CNT;
if ((args.length < 1)) {
System.out.println("Usage: MessageClient QueueName");
}
try {
cntx = new InitialContext();
qcf = (QueueConnectionFactory) cntx.lookup(
"java:comp/env/QueueConnectionFactory");
q = (Queue) cntx.lookup(args[0]);
qcon = qcf.createQueueConnection();
qses = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
qs = qses.createSender(q);
message = qses.createMapMessage();
message.setString("name", "Bob the Builder");
message.setString("email", "bob@builder.com");
qs.send(message);
} catch (NamingException ex) {
ex.printStackTrace();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
5. 使用 JBuilder 构建消息驱动 Bean
使用 JBuilder 构建消息驱动 Bean 可以通过 EJB 设计器来完成,它提供了创建 Bean 和构建部署描述符的功能。具体步骤如下:
1. 在 EJB 设计器中右键单击并创建一个新的消息驱动 Bean。
2. 配置 Bean 的参数和名称。
3. 编辑消息驱动 Bean 的部署描述符。
以下是消息 Bean 部署描述符中各属性的说明表格:
| 属性 | 描述 | 示例 |
| ---- | ---- | ---- |
| Bean Name | 为消息驱动 Bean 提供的名称 | EmployeeMDB |
| Transaction Type | 指定 Bean 的事务管理类型,需要决定是由 Bean 还是容器来管理事务处理 | Bean |
| Acknowledge Mode | 仅当事务类型值为 Bean 时有效。自动确认模式定义所有消息都被确认,并检查以确保不会对重复消息进行操作。Dups-ok-acknowledge 表示所有消息都被确认,并且仍然包含所有重复消息 | Auto-acknowledge |
| Message Selector | 告知服务器哪些消息将被传递给给定的消费者,选择器本身使用包含 SQL QL 的字符串定义 | Price between 100.0 and 200.0 |
| Connection Factory Name | 连接工厂为队列或主题创建消费者代理,选择与队列、队列连接工厂或主题、主题连接工厂匹配的工厂 | Serial:/jms/qcf. 这是 SonicMQ(Borland 应用服务器的 JMS 实现)中 QueueConnectionFactory 的 JNDI 名称 |
| Destination Name | 每个创建的主题或队列在 JNDI 命名空间中都有一个唯一的名称,此属性指定消息驱动 Bean 监听的 JNDI 注册队列或主题,这是消息驱动 Bean 实例消费消息的 JMS 目的地 | orderConfirm |
| Destination Type | 此目的地类型需要与关联的工厂匹配,选择可以是 javax.jms.Queue 或 javax.jms.Topic,如果选择主题,则启用持久性 | Javax.jms.Queue |
| Subscription Durability | 持久性是指订阅者即使在不活动时也不会丢失传入的消息。换句话说,如果订阅者不活动,消息将被存储和维护,直到订阅者重新连接到主题 | NonDurable |
| Initial Pool Size | 容器应默认创建的消息驱动 Bean 实例数量 | 0 |
| Maximum Pool Size | 在任何给定时间池中应有的最大消息驱动 Bean 数量,值为 0 表示最大池大小不受限制 | 0 |
-
实现
onMessage()方法以包含消费消息的业务流程。 - 创建一个客户端应用程序来产生消息。
6. 设计准则
6.1 何时使用消息驱动 Bean
会话 Bean 和实体 Bean 允许同步发送和接收 JMS 消息,但不能异步接收。为避免占用服务器资源,可能不希望在服务器端组件中使用阻塞式同步接收。若要异步接收消息,可使用消息驱动 Bean。
6.2 为何使用消息驱动 Bean 而非 JMS
消息驱动 Bean 是企业计算近年来令人兴奋且强大的补充,它允许通过 JMS 实现接收的消息由简单而强大的组件模型处理。而且,消息驱动 EJB 有可能成为处理任何类型消息的事实上的组件模型,而不仅仅是 JMS 传递的消息。
JMS 消息旨在跨 JMS 实现实现互操作性,这意味着 JMS 消息可能在底层有专有实现,但内容可以透明地转换为另一个专有 JMS 实现,而不会以任何方式影响消息消费者。由于这种转换可以在消息传递给客户端之前从一个 JMS 实现转换到另一个,任何任意消息格式都可以转换为 JMS 消息并传递给消息驱动 Bean。
也就是说,只要应用服务器有将这些协议转换为 JMS 消息的功能,消息驱动 Bean 就可以处理通过电子邮件、HTTP、FTP 或任何其他协议发送的消息。这为一个标准、简单、可移植的组件模型打开了大门,该模型可以处理任何协议传递的任何消息。如果消息是用 XML 等开放、可扩展的语言定义的,那么在松散耦合系统中可以实现前所未有的互操作性。
7. 实践案例
7.1 场景描述
假设要处理来自多个不同来源(如销售人员、个人或企业)的课程确认消息。
7.2 解决方案
使用消息驱动 Bean 提供一种异步方法来处理注册和确认。以下是具体实现步骤:
7.2.1 构建消息驱动 Bean
- 打开相关解决方案。
- 在 EJB 设计器中右键单击并创建一个新的消息驱动 Bean。
-
配置 Bean 的属性:
| 属性 | 值 |
| ---- | ---- |
| Bean name | ConfirmationProcessor |
| Transaction type | Bean |
| Acknowledge mode | Auto-acknowledge |
| Message selector | |
| Destination name | |
| Destination type | javax.jms.Queue |
| Initial pool size | 0 |
| Connection factory name | | -
点击“Classes and Packages”按钮将包名更改为
com.sams.chalktalk.beans。
7.2.2 实现消息处理
业务逻辑由 MDB 的
onMessage()
方法触发,当 JMS 队列或主题收到消息时,容器会调用该方法。以下是实现
onMessage()
处理的步骤:
1. 找到
ConfirmationProcessor
Bean 中的
onMessage()
方法。
2. 实现以下代码来处理消息:
public void onMessage(Message msg) {
try {
MapMessage confMsg = (MapMessage) msg;
int studentPK = confMsg.getInt("StudentID");
int coursePK = confMsg.getInt("CourseID");
int schedulePK = confMsg.getInt("ScheduleID");
double price = confMsg.getDouble("Price");
String creditCardNum = confMsg.getString("CreditCard");
Date creditCardExp = new Date(confMsg.getLong("CreditCardExp"));
String creditCardType = confMsg.getString("CreditCardType");
LocalRegisterHome resHome = (LocalRegisterHome)
jndiContext.lookup("java:comp/env/ejb/LocalRegisterHome");
resHome.register(studentPK,coursePK,schedulePK);
Integer confNum = new
Integer(resHome.processPayment(creditCardNum,creditCardExp,
creditCardExp,studentPK));
//deliver confirmation
Queue queue = (Queue) confMsg.getJMSReplyTo();
QueueConnectionFactory fact = (QueueConnectionFactory)
jndiContext.lookup("java:comp/env/jms/QueueFactory");
QueueConnection connect = fact.createQueueConnection();
QueueSession session = connect.createQueueSession(false,0);
QueueSender sender = session.createSender(queue);
ObjectMessage message = session.createObjectMessage();
message.setObject(confNum);
sender.send(message);
connect.close();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
- 向消息驱动 Bean 添加成员变量:
Context jndiContext;
-
找到
ejbRemove()方法并添加以下代码:
public void ejbRemove() {
jndiContext.close();
messageDrivenContext = null;
}
- 编译并保存 Bean。
- 将新创建的 Bean 部署到应用服务器容器。
7.2.3 创建 JMS 客户端
SimpleMessageClient
向
ConfirmationProcessor
监听的队列发送消息,客户端首先要定位连接工厂和队列。创建该客户端的步骤如下:
1. 创建一个 JMS 客户端应用程序,以下是实现可运行类的框架:
package com.sams.chalktalk.client;
public class Untitled1 {
public static void main(String[] args) {
}
}
- 实现与 JMS 的连接以及注册课程的消息:
package com.sams.chalktalk.client;
import javax.naming.*;
import javax.jms.*;
public class SimpleMessageClient {
public static void main(String[] args) {
Context cntx = null;
QueueConnectionFactory qcf = null;
QueueConnection qcon = null;
QueueSession qses = null;
Queue q = null;
QueueSender qs = null;
MapMessage message = null;
if ((args.length < 1)) {
System.out.println("Usage: MessageClient QueueName");
}
try {
//Establish Connection
cntx = new InitialContext();
qcf = (QueueConnectionFactory)cntx.lookup(
"java:comp/env/QueueConnectionFactory");
q = (Queue)cntx.lookup(args[0]);
qcon = qcf.createQueueConnection();
qses = qcon.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
qs = qses.createSender(q);
//Create Message
message = qses.createMapMessage();
message.setInt("StudentID",100);
message.setInt("CourseID",30);
} catch (NamingException ex) {
ex.printStackTrace();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
通过以上步骤,我们可以看到消息驱动 Bean 在实际应用中的强大功能和灵活性,它能够有效地处理异步消息,并且可以与多种协议集成,为企业级应用开发提供了有力的支持。
下面是一个简单的 mermaid 流程图,展示消息驱动 Bean 的工作流程:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(消息生产者):::process --> B(发送消息):::process
B --> C(消息队列/主题):::process
C --> D(消息驱动 Bean):::process
D --> E(处理消息):::process
E --> F(执行业务逻辑):::process
F --> G(消息消费者):::process
这个流程图清晰地展示了消息从生产者到消费者的整个过程,消息驱动 Bean 在其中起到了关键的消息处理和业务逻辑执行的作用。
超级会员免费看
173万+

被折叠的 条评论
为什么被折叠?



