一、核心概念与模型

Spring 的事件机制是观察者模式(也叫发布-订阅模型)的一种典型实现。它主要由三个核心部分组成:

  1. 事件 (Event): 承载信息的对象,通常是某种状态变化的通知。可以是继承 ApplicationEvent 的类,也可以是任何普通的 Java 对象(POJO)。

  2. 发布者 (Publisher): 负责产生并发布事件的组件。它通过 ApplicationEventPublisher 来发布事件。

  3. 监听器 (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());
    }
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