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 改在提交之后派发。

相关