以下是关于 Spring Boot 事件机制在四大核心应用场景中的完整实践指南,涵盖 应用程序生命周期管理 与 业务场景应用,每个场景均提供工业级标准代码示例,并附带详尽中文注释,完全符合你对“实际开发参考价值”和“注释清晰”的要求。
📜 Spring Boot 事件机制四大核心应用场景实践指南
✅ 适用版本:Spring Boot 3.x + JDK 17+(兼容 JDK 21 虚拟线程)
✅ 核心理念:事件驱动解耦,发布者不关心消费者,监听器专注单一职责
一、应用程序生命周期管理(Application Lifecycle Management)
场景说明:
在应用启动或关闭时,常需执行初始化、校验、健康检查等操作。若直接写在 @PostConstruct 或 CommandLineRunner 中,会:
- 无法感知 Web 服务器是否就绪
- 无法区分“容器加载完成”与“服务可访问”
- 无法优雅处理异常退出
使用事件机制可精准控制时机,实现“何时做、做什么”。
✅ 场景 1:启动时的数据初始化(缓存预热、字典加载)
🎯 目标:
在应用完全启动后,从数据库加载系统字典(如:地区、状态码、用户角色)到 Redis 缓存中,供后续接口快速访问。
🛠 实现方案:
监听 ApplicationReadyEvent,确保数据库连接、Redis 连接均已建立。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationReadyEvent;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 系统字典缓存预热监听器
* 作用:在应用完全启动后,从数据库加载字典数据到 Redis,避免首次请求慢
* 注意:必须监听 ApplicationReadyEvent,确保数据库和 Redis 都已连接成功
*/
@Component
public class DictionaryCacheWarmupListener implements ApplicationListener<ApplicationReadyEvent> {
@Autowired
private StringRedisTemplate redisTemplate; // Spring Boot 自动配置的 Redis 模板
@Autowired
private DictionaryService dictionaryService; // 自定义服务,用于查询数据库
private static final String CACHE_PREFIX = "system:dict:";
/**
* Spring 容器在所有 Bean 加载完毕、Web 服务器启动完成、健康检查通过后触发此方法
* 此时数据库连接池、Redis 连接池均已就绪,可安全执行初始化操作
*
* @param event 应用就绪事件,不携带数据,仅作为触发信号
*/
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("🚀 [事件监听] 应用已完全启动,开始预热系统字典缓存...");
try {
// 1. 从数据库加载所有字典项(模拟:地区、用户状态、订单状态)
List<DictionaryItem> dictList = dictionaryService.loadAllDictionaries();
// 2. 批量写入 Redis,使用 Pipeline 提高性能
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (DictionaryItem item : dictList) {
String key = CACHE_PREFIX + item.getCode(); // 如:system:dict:region
String value = item.getValue(); // 如:北京市
// 设置缓存,过期时间 24 小时(字典变更频率低)
connection.set(key.getBytes(), value.getBytes());
connection.expire(key.getBytes(), 24 * 60, TimeUnit.MINUTES);
System.out.println("💾 缓存字典: " + key + " = " + value);
}
return null;
});
System.out.println("✅ 字典缓存预热完成,共加载 " + dictList.size() + " 条记录");
} catch (Exception e) {
System.err.println("❌ 字典缓存预热失败,请检查数据库或 Redis 连接: " + e.getMessage());
// 生产环境建议:记录日志 + 发送告警(如钉钉、邮件)
}
}
}
// 模拟字典数据实体
class DictionaryItem {
private String code; // 编码,如 "region"
private String value; // 值,如 "北京市"
public DictionaryItem(String code, String value) {
this.code = code;
this.value = value;
}
public String getCode() { return code; }
public String getValue() { return value; }
}
// 模拟数据服务(实际项目中为 @Service)
class DictionaryService {
public List<DictionaryItem> loadAllDictionaries() {
// 模拟从数据库查询
return List.of(
new DictionaryItem("region", "北京市"),
new DictionaryItem("region", "上海市"),
new DictionaryItem("user_status", "激活"),
new DictionaryItem("user_status", "冻结"),
new DictionaryItem("order_status", "待支付"),
new DictionaryItem("order_status", "已发货")
);
}
}
✅ 为什么用
ApplicationReadyEvent?
ContextRefreshedEvent:可能被多次触发(如/actuator/refresh)ApplicationStartedEvent:Web 服务器可能未启动,Redis 连接可能失败- ✅
ApplicationReadyEvent:唯一能保证“服务可访问”的事件,是初始化的黄金时机
✅ 场景 2:环境配置验证(启动时校验必要配置)
🎯 目标:
应用启动时,强制校验关键配置项(如:支付密钥、短信平台地址)是否配置,未配置则立即退出。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationReadyEvent;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 环境配置校验监听器
* 作用:在应用启动完成后,强制校验必要外部服务配置,防止“启动成功但无法工作”
* 若校验失败,主动抛出异常,使应用无法启动(符合生产安全原则)
*/
@Component
public class EnvironmentValidatorListener implements ApplicationListener<ApplicationReadyEvent> {
@Value("${payment.secret-key:}")
private String paymentSecretKey;
@Value("${sms.endpoint:}")
private String smsEndpoint;
@Value("${third-party.api-key:}")
private String thirdPartyApiKey;
/**
* 在应用就绪时执行配置校验
* 若任一关键配置缺失,立即抛出异常,阻止服务对外提供
* 适用于 Docker/K8s 部署,结合 livenessProbe 可自动重启
*
* @param event 应用就绪事件
*/
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("🔍 [事件监听] 开始校验关键环境配置...");
StringBuilder missing = new StringBuilder();
if (Objects.isNull(paymentSecretKey) || paymentSecretKey.trim().isEmpty()) {
missing.append("payment.secret-key, ");
}
if (Objects.isNull(smsEndpoint) || smsEndpoint.trim().isEmpty()) {
missing.append("sms.endpoint, ");
}
if (Objects.isNull(thirdPartyApiKey) || thirdPartyApiKey.trim().isEmpty()) {
missing.append("third-party.api-key, ");
}
if (missing.length() > 0) {
// 截断末尾逗号
String errMsg = "❌ 启动失败:以下关键配置缺失:" + missing.substring(0, missing.length() - 2);
System.err.println(errMsg);
throw new IllegalStateException(errMsg); // 强制终止应用
}
System.out.println("✅ 所有关键配置校验通过,应用可安全运行");
}
}
✅ 生产建议:
- 配置校验应放在
ApplicationReadyEvent,而非ApplicationStartingEvent,因为此时@Value已注入完成- 抛出
IllegalStateException会触发 Spring Boot 的ApplicationFailedEvent,可用于告警
✅ 场景 3:服务健康检查(自定义健康指标)
🎯 目标:
在 Spring Boot Actuator 的 /actuator/health 接口中,暴露一个自定义健康检查项(如:Redis 是否可读写)。
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
/**
* 自定义健康检查器:验证 Redis 是否可正常读写
* 此类不是事件监听器,但常与事件配合使用:在 ApplicationReadyEvent 中初始化后,才开启健康检查
* 本例展示如何与事件机制协同工作
*/
@Component
public class RedisHealthIndicator implements HealthIndicator {
private final StringRedisTemplate redisTemplate;
public RedisHealthIndicator(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* Spring Boot Actuator 会自动调用此方法,用于 /actuator/health
* 注意:此方法由 Spring Boot 调度,非事件触发
* 但我们可以结合事件确保在 Redis 初始化后才启用健康检查
*/
@Override
public Health health() {
try {
// 尝试执行 PING 命令
String response = redisTemplate.opsForValue().get("health-check");
if ("OK".equals(response)) {
return Health.up().withDetail("redis", "connected and writable").build();
} else {
return Health.down().withDetail("redis", "ping failed").build();
}
} catch (Exception e) {
return Health.down(e).withDetail("redis", "connection error").build();
}
}
}
💡 与事件联动:
在ApplicationReadyEvent中写入health-check=OK:
// 在 DictionaryCacheWarmupListener 中追加:
redisTemplate.opsForValue().set("health-check", "OK", 30, TimeUnit.SECONDS);
这样 /actuator/health 才能返回 UP,否则可能因 Redis 未初始化而误判为 DOWN。
二、业务场景应用(Business Logic Scenarios)
✅ 场景 1:用户注册成功发送通知(邮件、短信、站内信)
🎯 目标:
用户注册后,异步发送欢迎邮件、积分奖励、推送通知,不阻塞注册主流程。
🛠 实现方案:
- 发布
UserRegisteredEvent - 异步监听器处理通知(避免阻塞 HTTP 请求)
// 1. 自定义事件:用户注册成功
package com.example.demo.event;
import org.springframework.context.ApplicationEvent;
public class UserRegisteredEvent extends ApplicationEvent {
private final Long userId;
private final String email;
private final String phone;
public UserRegisteredEvent(Object source, Long userId, String email, String phone) {
super(source);
this.userId = userId;
this.email = email;
this.phone = phone;
}
public Long getUserId() { return userId; }
public String getEmail() { return email; }
public String getPhone() { return phone; }
@Override
public String toString() {
return "UserRegisteredEvent{userId=" + userId + ", email='" + email + "', phone='" + phone + "'}";
}
}
// 2. 事件发布者:UserService
package com.example.demo.service;
import com.example.demo.event.UserRegisteredEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher; // 自动注入
public String register(String email, String phone) {
// 1. 校验参数
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("邮箱格式错误");
}
// 2. 模拟保存用户(数据库插入)
Long userId = saveUser(email, phone);
// 3. ✅ 发布事件:通知其他模块“用户已注册”
// 此处不调用 EmailService,实现解耦
eventPublisher.publishEvent(new UserRegisteredEvent(this, userId, email, phone));
return "注册成功!欢迎加入我们的社区";
}
private Long saveUser(String email, String phone) {
// 模拟数据库插入,返回 ID
return 1001L;
}
}
// 4. 异步监听器:发送邮件(非阻塞)
package com.example.demo.listener;
import com.example.demo.event.UserRegisteredEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class WelcomeEmailListener {
private static final Logger log = LoggerFactory.getLogger(WelcomeEmailListener.class);
/**
* 使用 @Async 注解,使此方法在独立线程中执行
* 不阻塞 UserService 的 HTTP 响应,提升接口吞吐量
*
* Spring Boot 会自动将 UserRegisteredEvent 传递给此方法(基于参数类型匹配)
*/
@Async
public void sendWelcomeEmail(UserRegisteredEvent event) {
log.info("🚀 [异步] 开始发送欢迎邮件给用户 ID={},邮箱={}", event.getUserId(), event.getEmail());
// 模拟网络延迟:调用 SMTP 服务(真实项目中调用 JavaMailSender)
try {
TimeUnit.SECONDS.sleep(2); // 模拟发送耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("邮件发送被中断", e);
return;
}
log.info("✅ 邮件发送成功!用户ID={},邮箱={}", event.getUserId(), event.getEmail());
// 可记录到日志表或发送到 Kafka
}
/**
* 同时监听同一个事件,发送短信(多监听器并行)
*/
@Async
public void sendSmsNotification(UserRegisteredEvent event) {
log.info("📱 [异步] 开始发送短信给用户 ID={},手机={}", event.getUserId(), event.getPhone());
try {
TimeUnit.SECONDS.sleep(1); // 模拟短信网关调用
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("短信发送被中断", e);
return;
}
log.info("✅ 短信发送成功!用户ID={},手机={}", event.getUserId(), event.getPhone());
}
}
✅ 优势对比:
方式 阻塞? 耦合度 扩展性 直接调用 EmailService ✅ 是 高 差 事件 + 同步监听器 ✅ 是 低 中 事件 + @Async❌ 否 低 ✅ 极佳
✅ 场景 2:订单状态变更处理(状态机 + 事件驱动)
🎯 目标:
当订单从“待支付”变为“已支付”时,自动:
- 扣减库存
- 发送支付成功通知
- 记录财务流水
- 更新用户积分
🛠 实现方案:
使用 OrderStatusChangedEvent,每个业务模块独立监听,实现领域驱动设计(DDD)。
// 1. 自定义事件:订单状态变更
package com.example.demo.event;
import org.springframework.context.ApplicationEvent;
public class OrderStatusChangedEvent extends ApplicationEvent {
private final Long orderId;
private final String oldStatus;
private final String newStatus;
public OrderStatusChangedEvent(Object source, Long orderId, String oldStatus, String newStatus) {
super(source);
this.orderId = orderId;
this.oldStatus = oldStatus;
this.newStatus = newStatus;
}
public Long getOrderId() { return orderId; }
public String getOldStatus() { return oldStatus; }
public String getNewStatus() { return newStatus; }
@Override
public String toString() {
return "OrderStatusChangedEvent{orderId=" + orderId + ", from=" + oldStatus + ", to=" + newStatus + "}";
}
}
// 2. 事件发布者:OrderService
package com.example.demo.service;
import com.example.demo.event.OrderStatusChangedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void updateOrderStatus(Long orderId, String newStatus) {
// 1. 校验状态转换合法性(如:不能从“已发货”变回“待支付”)
String oldStatus = getOrderStatus(orderId); // 模拟查询
if (!isValidTransition(oldStatus, newStatus)) {
throw new IllegalArgumentException("非法状态变更:" + oldStatus + " → " + newStatus);
}
// 2. 更新数据库状态
updateOrderStatusInDb(orderId, newStatus);
// 3. ✅ 发布事件:通知所有相关模块
eventPublisher.publishEvent(new OrderStatusChangedEvent(this, orderId, oldStatus, newStatus));
}
private String getOrderStatus(Long orderId) {
return "待支付"; // 模拟
}
private void updateOrderStatusInDb(Long orderId, String newStatus) {
System.out.println("💾 更新订单 " + orderId + " 状态为:" + newStatus);
}
private boolean isValidTransition(String old, String newStatus) {
// 简化:仅允许 "待支付" → "已支付"
return "待支付".equals(old) && "已支付".equals(newStatus);
}
}
// 3. 多个独立监听器,各司其职
package com.example.demo.listener;
import com.example.demo.event.OrderStatusChangedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class OrderStatusChangeListeners {
private static final Logger log = LoggerFactory.getLogger(OrderStatusChangeListeners.class);
/**
* 扣减库存监听器
* 仅在订单从“待支付”变为“已支付”时执行
*/
@Async
public void deductInventory(OrderStatusChangedEvent event) {
if ("待支付".equals(event.getOldStatus()) && "已支付".equals(event.getNewStatus())) {
log.info("📦 [异步] 扣减订单 {} 的库存", event.getOrderId());
// 调用 InventoryService.deduct()
}
}
/**
* 发送支付成功通知
*/
@Async
public void sendPaymentSuccessNotice(OrderStatusChangedEvent event) {
if ("待支付".equals(event.getOldStatus()) && "已支付".equals(event.getNewStatus())) {
log.info("📩 [异步] 向用户发送支付成功通知,订单ID={}", event.getOrderId());
// 调用 NotificationService.send()
}
}
/**
* 记录财务流水
*/
@Async
public void recordFinancialLedger(OrderStatusChangedEvent event) {
if ("待支付".equals(event.getOldStatus()) && "已支付".equals(event.getNewStatus())) {
log.info("💰 [异步] 记录财务流水,订单ID={},金额=XXX", event.getOrderId());
// 调用 LedgerService.record()
}
}
/**
* 更新用户积分
*/
@Async
public void awardPoints(OrderStatusChangedEvent event) {
if ("待支付".equals(event.getOldStatus()) && "已支付".equals(event.getNewStatus())) {
log.info("⭐ [异步] 给用户奖励积分,订单ID={}", event.getOrderId());
// 调用 PointsService.award()
}
}
}
✅ 架构优势:
- 每个模块独立开发、测试、部署
- 新增业务(如发优惠券)只需新增一个监听器,无需修改 OrderService
- 支持幂等性:监听器可记录事件ID,避免重复处理
✅ 场景 3:缓存刷新机制(数据变更时自动失效缓存)
🎯 目标:
当商品信息被修改时,自动清除 Redis 中该商品的缓存,避免脏读。
// 1. 商品更新事件
package com.example.demo.event;
import org.springframework.context.ApplicationEvent;
public class ProductUpdatedEvent extends ApplicationEvent {
private final Long productId;
public ProductUpdatedEvent(Object source, Long productId) {
super(source);
this.productId = productId;
}
public Long getProductId() { return productId; }
}
// 2. 商品服务:更新数据后发布事件
package com.example.demo.service;
import com.example.demo.event.ProductUpdatedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void updateProduct(Long productId, String name) {
// 1. 更新数据库
updateProductInDb(productId, name);
// 2. ✅ 发布事件:通知缓存模块刷新
eventPublisher.publishEvent(new ProductUpdatedEvent(this, productId));
}
private void updateProductInDb(Long productId, String name) {
System.out.println("💾 更新商品 " + productId + " 名称为:" + name);
}
}
// 3. 缓存刷新监听器
package com.example.demo.listener;
import com.example.demo.event.ProductUpdatedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class ProductCacheInvalidator {
private static final Logger log = LoggerFactory.getLogger(ProductCacheInvalidator.class);
@Autowired
private StringRedisTemplate redisTemplate;
private static final String PRODUCT_CACHE_PREFIX = "product:info:";
/**
* 监听商品更新事件,删除对应缓存
* 注意:此处不主动重新加载缓存,而是“惰性加载”(下次请求时重建)
* 避免高并发下缓存击穿
*/
public void invalidateProductCache(ProductUpdatedEvent event) {
String cacheKey = PRODUCT_CACHE_PREFIX + event.getProductId();
Long deletedCount = redisTemplate.delete(cacheKey);
if (deletedCount > 0) {
log.info("🗑️ 缓存已失效:商品ID={},键={}", event.getProductId(), cacheKey);
} else {
log.info("ℹ️ 缓存未命中或已失效:商品ID={}", event.getProductId());
}
}
}
✅ 最佳实践:
- 不主动重建缓存 → 避免缓存雪崩
- 使用 “缓存穿透”策略:查询时若缓存为空,从 DB 加载并重建
- 可结合
@Cacheable+@CacheEvict,但事件机制更灵活,适用于跨服务
✅ 场景 4:系统监控和日志收集(统一事件总线)
🎯 目标:
将所有关键业务操作(如登录、支付、下单)统一记录到日志系统,便于审计与监控。
// 1. 定义通用审计事件
package com.example.demo.event;
import org.springframework.context.ApplicationEvent;
import java.time.LocalDateTime;
public class AuditLogEvent extends ApplicationEvent {
private final String module; // 模块名:user、order、payment
private final String action; // 操作:login、pay、create
private final Long userId; // 用户ID
private final String details; // 详情:订单号、金额
private final LocalDateTime timestamp;
public AuditLogEvent(Object source, String module, String action, Long userId, String details) {
super(source);
this.module = module;
this.action = action;
this.userId = userId;
this.details = details;
this.timestamp = LocalDateTime.now();
}
// Getter 省略...
public String getModule() { return module; }
public String getAction() { return action; }
public Long getUserId() { return userId; }
public String getDetails() { return details; }
public LocalDateTime getTimestamp() { return timestamp; }
@Override
public String toString() {
return "AuditLogEvent{" +
"module='" + module + '\'' +
", action='" + action + '\'' +
", userId=" + userId +
", details='" + details + '\'' +
", timestamp=" + timestamp +
'}';
}
}
// 2. 在各服务中发布审计事件
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void login(String email) {
// 登录逻辑...
System.out.println("👤 用户 " + email + " 登录成功");
// ✅ 发布审计事件
eventPublisher.publishEvent(new AuditLogEvent(this, "user", "login", 1001L, "IP:192.168.1.1"));
}
}
// 3. 统一日志监听器:写入日志文件 / Kafka / ELK
package com.example.demo.listener;
import com.example.demo.event.AuditLogEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class AuditLogListener {
private static final Logger log = LoggerFactory.getLogger("AUDIT_LOG"); // 专用日志记录器
/**
* 所有审计事件统一由本监听器处理
* 可配置输出到文件、Kafka、数据库、或发送至监控平台
*/
public void logAuditEvent(AuditLogEvent event) {
// 使用独立日志记录器,避免与业务日志混杂
log.info("AUDIT | {} | {} | {} | {} | {}",
event.getModule(),
event.getAction(),
event.getUserId(),
event.getDetails(),
event.getTimestamp()
);
// 生产环境建议:异步写入 Kafka → ELK 集群
// kafkaTemplate.send("audit-logs", event.toString());
}
}
✅ 效果:
日志文件输出示例:AUDIT | user | login | 1001 | IP:192.168.1.1 | 2025-10-14T10:30:00 AUDIT | order | create | 1001 | orderNo=ORD20251014001 | 2025-10-14T10:30:05 AUDIT | payment | pay | 1001 | amount=299.00, method=Alipay | 2025-10-14T10:30:10
🧩 总结:四大场景最佳实践对照表
| 场景 | 推荐事件 | 是否异步 | 核心要点 |
|---|---|---|---|
| 启动数据初始化 | ApplicationReadyEvent | ❌ 同步 | 确保依赖服务就绪,避免空指针 |
| 环境配置校验 | ApplicationReadyEvent | ❌ 同步 | 失败则抛异常,强制终止 |
| 用户注册通知 | UserRegisteredEvent | ✅ 异步 | 解耦通知逻辑,提升响应速度 |
| 订单状态变更 | OrderStatusChangedEvent | ✅ 异步 | 每个动作独立监听,支持幂等 |
| 缓存失效 | ProductUpdatedEvent | ✅ 异步 | 采用“删除缓存”而非“重建” |
| 系统审计日志 | AuditLogEvent | ✅ 异步 | 使用独立日志记录器,便于集中分析 |
⚠️ 最佳实践总结(开发必须牢记)
| 原则 | 说明 |
|---|---|
| ✅ 事件命名规范 | 使用名词+过去式:UserRegisteredEvent, OrderPaidEvent |
| ✅ 事件类轻量 | 只包含必要字段(ID、状态),不传大对象 |
| ✅ 异步处理耗时操作 | 邮件、短信、日志、RPC 调用一律 @Async |
| ✅ 监听器幂等设计 | 记录事件ID,避免重复消费(尤其在 Kafka 场景) |
| ✅ 避免循环事件 | A → B → A 会导致栈溢出 |
| ✅ 生产环境监控 | 监听 ApplicationFailedEvent,自动告警 |
| ✅ 日志分离 | 审计日志使用独立 logger,避免与业务日志混淆 |
| ✅ 测试事件 | 使用 ApplicationEventPublisher + @Test 模拟发布,验证监听器行为 |
✅ 附:完整项目结构建议
src/main/java/com/example/demo/
├── event/
│ ├── ApplicationReadyEvent.java (内置)
│ ├── UserRegisteredEvent.java
│ ├── OrderStatusChangedEvent.java
│ ├── ProductUpdatedEvent.java
│ └── AuditLogEvent.java
├── listener/
│ ├── DictionaryCacheWarmupListener.java
│ ├── EnvironmentValidatorListener.java
│ ├── WelcomeEmailListener.java
│ ├── OrderStatusChangeListeners.java
│ ├── ProductCacheInvalidator.java
│ └── AuditLogListener.java
├── service/
│ ├── UserService.java
│ ├── OrderService.java
│ └── ProductService.java
├── config/
│ └── AsyncConfig.java # @EnableAsync
└── controller/
└── TestEventController.java # 提供 HTTP 触发入口
💡 进阶建议:
当业务复杂、监听器增多时,建议引入 Kafka/RabbitMQ 替代 Spring 事件,实现跨服务、持久化、重试、削峰填谷。
本方案适用于单体/微服务内解耦,是 Spring Boot 事件机制的工业级标准实践。

1309

被折叠的 条评论
为什么被折叠?



