《spring如此简单》第六节--事件发布机制

什么是事件发布机制

事件发布是经典的工作模式,其本质是流程到达特定节点,需要处理的任务。

打个比方,篮球赛进行到中场,就会发布一个暂停休息的事件,球员们接收到这个事件后,就自行去休息,喝水,布置战术了。

最简陋的事件发布机制,可以理解为观察者模式。

typescript 复制代码
// 观察者接口
interface Observer {
    void update(String msg);
}

// 被观察者(事件发布者)
class Subject {
    // 维护订阅者列表
    private List<Observer> observers = new ArrayList<>();

    // 订阅
    public void attach(Observer o) {
        observers.add(o);
    }

    // 发布事件,遍历通知所有订阅者
    public void notifyObservers(String msg) {
        for (Observer o : observers) {
            o.update(msg);
        }
    }
}

// 具体订阅者A
class UserA implements Observer {
    @Override
    public void update(String msg) {
        System.out.println("用户A收到:" + msg);
    }
}

// 具体订阅者B
class UserB implements Observer {
    @Override
    public void update(String msg) {
        System.out.println("用户B收到:" + msg);
    }
}

// 测试
public class Demo {
    public static void main(String[] args) {
        Subject subject = new Subject();
        subject.attach(new UserA());
        subject.attach(new UserB());

        // 发布消息,自动遍历通知列表
        subject.notifyObservers("订单已支付");
    }
}

但是这样的代码有一个问题,耦合性强。

第一,观察者需要被添加到观察者列表。

第二,事件发布的时候,发布者需要主动调用通知方法。

一旦有了耦合,那么代码维护和添加就是很麻烦的问题。

spring采用的是升级版,观察者模式+事件总线。

  1. 发布者只管发事件,完全不知道谁在监听。
scala 复制代码
// 事件:只是一个数据载体
public class OrderPayEvent extends ApplicationEvent {
    public OrderPayEvent(Object source) {
        super(source);
    }
}
typescript 复制代码
@Service
public class OrderService {
    // 注入事件发布器
    @Autowired
    private ApplicationEventPublisher publisher;

    public void pay() {
        System.out.println("主流程:订单支付完成");
        
        // 只发事件!!!不知道谁会处理
        publisher.publishEvent(new OrderPayEvent(this));
    }
}
  1. 总线统一管理所有监听器,负责转发。
  2. 监听者只注册自己,处理自己的逻辑。(这里的监听器注册,又是我们的老朋友IOC)
typescript 复制代码
@Service
public class SmsService {
    // 监听事件,自动被Spring管理
    @EventListener
    public void handle(OrderPayEvent event) {
        System.out.println("监听者:发送短信");
    }
}

@Service
public class LogService {
    @EventListener
    public void handle(OrderPayEvent event) {
        System.out.println("监听者:记录日志");
    }
}

发布者和监听者直接零依赖。

spring自带哪些事件

spring自有的事件集中于启动流程中。

  • ApplicationContextRefreshedEvent:容器刷新完成触发。容器刷新完毕,Bean 全部加载完成,做全局资源初始化、配置校验
  • ContextStartedEvent:容器启动触发。应用启动阶段,打印启动日志、初始化连接池
  • ContextStoppedEvent:应用暂停运行,不再接收请求
  • ContextClosedEvent:容器关闭销毁触发。容器关闭,优雅释放资源:关闭连接、保存临时数据、注销服务
  • ApplicationStartedEvent:容器刷新完毕,应用启动流程走完,还未对外接收请求。应用启动阶段,打印启动日志、初始化连接池
  • ApplicationReadyEvent:内嵌服务器启动就绪,端口监听完成,可以正式处理请求。项目完全就绪,执行初始化任务:加载缓存、定时任务注册、接口预热
  • ApplicationFailedEvent:启动流程中途抛出异常,启动终止节点

以Context开头的事件,spring容器生命周期事件。

application开头是springboot应用生命周期事件。

自定义事件发布

首先,定义自己的事件。

scala 复制代码
// 自定义业务事件
public class UserRegisterEvent extends ApplicationEvent {
    // 自定义业务参数
    private String username;

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

然后,选择发布的时机。

typescript 复制代码
@Service
public class UserService {
    @Resource
    private ApplicationEventPublisher publisher;

    public void register(String username){
        System.out.println("主逻辑:用户["+username+"]注册完成");
        // 发布自定义事件
        publisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

当然,要写一个监听者

csharp 复制代码
@Service
public class UserEventListener {

    // 监听事件,触发后执行自定义逻辑
    @EventListener
    public void handleRegister(UserRegisterEvent event){
        String name = event.getUsername();
        System.out.println("监听处理:给"+name+"发送欢迎短信");
        System.out.println("监听处理:新增用户注册日志");
    }
}

可以看到,publisher是框架中维护的单例对象。

事件发布流程:

  1. 业务publishEvent发起事件
  2. 发布器将事件交给单例ApplicationEventMulticaster
  3. 根据事件类型,从注册表
  4. 遍历执行逻辑,找到匹配的监听器
  5. 监听执行完毕,流程切回主线,是的publishEvent是个阻塞方法。
相关推荐
你好潘先生4 小时前
让 AI 任务不丢进度:YeeroAI 后台续跑与全局快捷操作实践
前端·人工智能·后端
Circ.4 小时前
SpringBoot 实现文件上传与下载(完整源码 + 详细教程)
java·spring boot·后端
Java技术小馆4 小时前
Claude Code CLI 命令大全:60 个原生命令一次讲清
前端·后端
zzqssliu4 小时前
Spring Boot + XXL-JOB 搭建淘宝代购系统任务调度中心
java·spring boot·后端
kunge20135 小时前
Claude Code 工作流中的命令实现与自定义指南
人工智能·后端·架构
明月_清风5 小时前
Go 没有 `class`,如何实现面向对象三要素?与传统 OOP 的深度对比
后端·go
xixingzhe25 小时前
spring构造函数注入对比@Resource
java·后端·spring
程序员牛奶5 小时前
[Algo-2]双指针技巧:你真的学懂双指针了吗?
后端
Kir1to5 小时前
RabbitMQ 核心概念与快速安装
后端