EventListenerExtension
EventListenerExtension 让 plugin 能够响应平台发出的领域事件 —— 记录的创建/更新/删除、状态流转、自定义 command 完成 —— 而不与发起 command 耦合。事件经平台事件总线 AuraEventBus 派发到匹配的 listener;这让副作用与发起方解耦,单个 listener 失败不会阻塞其他 listener。
概念
平台通过 AuraEventBus.publish(AuraEvent)(或 publishAfterCommit(AuraEvent),在事务提交之后再派发)发布领域事件。PluginEventDispatcher(默认实现 DefaultPluginEventDispatcher)从 ExtensionRegistry 取出匹配的 plugin listener,并同步逐个调用 —— 每个 listener 被隔离,抛出的异常会被捕获并记录日志,不会中断其余 listener。
Listener 通过 getSubscribedEvents() 声明订阅的事件类型,格式为 "domain:event",并支持通配符:
"order:created"、"order:completed"、"user:login"—— 精确匹配"order:*"—— 某个 domain 下的全部事件"*:created"—— 全部created事件"*"—— 全部事件
ExtensionRegistry.getEventListeners(eventType) 用 isInterestedIn(eventType) 过滤,并按 getOrder() 升序排序后派发。
派发为同步、尽力而为(best-effort):listener 抛异常仅被记录日志,框架不会自动重试,也没有 DLQ。需要可靠/事务性投递的副作用,请走
EventPolicy事务性 outbox(OutboxWriter),而不是直接依赖本扩展点的投递语义。handler 仍应保持幂等。
接口签名
package com.auraboot.framework.plugin.extension;
import org.pf4j.ExtensionPoint;
import java.util.Map;
import java.util.Set;
public interface EventListenerExtension extends ExtensionPoint {
/** 此 listener 订阅的事件类型,格式 "domain:event",支持通配符(如 "order:*"、"*:created"、"*")。 */
Set<String> getSubscribedEvents();
/** 处理事件。 */
void onEvent(EventContext context);
/** 是否对给定事件类型感兴趣;默认按 getSubscribedEvents() 的通配符匹配。 */
default boolean isInterestedIn(String eventType) { /* 通配符匹配 */ return true; }
/** 执行顺序,值越小越先执行,默认 100。 */
default int getOrder() { return 100; }
/** 是否倾向异步执行,默认 false。 */
default boolean isAsync() { return false; }
}EventContext 是一个 record,字段(即同名访问器方法)为:tenantId()、pluginId()、namespace()、eventType()、sourceModel()、recordId()、eventData()(类型 Map<String, Object>)、previousData()(类型 Map<String, Object>)与 timestamp()(long)。
实现示例
订阅订单完成事件,完成后自动派发后续 command:
package com.acme.crm.listener;
import com.auraboot.framework.plugin.extension.EventListenerExtension;
import com.auraboot.framework.meta.service.CommandExecutor;
import com.auraboot.framework.meta.dto.CommandExecuteRequest;
import org.pf4j.Extension;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map;
import java.util.Set;
@Extension
public class OrderCompletedListener implements EventListenerExtension {
@Autowired private CommandExecutor commandExecutor;
@Autowired private SalesRouter router;
@Override
public Set<String> getSubscribedEvents() {
return Set.of("order:completed");
}
@Override
public void onEvent(EventContext e) {
// sourceModel / recordId / eventData 来自事件上下文
if (!"crm_order".equals(e.sourceModel())) return;
Long rep = router.nextAvailableRep(e.tenantId());
CommandExecuteRequest request = new CommandExecuteRequest();
request.setPayload(Map.of(
"id", e.recordId(),
"assigned_to", rep
));
commandExecutor.execute("crm_order:update", request);
}
}注册
使用 @Extension 注解;PF4J 注解处理器会生成 META-INF/extensions.idx。无需额外配置 —— listener 在 plugin enable 时由平台接入 ExtensionRegistry,在 disable 时注销;PluginEventDispatcher 派发事件时即从注册表取用。
要发布事件,在任意 handler 或 service 中注入并调用 AuraEventBus.publish(event)(或 publishAfterCommit(event) 在事务提交之后派发)。
常见陷阱
- 派发是尽力而为,不是事务性/可靠投递。
onEvent抛出的异常只会被记录日志、不会重试,也不会进 DLQ。需要可靠副作用请改用EventPolicy事务性 outbox。 - 派发是同步的。 你的 listener 与其他 listener 在同一派发循环里顺序执行;耗时操作会拖慢整体。需要异步可在 listener 内自行调度(
isAsync()仅表达偏好)。 - 保持幂等。 同一业务事件可能被多个 listener、多次触发到达;以
recordId()/sourceModel()等业务键去重,不要假设恰好一次。 - 在派发上下文内调用
commandExecutor.execute。 它会走完整命令管道;若事件本身在某事务内派发,注意你的写入与发起事务的关系,必要时用publishAfterCommit改在提交之后派发。
相关
- Commands —— 事件发射点
- CommandHandlerExtension —— 事务内反应
- ConversationTurnService —— 对话回合事件 chokepoint