Spring 事件驱动编程初探:用 @EventListener 轻松处理业务通知
·
一、核心概念与模型
Spring 的事件机制是观察者模式(也叫发布-订阅模型)的一种典型实现。它主要由三个核心部分组成:
-
事件 (Event): 承载信息的对象,通常是某种状态变化的通知。可以是继承
ApplicationEvent的类,也可以是任何普通的 Java 对象(POJO)。 -
发布者 (Publisher): 负责产生并发布事件的组件。它通过
ApplicationEventPublisher来发布事件。 -
监听器 (Listener): 负责接收和处理事件的组件。它监听特定类型的事件并做出响应。
工作流程:发布者发布一个事件 -> Spring 的 ApplicationContext(作为事件广播器)接收事件 -> 广播器将事件通知给所有注册的、对该事件感兴趣的监听器 -> 监听器执行自身的业务逻辑。
二、详细用法与步骤
我们将通过一个经典的业务场景——用户注册成功后,发送邮件和短信——来演示每一步的用法。
第 1 步:定义事件 (Event)
事件是一个简单的 POJO,用于封装需要传递的数据。自 Spring 4.2 以后,不再需要强制继承 ApplicationEvent。
package jnpf.model.cultivate.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class JnpfApplicationEvent<T> extends ApplicationEvent {
private final T data;
public JnpfApplicationEvent(T source) {
super(source);
this.data = source;
}
}
第 2 步:发送消息
SpringContext.getApplicationContext().publishEvent(new JnpfApplicationEvent<>(
CourseEventDTO.builder()
.courseIds(List.of(ftbCultivateCourseDTO.getCourseId()))
.userIds(new ArrayList<>())
.whetherToChange(true).build()));
第 3 步:实现监听器 (Listener)
package jnpf.attendance.event;
import com.alibaba.fastjson.JSON;
import jnpf.model.cultivate.event.JnpfApplicationEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* 事件处理程序
* @author shitou
*/
@Slf4j
@Component
public class AttendanceEventHandler {
private final Map<Class<?>, AttendanceEventService> eventServiceMap = new ConcurrentHashMap<>();
@Resource
private List<AttendanceEventService> jnpfApplicationEventServices;
@PostConstruct
public void init() {
for (AttendanceEventService service : jnpfApplicationEventServices) {
eventServiceMap.put(service.getSupportedEventType(), service);
}
}
@Async("ioIntensiveThreadPool")
@EventListener(value = JnpfApplicationEvent.class)
public <T> void courseEvent(JnpfApplicationEvent<T> jnpfApplicationEvent) {
CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS)
.execute(() -> processEvent(jnpfApplicationEvent));
}
private <T> void processEvent(JnpfApplicationEvent<T> jnpfApplicationEvent) {
try {
log.error("接收到考勤统计数据生成事件: {}", JSON.toJSONString(jnpfApplicationEvent.getData()));
// 使用缓存查找处理器,避免每次都遍历
AttendanceEventService service = findServiceForEvent(jnpfApplicationEvent.getData());
if (service != null) {
// 添加异常处理,避免事件处理失败影响其他事件
try {
service.handlerEvent(jnpfApplicationEvent.getData());
} catch (Exception e) {
log.error("处理考勤事件失败: data={}, error={}",JSON.toJSONString(jnpfApplicationEvent.getData()), e.getMessage(), e);
}
} else {
log.warn("未找到对应的事件处理器: data={}", JSON.toJSONString(jnpfApplicationEvent.getData()));
}
} catch (Exception e) {
log.error("处理考勤事件时发生异常: data={}, error={}",
JSON.toJSONString(jnpfApplicationEvent.getData()), e.getMessage(), e);
}
}
/**
* 查找处理器
* @param data 数据
* @return 处理器
*/
private AttendanceEventService findServiceForEvent(Object data) {
// 优先使用缓存
AttendanceEventService service = eventServiceMap.get(data.getClass());
if (service != null) {
return service;
}
// 缓存未命中时再查找并缓存
return jnpfApplicationEventServices.stream()
.filter(t -> t.isSupportedEvent(data))
.findFirst()
.map(s -> {
eventServiceMap.put(data.getClass(), s);
return s;
}).orElse(null);
}
}
第 4 步:创建事件处理类接口
package jnpf.attendance.event;
/**
* @author shitou
*/
public interface AttendanceEventService {
/**
* 获取支持的事件数据类型
*/
Class<?> getSupportedEventType();
/**
* 是否支持此事件
*
* @param event 事件
* @return boolean
*/
boolean isSupportedEvent(Object event);
/**
* 处理事件
*
* @param courseEvent 事件入参
*/
void handlerEvent(Object courseEvent);
}
第 5 步:事件处理类impl
package jnpf.attendance.event.impl;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.event.AttendanceEventService;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.model.attendance.event.AttendanceStatisticsBatchClearDto;
import jnpf.util.CustomTenantUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author shitou
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AttendanceStatisticsBatchClearService implements AttendanceEventService {
@Autowired
private CustomTenantUtil tenantUtil;
@Resource
private AttendanceDayStatisticsService attendanceDayStatisticsService;
@Override
public Class<?> getSupportedEventType() {
return AttendanceStatisticsBatchClearService.class;
}
@Override
public boolean isSupportedEvent(Object event) {
return event instanceof AttendanceStatisticsBatchClearDto;
}
@Override
public void handlerEvent(Object courseEvent) {
AttendanceStatisticsBatchClearDto courseEventDTO = (AttendanceStatisticsBatchClearDto) courseEvent;
tenantUtil.checkOutTenant(courseEventDTO.getTenantId());
log.error("监听到-批量清除日统计数据-消息:{}", JSONObject.toJSONString(courseEventDTO));
attendanceDayStatisticsService.batchStatisticDataClear(courseEventDTO);
}
}
package jnpf.attendance.event.impl;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.event.AttendanceEventService;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.model.attendance.event.AttendanceStatisticsSingleDto;
import jnpf.util.CustomTenantUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author shitou
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AttendanceStatisticsSingleService implements AttendanceEventService {
@Autowired
private CustomTenantUtil tenantUtil;
@Resource
private AttendanceDayStatisticsService attendanceDayStatisticsService;
@Override
public Class<?> getSupportedEventType() {
return AttendanceStatisticsSingleService.class;
}
@Override
public boolean isSupportedEvent(Object event) {
return event instanceof AttendanceStatisticsSingleDto;
}
@Override
public void handlerEvent(Object courseEvent) {
AttendanceStatisticsSingleDto courseEventDTO = (AttendanceStatisticsSingleDto) courseEvent;
log.error("监听到-生成用户日统计数据-消息:{}", JSONObject.toJSONString(courseEventDTO));
tenantUtil.checkOutTenant(courseEventDTO.getTenantId());
attendanceDayStatisticsService.statisticDataChange(courseEventDTO.getGroupId(),
courseEventDTO.getUserId(), courseEventDTO.getDay());
}
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)