事件驱动架构设计思路和笔记

事件驱动架构完整学习指南

作者: smj 

日期: 2025-01-20  

版本: V1.0  

项目: HCMS-WMS 机器人管理模块 V3.0


📚 目录

  1. 核心概念

  2. 三大核心类详解

  3. 完整工作流程

  4. 代码实战示例

  5. 设计模式深入

  6. 性能优化要点

  7. 常见问题FAQ

  8. 扩展知识


🎯 核心理解图

监听器实现接口 → 重写propertyChange()方法 → 注册到事件总线

                                                    ↓

                                            事件总线存储监听器

                                                    ↓

业务类发布事件 → 调用事件总线的firePropertyChange()

                                                    ↓

                                    事件总线遍历所有注册的监听器

                                                    ↓

                                            匹配事件模式

                                                    ↓

                                调用监听器的propertyChange()方法

                                                    ↓

                                            执行业务逻辑

核心概念

什么是事件驱动架构?

事件驱动架构(Event-Driven Architecture, EDA)是一种软件设计模式,系统中的组件通过发布事件和订阅事件来进行通信,而不是直接调用彼此的方法。

核心角色

┌─────────────┐      发布事件      ┌─────────────┐      通知      ┌─────────────┐

│  发布者     │ ───────────────→  │  事件总线   │ ───────────→  │  监听器     │

│ (Publisher) │                    │ (Event Bus) │                │ (Listener)  │

└─────────────┘                    └─────────────┘                └─────────────┘

   业务服务类                         中介/容器                      处理业务逻辑

三大角色

  • 发布者(Publisher):发布事件的组件(如业务服务类)

  • 事件总线(Event Bus):管理监听器和分发事件的中介

  • 监听器(Listener):订阅事件并处理业务逻辑的组件

为什么使用事件驱动架构?

优点

  • 解耦:发布者和监听器互不依赖,降低耦合度

  • 扩展性:新增监听器不影响现有代码

  • 异步处理:事件发布后立即返回,不阻塞主流程

  • 灵活性:一个事件可以被多个监听器处理

缺点

  • 调试困难:事件流程不直观,需要追踪事件链路

  • 性能开销:事件分发和异步处理有一定开销

  • 事件丢失风险:需要持久化机制保证可靠性


三大核心类详解

1. WmsEventBus - 事件总线

职责

  • 管理所有监听器(注册/移除)

  • 接收事件发布请求

  • 匹配事件模式

  • 异步通知监听器

核心数据结构

// 存储监听器的容器

private final Map<String, List> listeners = new ConcurrentHashMap<>();

//                 ↑                    ↑

//            事件模式(Key)      监听器列表(Value)

// 示例数据:

// {

//     "wms:in:*": [RobotScheduleListener, LoggingListener],

//     "wms:out:*": [RobotScheduleListener, InventoryListener],

//     "wms:robot:*": [MonitoringListener]

// }

为什么用 ConcurrentHashMap?

  • 多线程环境下保证线程安全

  • 支持高并发读写操作

  • 避免数据竞争和不一致

核心方法

1. 注册监听器

public void addPropertyChangeListener(String pattern, PropertyChangeListener listener) {

    listeners.computeIfAbsent(pattern, k -> new CopyOnWriteArrayList<>()).add(listener);

    log.info("监听器已注册: pattern={}, listener={}", pattern, listener.getClass().getSimpleName());

}

关键点

  • computeIfAbsent:如果key不存在,创建新列表;存在则返回现有列表

  • CopyOnWriteArrayList:写时复制列表,适合读多写少场景

  • 支持同一个监听器订阅多个事件模式

2. 发布事件

public void firePropertyChange(String eventName, Object oldValue, Object newValue) {

    PropertyChangeEvent event = new PropertyChangeEvent(this, eventName, oldValue, newValue);

    // 遍历所有注册的监听器

    for (Map.Entry<String, List> entry : listeners.entrySet()) {

        String pattern = entry.getKey();

        // 匹配事件模式

        if (matches(pattern, eventName)) {

            // 异步通知每个监听器

            for (PropertyChangeListener listener : entry.getValue()) {

                executor.submit(() -> notifyListener(listener, event));

            }

        }

    }

}

