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 的生命周期极其简单,只有两个状态:

  1. ❌ 不存在于容器中
  2. ✅ 创建完成,等待消息

关键点:

  • 依赖注入(如 @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());
        }
    }
}

📌 核心流程:

  1. 通过 JNDI 查找 ConnectionFactoryQueue
  2. 创建非事务性 Session(false),自动确认模式(AUTO_ACKNOWLEDGE
  3. 构造 TextMessage 并发送
  4. 使用 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 处理


原始标题:A Guide to Message Driven Beans in EJB