在Java生态中,Spring框架提供了一套优雅的事件发布-订阅机制,让我们能够以松耦合的方式实现组件间的通信。这套机制的核心就是ApplicationEventPublisher接口,它采用了观察者模式的设计思想。
Spring事件机制主要由三个核心部分组成:
这种设计模式的最大优势在于发布者和订阅者之间完全解耦。发布者不需要知道有哪些监听器存在,监听器也不需要关心事件是如何产生的。这种松耦合的设计使得系统更容易维护和扩展。
虽然消息队列(MQ)也能实现类似的功能,但ApplicationEventPublisher有以下几个显著优势:
提示:对于需要跨服务、高可靠、持久化的场景,还是应该考虑使用专业的MQ解决方案。ApplicationEventPublisher更适合服务内部的事件通知。
在门店管理系统中,我们首先需要定义事件对象。良好的事件设计应该遵循以下原则:
java复制@Getter
@AllArgsConstructor
public class ShopCreationEvent extends ApplicationEvent {
private final Long shopId;
private final String eventCode;
public ShopCreationEvent(Long shopId, String eventCode) {
super(shopId);
this.shopId = shopId;
this.eventCode = eventCode;
}
}
直接使用ApplicationEventPublisher虽然简单,但缺乏统一管理和扩展点。更好的做法是进行一层封装:
java复制@Component
@RequiredArgsConstructor
@Slf4j
public class ShopCreationEventPublisher {
private final ApplicationEventPublisher applicationEventPublisher;
public void publishShopCreationEvent(Long shopId) {
if (shopId == null) {
log.warn("Attempted to publish shop creation event with null shopId");
return;
}
ShopCreationEvent event = new ShopCreationEvent(
shopId,
ShopDomainEvent.SHOP_CREATION_EVENT.getCode()
);
try {
applicationEventPublisher.publishEvent(event);
log.info("Published shop creation event for shopId: {}", shopId);
} catch (Exception e) {
log.error("Failed to publish shop creation event for shopId: {}", shopId, e);
// 可以根据业务需求决定是否抛出异常
}
}
}
这种封装带来了几个好处:
在业务服务中,我们只需要注入自定义的事件发布器,而不需要直接依赖ApplicationEventPublisher:
java复制@Service
@RequiredArgsConstructor
public class ShopApplicationService {
private final ShopCreationEventPublisher shopCreationEventPublisher;
private final ShopInfoRepository shopInfoRepository;
@Transactional
public void saveShopInfo(SaveShopInfoCommand command) {
ShopInfoAggregateRoot root = ShopInfoAggregateRoot.fromCommand(command);
shopInfoRepository.save(root);
// 发布事件
shopCreationEventPublisher.publishShopCreationEvent(root.getId());
}
}
最简单的监听器实现方式是使用@EventListener注解:
java复制@Component
@Slf4j
@RequiredArgsConstructor
class NewShopRuleGenerateListener {
private final ShopRuleService shopRuleService;
@EventListener
public void handleShopCreation(ShopCreationEvent event) {
log.info("Processing shop creation for shopId: {}", event.getShopId());
shopRuleService.generateDefaultRules(event.getShopId());
}
}
默认情况下,事件监听是同步执行的。要实现异步处理,有几种方式:
java复制@EventListener
@Async("taskExecutor")
public void asyncHandleShopCreation(ShopCreationEvent event) {
// 异步处理逻辑
}
java复制@Configuration
public class AsyncEventConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("event-async-");
executor.initialize();
return executor;
}
}
注意:直接使用new Thread()的方式不推荐,因为无法控制线程数量,容易导致资源耗尽。
Spring允许我们根据条件决定是否处理某个事件:
java复制@EventListener(condition = "#event.shopId != null && #event.shopId > 0")
public void conditionalHandle(ShopCreationEvent event) {
// 只有当shopId不为null且大于0时才执行
}
一个监听器可以处理多种事件类型:
java复制@EventListener
public void handleMultipleEvents(ApplicationEvent event) {
if (event instanceof ShopCreationEvent) {
// 处理创建事件
} else if (event instanceof ShopUpdateEvent) {
// 处理更新事件
}
}
事件未触发:
监听器执行顺序问题:
内存泄漏:
性能瓶颈:
事件发布与数据库事务的协调是个常见难题。有几种处理方式:
java复制@Transactional
public void saveShopInfo(SaveShopInfoCommand command) {
// 保存数据
shopInfoRepository.save(root);
// 事务提交后发布事件
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
eventPublisher.publishShopCreationEvent(root.getId());
}
}
);
}
java复制@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(ShopCreationEvent event) {
// 事务提交后执行
}
ApplicationEventPublisher可以配合事件溯源模式使用:
java复制public class ShopAggregate {
private final List<ShopEvent> changes = new ArrayList<>();
public void createShop(CreateShopCommand command) {
// 应用业务逻辑
apply(new ShopCreatedEvent(command.getShopId(), command.getShopName()));
}
private void apply(ShopEvent event) {
// 应用事件到聚合
// ...
// 记录未提交事件
changes.add(event);
}
public List<ShopEvent> getUncommittedChanges() {
return Collections.unmodifiableList(changes);
}
}
@Service
public class ShopCommandHandler {
@Transactional
public void handle(CreateShopCommand command) {
ShopAggregate aggregate = repository.load(command.getShopId());
aggregate.createShop(command);
repository.save(aggregate);
// 发布所有未提交事件
aggregate.getUncommittedChanges().forEach(eventPublisher::publishEvent);
aggregate.clearChanges();
}
}
虽然ApplicationEventPublisher主要用于进程内通信,但也可以扩展用于跨上下文集成:
java复制@EventListener
public void handleLocalEvent(ShopCreationEvent event) {
// 添加追踪信息
try (Scope scope = tracer.buildSpan("handleShopCreation").startActive(true)) {
// 处理本地事件
// 触发远程调用
remoteService.syncShopInfo(event.getShopId());
}
}
java复制@Component
@RequiredArgsConstructor
class ShopEventToMQBridge {
private final RabbitTemplate rabbitTemplate;
@EventListener
public void bridgeToMQ(ShopCreationEvent event) {
rabbitTemplate.convertAndSend(
"shop.exchange",
"shop.created",
new ShopCreatedMessage(event.getShopId())
);
}
}
在生产环境中,我们需要对事件系统进行全面的监控:
java复制@Component
public class EventMetrics {
private final MeterRegistry meterRegistry;
@EventListener
public void countEvent(ApplicationEvent event) {
meterRegistry.counter("application.events",
"type", event.getClass().getSimpleName())
.increment();
}
}
java复制@Aspect
@Component
@RequiredArgsConstructor
public class EventTracingAspect {
private final Tracer tracer;
@Around("@annotation(org.springframework.context.event.EventListener)")
public Object traceEventListener(ProceedingJoinPoint joinPoint) throws Throwable {
Object event = joinPoint.getArgs()[0];
String spanName = "handle." + event.getClass().getSimpleName();
Span span = tracer.buildSpan(spanName).start();
try (Scope scope = tracer.activateSpan(span)) {
return joinPoint.proceed();
} catch (Exception e) {
span.log(Map.of("error", e.getMessage()));
throw e;
} finally {
span.finish();
}
}
}
java复制@Slf4j
@Component
public class EventLogger {
@EventListener
public void logEvent(ApplicationEvent event) {
if (log.isInfoEnabled()) {
log.info("Event received: {}",
JsonUtils.toJson(event));
}
}
}
完善的事件系统需要全面的测试覆盖:
java复制@ExtendWith(MockitoExtension.class)
class ShopCreationEventPublisherTest {
@Mock
private ApplicationEventPublisher eventPublisher;
@InjectMocks
private ShopCreationEventPublisher publisher;
@Test
void shouldPublishEventForValidShopId() {
publisher.publishShopCreationEvent(123L);
ArgumentCaptor<ShopCreationEvent> captor =
ArgumentCaptor.forClass(ShopCreationEvent.class);
verify(eventPublisher).publishEvent(captor.capture());
ShopCreationEvent event = captor.getValue();
assertEquals(123L, event.getShopId());
}
}
java复制@SpringBootTest
class ShopEventIntegrationTest {
@Autowired
private ShopApplicationService shopService;
@Autowired
private ApplicationEventPublisher eventPublisher;
@MockBean
private ShopRuleService ruleService;
@Test
void shouldTriggerRuleGenerationOnShopCreation() {
SaveShopInfoCommand command = new SaveShopInfoCommand(...);
shopService.saveShopInfo(command);
ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
verify(ruleService, timeout(1000)).generateDefaultRules(captor.capture());
assertNotNull(captor.getValue());
}
}
java复制@SpringBootTest
@ActiveProfiles("test")
class EventPerformanceTest {
@Autowired
private ShopCreationEventPublisher publisher;
@Test
void shouldHandleHighVolumeEvents() {
int count = 1000;
long start = System.currentTimeMillis();
IntStream.range(0, count)
.parallel()
.forEach(i -> publisher.publishShopCreationEvent((long)i));
long duration = System.currentTimeMillis() - start;
assertTrue(duration < 2000, "Processed " + count + " events in " + duration + "ms");
}
}
在实际项目中,我遇到过事件监听器因为数据库连接泄漏导致系统挂掉的情况。通过添加完善的监控和资源管理,我们最终将事件系统的稳定性提升到了99.99%。关键是要记住:即使是"简单"的本地事件系统,也需要像对待分布式系统一样考虑它的可靠性和可观测性。