执行流程

1. 创建事件对象

    ↓

2. 遍历所有监听器

    ↓

3. 匹配事件模式(通配符支持)

    ↓

4. 使用虚拟线程异步通知

    ↓

5. 调用监听器的 propertyChange() 方法
3. 通配符匹配

private boolean matches(String pattern, String eventName) {

    if (pattern.equals(eventName)) {

        return true;  // 完全匹配

    }

    if (pattern.endsWith("*")) {

        String prefix = pattern.substring(0, pattern.length() - 1);

        return eventName.startsWith(prefix);  // 前缀匹配

    }

    return false;

}

匹配规则

matches("wms:in:*", "wms:in:start")     → true  ✅ 前缀匹配

matches("wms:in:*", "wms:in:complete")  → true  ✅ 前缀匹配

matches("wms:in:*", "wms:out:start")    → false ❌ 前缀不匹配

matches("wms:in:start", "wms:in:start") → true  ✅ 完全匹配

虚拟线程优化

// 构造方法中初始化线程池

public WmsEventBus() {

    ExecutorService executor;

    try {

        // Java 21+ 使用虚拟线程

        Method virtualThreadMethod = Executors.class.getMethod("newVirtualThreadPerTaskExecutor");

        executor = (ExecutorService) virtualThreadMethod.invoke(null);

        log.info("使用虚拟线程池");

    } catch (Exception e) {

        // 降级到普通线程池

        executor = Executors.newFixedThreadPool(10);

        log.info("使用普通线程池");

    }

    this.executor = executor;

}

虚拟线程 vs 普通线程

| 特性 | 虚拟线程 | 普通线程 |

|------|---------|---------|

| 创建成本 | 极低 | 高 |

| 内存占用 | 几KB | 1-2MB |

| 数量限制 | 百万级 | 几千个 |

| 适用场景 | 高并发I/O | CPU密集型 |


2. WmsEventPersistence - 事件持久化服务

职责

  • 将事件保存到数据库

  • 提供故障恢复能力

  • 支持事件重试机制

  • 记录事件处理状态

核心方法

1. 保存事件

@Transactional(rollbackFor = Exception.class)

public String saveEvent(String eventName, String businessId, String businessType, String eventData) {

    WmsEventLog eventLog = new WmsEventLog();

    eventLog.setEventName(eventName);

    eventLog.setBusinessId(businessId);      // 业务单号

    eventLog.setBusinessType(businessType);  // 业务类型

    eventLog.setEventData(eventData);        // 事件数据(JSON)

    eventLog.setStatus(EventStatusEnum.PENDING.getCode());

    eventLog.setRetryCount(0);

    eventLogMapper.insert(eventLog);

    return eventLog.getId();

}

使用场景

// 入库单开始时保存事件

String eventId = wmsEventPersistence.saveEvent(

    "wms:in:start",           // 事件名称

    "IN202501200001",         // 入库单号

    "INBOUND",                // 业务类型

    JSON.toJSONString(order)  // 事件数据

);
2. 更新事件状态

@Transactional(rollbackFor = Exception.class)

public void updateEventStatus(String eventId, EventStatusEnum status, String errorMessage) {

    WmsEventLog eventLog = eventLogMapper.selectById(eventId);

    eventLog.setStatus(status.getCode());

    eventLog.setUpdateTime(new Date());

    if (status == EventStatusEnum.FAILED && errorMessage != null) {

        eventLog.setErrorMessage(errorMessage);

    }

    if (status == EventStatusEnum.COMPLETED) {

        eventLog.setProcessedTime(new Date());

    }

    eventLogMapper.updateById(eventLog);

}

状态流转

PENDING(待处理)

    ↓

