无需继承ApplicationEvent定义Spring自定义应用事件可行吗?
领域事件独立于Spring框架的实现方案
核心思路
严格遵循端口与适配器原则:把领域事件的定义、发布/订阅契约完全放在domain层,application层作为适配器,用Spring的能力适配这些契约,确保domain层完全不依赖Spring生态。
具体实现步骤
1. Domain层定义纯Java的事件与端口
先在domain层定义无任何框架依赖的事件基类和具体事件:
// domain层 - 纯POJO事件基类 public abstract class DomainEvent { private final Instant occurredAt; protected DomainEvent() { this.occurredAt = Instant.now(); } public Instant getOccurredAt() { return occurredAt; } } // 示例领域事件 public class OrderCreatedEvent extends DomainEvent { private final String orderId; private final BigDecimal amount; public OrderCreatedEvent(String orderId, BigDecimal amount) { this.orderId = orderId; this.amount = amount; } // Getter方法 public String getOrderId() { return orderId; } public BigDecimal getAmount() { return amount; } }
接着定义领域事件的发布端口(抽象接口,domain层仅依赖此端口,不关心具体实现):
// domain层 - 事件发布端口 public interface EventPublisherPort { void publish(DomainEvent event); }
再定义领域事件处理器的契约接口:
// domain层 - 事件处理器契约 public interface DomainEventHandler<T extends DomainEvent> { void handle(T event); }
2. Application层实现Spring适配器
事件发布适配器
实现domain层的EventPublisherPort,适配Spring的ApplicationEventPublisher:
// application层 - Spring事件发布适配器 @Component public class SpringEventPublisherAdapter implements EventPublisherPort { private final ApplicationEventPublisher springPublisher; public SpringEventPublisherAdapter(ApplicationEventPublisher springPublisher) { this.springPublisher = springPublisher; } @Override public void publish(DomainEvent event) { // 用Spring事件包装类包裹领域事件,仅在application层存在 springPublisher.publishEvent(new SpringDomainEventWrapper(event)); } } // Spring事件包装类,仅用于适配Spring事件机制 public class SpringDomainEventWrapper extends ApplicationEvent { private final DomainEvent domainEvent; public SpringDomainEventWrapper(DomainEvent domainEvent) { super(domainEvent); this.domainEvent = domainEvent; } public DomainEvent getDomainEvent() { return domainEvent; } }
事件分发适配器
实现Spring的ApplicationListener,把Spring事件转发给domain层的事件处理器:
// application层 - Spring事件分发器 @Component public class SpringDomainEventDispatcher implements ApplicationListener<SpringDomainEventWrapper> { private final Map<Class<? extends DomainEvent>, DomainEventHandler<? extends DomainEvent>> handlerMap; // 构造注入所有实现了DomainEventHandler的Bean public SpringDomainEventDispatcher(List<DomainEventHandler<? extends DomainEvent>> handlers) { this.handlerMap = handlers.stream() .collect(Collectors.toMap(this::extractEventType, Function.identity())); } // 通过反射获取处理器对应的事件类型 @SuppressWarnings("unchecked") private <T extends DomainEvent> Class<T> extractEventType(DomainEventHandler<T> handler) { ParameterizedType type = (ParameterizedType) handler.getClass().getGenericInterfaces()[0]; return (Class<T>) type.getActualTypeArguments()[0]; } @SuppressWarnings("unchecked") @Override public void onApplicationEvent(SpringDomainEventWrapper event) { DomainEvent domainEvent = event.getDomainEvent(); DomainEventHandler<DomainEvent> handler = (DomainEventHandler<DomainEvent>) handlerMap.get(domainEvent.getClass()); if (handler != null) { handler.handle(domainEvent); } } }
3. Domain层使用事件发布端口
在领域服务或实体中,依赖EventPublisherPort发布事件,完全不接触Spring类:
// domain层 - 领域服务 public class OrderService { private final EventPublisherPort eventPublisher; public OrderService(EventPublisherPort eventPublisher) { this.eventPublisher = eventPublisher; } public void createOrder(String orderId, BigDecimal amount) { // 执行订单创建业务逻辑... // 发布领域事件 eventPublisher.publish(new OrderCreatedEvent(orderId, amount)); } }
4. 实现领域事件处理器
处理器实现domain层的DomainEventHandler,可以放在application层(处理应用级逻辑)或infrastructure层(处理外部集成逻辑):
// application层 - 订单创建事件处理器 @Component public class OrderCreatedEventHandler implements DomainEventHandler<OrderCreatedEvent> { @Override public void handle(OrderCreatedEvent event) { // 处理订单创建后的逻辑:比如发送通知、更新统计数据等 System.out.println("处理订单创建事件,订单ID:" + event.getOrderId()); } }
方案优势
- 领域层完全解耦Spring:所有事件相关定义都是纯Java,领域层可以独立测试、甚至迁移到其他框架(如Quarkus)。
- 复用Spring生态能力:application层作为适配器,可直接利用Spring的事件异步处理(给处理器加
@Async即可)、事务绑定等特性。 - 扩展性强:后续若切换事件框架(如Axon),仅需替换application层的适配器实现,domain层无需任何修改。
内容的提问来源于stack exchange,提问作者sidgate
相关产品推荐
相关产品推荐

