1. 简介
Enterprise JavaBean(EJB)是运行在应用服务器上的 Java EE 组件,而 Message Driven Bean(MDB) 是其中用于处理异步消息的核心组件。
本文重点讲解如何使用注解方式创建和使用 MDB。自 EJB 3.0 起,注解大大简化了 MDB 的开发流程,我们也将围绕这一现代实践展开。
✅ 重点:MDB 是异步的、由容器管理的、事件驱动的组件,适合解耦系统模块。
2. 背景知识
在深入 MDB 之前,先回顾几个关键的底层概念。
2.1. 消息机制(Messaging)
消息机制是一种松耦合的通信方式。
✅ 生产者和消费者无需了解彼此的存在,甚至可以用不同语言、运行在不同系统上进行通信。
✅ 支持异步通信,双方不必同时在线。
这种“解耦 + 异步”的特性,正是 MDB 发挥作用的基础。
2.2. 同步 vs 异步通信
类型 | 特点 | 适用场景 |
---|---|---|
同步通信 | 调用方阻塞等待响应 | HTTP 请求、RPC 调用 |
异步通信 | 调用方发送后立即返回,结果后续处理 | 消息队列、事件驱动架构 |
MDB 属于典型的异步通信模型,避免阻塞主线程,提升系统吞吐。
2.3. JMS(Java Message Service)
JMS 是 Java 提供的标准消息 API,支持两种模型:
- 点对点(Peer-to-Peer):消息发送到队列(Queue),一个消费者处理
- 发布/订阅(Publish/Subscribe):消息发布到主题(Topic),多个订阅者接收
MDB 可以监听 Queue 或 Topic,实现灵活的消息消费。
3. Message Driven Bean 核心原理
MDB 是由容器在消息到达时自动触发的组件。一旦消息进入指定队列或主题,容器就会调用其 onMessage()
方法。
你可以在 onMessage()
中做任何业务逻辑:
- 解析消息并入库
- 触发下游服务调用
- 转发消息到其他队列
- 发送邮件、短信等异步任务
总之,它是你异步处理链的“入口”。
3.1. MDB 生命周期
MDB 的生命周期极其简单,只有两个状态:
- ❌ 不存在于容器中
- ✅ 创建完成,等待消息
关键点:
- 依赖注入(如
@EJB
,@Inject
)在 MDB 实例创建后仅执行一次 - 若需初始化操作,使用
@PostConstruct
注解方法:
@PostConstruct
public void init() {
System.out.println("MDB 初始化完成,准备接收消息");
}
⚠️ 注意:@PostConstruct
和依赖注入都只执行一次,不要在里面写每条消息都需执行的逻辑。
3.2. 事务管理
MDB 天然支持事务上下文。
- 整个
onMessage()
方法运行在一个事务中 - 如果方法内抛出异常 → 事务回滚 → 消息被重新投递(redelivered)
- 如果正常结束 → 事务提交 → 消息确认消费
✅ 这种机制保证了“至少一次”(at-least-once)的可靠性语义。
4. 实战:编写 MDB
4.1. 创建消费者(MDB)
使用 @MessageDriven
注解定义一个 MDB,并实现 MessageListener
接口:
@MessageDriven(
activationConfig = {
@ActivationConfigProperty(
propertyName = "destination",
propertyValue = "tutorialQueue"
),
@ActivationConfigProperty(
propertyName = "destinationType",
propertyValue = "javax.jms.Queue"
)
}
)
public class ReadMessageMDB implements MessageListener {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("收到消息: " + textMessage.getText());
} catch (JMSException e) {
System.err.println("消费消息失败: " + e.getMessage());
}
}
}
📌 关键说明:
activationConfig
:替代旧版 XML 配置,声明激活参数destination
:指定监听的 JNDI 队列名(如tutorialQueue
)destinationType
:指定目标类型,这里是Queue
onMessage()
参数类型可强转为具体消息类型:TextMessage
,BytesMessage
,MapMessage
等
⚠️ 踩坑提醒:确保队列已在应用服务器(如 WildFly)中正确配置并绑定 JNDI 名称。
4.2. 创建生产者(消息发送端)
生产者与消费者完全解耦,甚至可以是不同语言的服务。这里我们用一个简单的 Servlet 发送消息:
@WebServlet("/SendMessageServlet")
public class SendMessageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
String text = req.getParameter("text");
if (text == null || text.isEmpty()) {
text = "Hello World";
}
try (
Context context = new InitialContext();
ConnectionFactory cf = (ConnectionFactory) context.lookup("java:/ConnectionFactory");
Queue queue = (Queue) context.lookup("queue/tutorialQueue");
Connection connection = cf.createConnection();
) {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
connection.start();
TextMessage message = session.createTextMessage(text);
producer.send(message);
res.getWriter().println("消息已发送: " + text);
} catch (NamingException | JMSException e) {
res.getWriter().println("发送消息失败: " + e.getMessage());
}
}
}
📌 核心流程:
- 通过 JNDI 查找
ConnectionFactory
和Queue
- 创建非事务性 Session(
false
),自动确认模式(AUTO_ACKNOWLEDGE
) - 构造
TextMessage
并发送 - 使用 try-with-resources 确保资源释放
✅ Session.AUTO_ACKNOWLEDGE
:消费者成功处理后自动确认
❌ 若使用 CLIENT_ACKNOWLEDGE
,需手动调用 message.acknowledge()
5. 测试 MDB
部署应用后,通过浏览器或 curl 发起请求即可触发消息发送:
# 发送自定义消息
http://127.0.0.1:8080/producer/SendMessageServlet?text=测试MDB
# 使用默认消息
http://127.0.0.1:8080/producer/SendMessageServlet
预期输出:
- Servlet 页面显示:“消息已发送: xxx”
- 服务器日志中出现:
收到消息: xxx
✅ 验证成功:消息被 MDB 正确接收并处理。
6. 总结
MDB 是构建异步、解耦系统的重要工具。它带来的优势非常明显:
✅ 系统解耦:生产者与消费者互不依赖
✅ 弹性伸缩:可通过池化 MDB 实例提升消费能力
✅ 容错恢复:事务机制保障消息不丢失
✅ 简化开发:注解驱动,无需手动管理连接和线程
结合 JMS 和 EJB 容器,你可以快速搭建高可用的消息处理服务。
示例代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-ejb-modules/wildfly-mdb
📌 适用场景建议:
- 日志异步落盘
- 订单状态更新通知
- 批量任务调度
- 第三方服务回调处理
简单粗暴地说:凡是不想阻塞主线程的任务,都可以考虑扔进 MDB 处理。