PROCESSING(处理中)

    ↓

COMPLETED(已完成) / FAILED(失败)
3. 扫描未处理事件(故障恢复)

public List scanUnprocessedEvents(int maxRetryCount, int limit) {

    LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();

    queryWrapper.eq(WmsEventLog::getStatus, EventStatusEnum.PENDING.getCode())

                .lt(WmsEventLog::getRetryCount, maxRetryCount)

                .orderByAsc(WmsEventLog::getCreateTime)

                .last("LIMIT " + limit);

    return eventLogMapper.selectList(queryWrapper);

}

使用场景

// 系统重启后,扫描未处理的事件重新处理

@Scheduled(fixedDelay = 60000)  // 每分钟执行一次

public void recoverUnprocessedEvents() {

    List<WmsEventLog> events = wmsEventPersistence.scanUnprocessedEvents(3, 100);

    for (WmsEventLog event : events) {

        // 重新发布事件

        wmsEventBus.firePropertyChange(event.getEventName(), null, event.getEventData());

        // 更新状态为处理中

        wmsEventPersistence.updateEventStatus(event.getId(), EventStatusEnum.PROCESSING, null);

    }

}
4. 重试失败事件

@Transactional(rollbackFor = Exception.class)

public boolean retryEvent(String eventId) {

    WmsEventLog eventLog = eventLogMapper.selectById(eventId);

    // 增加重试次数

    eventLog.setRetryCount(eventLog.getRetryCount() + 1);

    // 重置状态为PENDING

    eventLog.setStatus(EventStatusEnum.PENDING.getCode());

    eventLog.setUpdateTime(new Date());

    eventLogMapper.updateById(eventLog);

    return true;

}

---



### 3. WmsEventNames - 事件名称常量



#### 职责

- 统一管理所有事件名称

- 避免硬编码和拼写错误

- 提供工具方法判断事件类型



#### 命名规范

wms:{业务模块}:{操作}

**示例**:



- `wms:in:start` - 入库开始

- `wms:out:complete` - 出库完成

- `wms:robot:task:finish` - 机器人任务完成



#### 核心常量

// 入库相关

public static final String INBOUND_START = "wms:in:start";

public static final String INBOUND_COMPLETE = "wms:in:complete";

public static final String INBOUND_CANCEL = "wms:in:cancel";



// 出库相关

public static final String OUTBOUND_START = "wms:out:start";

public static final String OUTBOUND_COMPLETE = "wms:out:complete";



// 机器人相关

public static final String ROBOT_TASK_FINISH = "wms:robot:task:finish";  // 重要!触发自动调度



// 通配符模式

public static final String INBOUND_ALL = "wms:in:*";

public static final String ROBOT_ALL = "wms:robot:*";

工具方法

// 判断是否为业务事件

public static boolean isBusinessEvent(String eventName) {

    return eventName.startsWith("wms:in:")

        || eventName.startsWith("wms:out:")

        || eventName.startsWith("wms:check:");

}



// 提取业务类型

public static String extractBusinessType(String eventName) {

    if (eventName.startsWith("wms:in:")) return "INBOUND";

    if (eventName.startsWith("wms:out:")) return "OUTBOUND";

    return "UNKNOWN";

}

完整工作流程

流程图

[系统启动]

    ↓

[监听器注册]

RobotScheduleListener.init()

wmsEventBus.addPropertyChangeListener("wms:in:*", this)

    ↓

[事件总线存储]

listeners = {

    "wms:in:*": [RobotScheduleListener]

}

    ↓

[用户操作]

POST /api/inbound/create

    ↓

[业务处理]

InboundService.createInbound()

保存入库单到数据库

    ↓

[发布事件]

wmsEventBus.firePropertyChange("wms:in:start", null, order)

    ↓

[事件总线匹配]

遍历listeners

匹配"wms:in:*"和"wms:in:start" ✅

    ↓

[异步通知]

executor.submit(() -> {

    RobotScheduleListener.propertyChange(event)

})

    ↓

[监听器处理]

handleInboundEvent()

查询可用机器人

创建机器人任务

下发任务

### 时序图

用户          Controller      Service       EventBus      Listener

 │               │              │              │             │

 │─创建入库单──→│              │              │             │

 │               │─调用服务──→│              │             │

 │               │              │─保存数据     │             │

 │               │              │─发布事件───→│             │

 │               │              │              │─匹配模式    │

 │               │              │              │─通知监听器→│

 │               │              │              │             │─处理业务

 │               │←返回成功────│              │             │

 │←返回结果────│              │              │             │

---



## 代码实战示例



### 完整示例:入库流程



#### 1. 监听器类

package com.slhc.hcms.module.robot.listener;



import com.slhc.hcms.module.robot.event.WmsEventBus;

import com.slhc.hcms.module.robot.service.IRobotInfoService;

import com.slhc.hcms.module.robot.service.IRobotTaskService;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;



import javax.annotation.PostConstruct;

import java.beans.PropertyChangeEvent;

import java.beans.PropertyChangeListener;



@Slf4j

@Component

public class RobotScheduleListener implements PropertyChangeListener {

    @Autowired

    private WmsEventBus wmsEventBus;

    @Autowired

    private IRobotInfoService robotInfoService;

    @Autowired

    private IRobotTaskService robotTaskService;

    // 系统启动时自动注册

    @PostConstruct

    public void init() {

        log.info("========== 开始注册机器人调度监听器 ==========");

        // 订阅入库事件

        wmsEventBus.addPropertyChangeListener("wms:in:*", this);

        log.info("✅ 已订阅入库事件");

        // 订阅出库事件

        wmsEventBus.addPropertyChangeListener("wms:out:*", this);

        log.info("✅ 已订阅出库事件");

        // 订阅盘点事件

        wmsEventBus.addPropertyChangeListener("wms:check:*", this);

        log.info("✅ 已订阅盘点事件");

        log.info("========== 监听器注册完成 ==========");

    }

    // 当事件发生时,这个方法会被自动调用

    @Override

    public void propertyChange(PropertyChangeEvent evt) {

        String eventName = evt.getPropertyName();

        Object eventData = evt.getNewValue();

        log.info("🔔 收到事件: {}", eventName);

        try {

            // 根据事件类型分发处理

            if (eventName.startsWith("wms:in:")) {

                handleInboundEvent(eventName, eventData);

            } else if (eventName.startsWith("wms:out:")) {

                handleOutboundEvent(eventName, eventData);

            } else if (eventName.startsWith("wms:check:")) {

                handleCheckEvent(eventName, eventData);

            }

        } catch (Exception e) {

            log.error("处理事件失败: eventName={}, error={}", eventName, e.getMessage(), e);

        }

    }

    // 处理入库事件

    private void handleInboundEvent(String eventName, Object eventData) {

        log.info("📥 处理入库事件: {}", eventName);

        // 1. 查询可用机器人

        RobotInfo robot = robotInfoService.findAvailableRobot("WH01");

        if (robot == null) {

            log.warn("⚠️ 没有可用机器人");

            return;

        }

        // 2. 创建机器人任务

        RobotTask task = robotTaskService.createTask(

            robot.getId(),

            "INBOUND",

            eventData.toString()

        );

        log.info("✅ 机器人任务已创建: taskId={}, robotCode={}", task.getId(), robot.getRobotCode());

    }

    // 处理出库事件

    private void handleOutboundEvent(String eventName, Object eventData) {

        log.info("📤 处理出库事件: {}", eventName);

        // 类似入库处理逻辑

    }

    // 处理盘点事件

    private void handleCheckEvent(String eventName, Object eventData) {

        log.info("📋 处理盘点事件: {}", eventName);

        // 类似入库处理逻辑

    }

}

2. 业务服务类

package com.slhc.hcms.module.inbound.service.impl;



import com.slhc.hcms.module.inbound.entity.InboundOrder;

import com.slhc.hcms.module.robot.event.WmsEventBus;

import com.slhc.hcms.module.robot.event.WmsEventNames;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;



@Slf4j

@Service

public class InboundServiceImpl {

    @Autowired

    private WmsEventBus wmsEventBus;

    @Transactional(rollbackFor = Exception.class)

    public void createInbound(InboundOrder order) {

        log.info("========== 开始创建入库单 ==========");

        log.info("入库单号: {}", order.getOrderNo());

        // 1. 保存入库单到数据库

        saveToDatabase(order);

        log.info("💾 入库单已保存");

        // 2. 发布事件(通知其他模块)

        wmsEventBus.firePropertyChange(

            WmsEventNames.INBOUND_START,  // 使用常量

            null,

            order

        );

        log.info("📢 入库开始事件已发布");

        log.info("========== 入库单创建完成 ==========");

    }

    private void saveToDatabase(InboundOrder order) {

        // 保存逻辑

    }

}

3. 控制器类

package com.slhc.hcms.module.inbound.controller;



import com.slhc.hcms.module.inbound.entity.InboundOrder;

import com.slhc.hcms.module.inbound.service.impl.InboundServiceImpl;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;



@Slf4j

@RestController

@RequestMapping("/api/inbound")

public class InboundController {

    @Autowired

    private InboundServiceImpl inboundService;

    @PostMapping("/create")

    public Result createInbound(@RequestBody InboundOrder order) {

        log.info("收到创建入库单请求: {}", order.getOrderNo());

        inboundService.createInbound(order);

        return Result.success("入库单创建成功");

    }

}

设计模式深入

观察者模式(Observer Pattern)

事件驱动架构是观察者模式的一种实现。

角色映射

  • Subject(主题) = Event Bus(事件总线)

  • Observer(观察者) = Listener(监听器)

  • notify() = firePropertyChange()

类图

┌─────────────────┐

│   EventBus      │

├─────────────────┤

│ +addListener()  │

│ +fireEvent()    │

└────────┬────────┘

         │ 1

         │

         │ *

┌────────▼────────┐

│   Listener      │

├─────────────────┤

│ +propertyChange()│

└─────────────────┘

发布-订阅模式(Pub-Sub Pattern)

与观察者模式的区别

| 特性 | 观察者模式 | 发布-订阅模式 |

|------|-----------|--------------|

| 耦合度 | 主题知道观察者 | 发布者不知道订阅者 |

| 中介 | 无 | 有(事件总线) |

| 通信方式 | 直接调用 | 通过中介 |

| 灵活性 | 较低 | 较高 |

💡 我们的实现更接近发布-订阅模式,因为有事件总线作为中介。


性能优化要点

1. 使用虚拟线程

// Java 21+ 虚拟线程

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();



// 优点:

// - 创建成本极低

// - 可以创建百万级线程

// - 适合I/O密集型任务

2. 异步处理

// 事件发布后立即返回,不等待监听器处理完成

wmsEventBus.firePropertyChange("wms:in:start", null, order);

// 这里立即返回,监听器在后台异步处理

3. 异常隔离

// 一个监听器异常不影响其他监听器

for (PropertyChangeListener listener : listeners) {

    try {

        listener.propertyChange(event);

    } catch (Exception e) {

        log.error("监听器处理失败: {}", e.getMessage());

        // 继续处理下一个监听器

    }

}

4. 事件持久化

// 保证事件不丢失

wmsEventPersistence.saveEvent(eventName, businessId, businessType, eventData);



// 定时扫描未处理事件

@Scheduled(fixedDelay = 60000)

public void recoverEvents() {

    List<WmsEventLog> events = wmsEventPersistence.scanUnprocessedEvents(3, 100);

    // 重新处理

}

常见问题FAQ

Q1: PropertyChangeListener是什么?

A: 这是Java标准库提供的一个接口(不是方法),定义了事件监听的契约。

package java.beans;



public interface PropertyChangeListener {

    void propertyChange(PropertyChangeEvent evt);

}

任何实现这个接口的类都必须提供propertyChange()方法。

Q2: 为什么要重写propertyChange()方法?

A: 因为事件总线需要调用这个方法来通知监听器。

// 事件总线内部

listener.propertyChange(event);  // 调用你重写的方法

Q3: 监听器和事件是什么关系?

A:

  • ❌ 不是"监听器包含事件"

  • ✅ 是"监听器订阅事件"

类比

  • 事件 = 电视节目

  • 监听器 = 观众

  • 订阅 = 观众选择看哪些节目

Q4: 一个监听器可以监听多个事件吗?

A: ✅ 可以!

@PostConstruct

public void init() {

    wmsEventBus.addPropertyChangeListener("wms:in:*", this);

    wmsEventBus.addPropertyChangeListener("wms:out:*", this);

    wmsEventBus.addPropertyChangeListener("wms:check:*", this);

}

Q5: 事件总线怎么通知监听器?

A: 通过接口调用

// 事件总线持有监听器对象的引用

PropertyChangeListener listener = robotScheduleListener;



// 通过接口调用监听器的方法

listener.propertyChange(event);  // ← 这就是通知!

Q6: 注册监听器和虚拟线程有什么关系?

A:

  • 注册监听器时:不使用线程(只是把监听器放到Map里)

  • 发布事件时:使用虚拟线程异步通知监听器

Q7: 事件会丢失吗?

A: 可能会。解决方案

  1. 事件持久化:保存到数据库

  2. 定时扫描:扫描未处理事件重新处理

  3. 重试机制:失败事件自动重试


扩展知识

1. Spring的事件机制

Spring也提供了事件驱动机制,与我们的实现类似:

// Spring事件

public class InboundStartEvent extends ApplicationEvent {

    public InboundStartEvent(Object source) {

        super(source);

    }

}

// Spring监听器

@Component

public class RobotScheduleListener {

    @EventListener

    public void handleInboundStart(InboundStartEvent event) {

        // 处理业务

    }

}

// 发布事件

@Autowired

private ApplicationEventPublisher eventPublisher;

eventPublisher.publishEvent(new InboundStartEvent(order));

**对比**:



| 特性 | 我们的实现 | Spring事件 |

|------|-----------|-----------|

| 通配符支持 | ✅ | ❌ |

| 异步处理 | ✅ | 需要@Async |

| 事件持久化 | ✅ | ❌ |

| 学习成本 | 低 | 中 |



---



### 2. 消息队列(MQ)

事件驱动架构的进阶版本是使用消息队列:

发布者 → RabbitMQ/Kafka → 订阅者

```

优点

  • ✅ 更高的可靠性

  • ✅ 支持分布式

  • ✅ 消息持久化

  • ✅ 流量削峰

缺点

  • ❌ 部署复杂

  • ❌ 运维成本高


总结

通过本文档,你应该已经掌握了:

  1. ✅ 事件驱动架构的核心概念和工作原理

  2. ✅ 三大核心类(WmsEventBus、WmsEventPersistence、WmsEventNames)的详细实现

  3. ✅ 完整的事件发布-订阅流程

  4. ✅ 实战代码示例和最佳实践

  5. ✅ 性能优化技巧和常见问题解决方案

  6. ✅ 与Spring事件机制和消息队列的对比

关键要点回顾

  • PropertyChangeListener 是Java标准库的接口,不是方法

  • 监听器 有两个职责:注册自己(@PostConstruct)+ 处理业务(propertyChange方法)

  • 事件总线 通过接口调用通知监听器

  • 虚拟线程 用于异步通知,注册时不使用线程

  • 通配符匹配 支持灵活的事件订阅模式


 

posted @ 2026-01-20 14:18  烈酒清茶  阅读(0)  评论(0)    收藏  举报