聊透Spring事件机制

 事件机制是Spring为企业级开发提供的神兵利器之一,它提供了一种低耦合、无侵入的解决方式,是我们行走江湖必备保命技能。但其实Spring事件的设计其实并不复杂,它由三部分组成:事件、发布器、监听器。事件是主体,发布器负责发布事件,监听器负责处理事件。

在这里插入图片描述

 在简单了解Spring事件的机制之后,本文将从源码的角度出发,和大家一起探讨:Spring事件的核心工作机制,并看一下作为企业级开发工具,Spring事件是如何支持全局异常处理和异步执行的。最后会和大家讨论目前Spring事件机制的一些缺陷和问题,话不多说,我们开始吧。

在这里插入图片描述

1. Spring事件如何使用

 所谓千里之行始于足下,在研究Spring的事件的机制之前,我们先来看一下Spring事件是如何使用的。通常情况下,我们使用自定义事件和内置事件,自定义事件主要是配合业务使用,自定义事件则多是做系统启动时的初始化工作或者收尾工作。

1.1 自定义事件的使用

  • 定义自定义事件

     自定义一个事件在使用上很简单,继承ApplicationEvent即可:
// 事件需要继承ApplicationEvent
public class MyApplicationEvent extends ApplicationEvent {
   
    private Long id;
    public MyApplicationEvent(Long id) {
   
        super(id);
        this.id = id;
    }

    public Long getId() {
   
        return id;
    }
}
  • 发布自定义事件

     现在自定义事件已经有了,该如何进行发布呢?Spring提供了ApplicationEventPublisher进行事件的发布,我们平常使用最多的ApplicationContext也继承了该发布器,所以我们可以直接使用applicationContext进行事件的发布。
// 发布MyApplicationEvent类型事件
applicationContext.publishEvent(new MyApplicationEvent(1L));
  • 处理自定义事件

     现在事件已经发布了,谁负责处理事件呢?当然是监听器了,Spring要求监听器需要实现ApplicationListener接口,同时需要通过泛型参数指定处理的事件类型。有了监听器需要处理的事件类型信息,Spring在进行事件广播的时候,就能找到需要广播的监听器了,从而准确传递事件了。
// 需要继承ApplicationListener,并指定事件类型
public class MyEventListener implements ApplicationListener<MyApplicationEvent> {
   
    // 处理指定类型的事件
    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
   
        System.out.println(Thread.currentThread().getName() + "接受到事件:"+event.getSource());
    }
}

1.2 Spring内置事件

1.2.1 ContextRefreshedEvent

 在ConfigurableApplicationContextrefresh()执行完成时,会发出ContextRefreshedEvent事件。refresh()是Spring最核心的方法,该方法内部完成的Spring容器的启动,是研究Spring的重中之重。在该方法内部,当Spring容器启动完成,会在finishRefresh()发出ContextRefreshedEvent事件,通知容器刷新完成。我们一起来看一下源码:

// ConfigurableApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
   
    try {
   
        // ...省略部分非关键代码
        //完成普通单例Bean的实例化(非延迟的)
        this.finishBeanFactoryInitialization(beanFactory);

        // 初始化声明周期处理器,并发出对应的时间通知
        this.finishRefresh();
    }
}

protected void finishRefresh() {
   
    // ...省略部分非核心代码
    // 发布上下文已经刷新完成的事件
    this.publishEvent(new ContextRefreshedEvent(this));
}

 其实这是Spring提供给我们的拓展点,此时容器已经启动完成,容器中的bean也已经创建完成,对应的属性、init()、Aware回调等,也全部执行。很适合我们做一些系统启动后的准备工作,此时我们就可以监听该事件,作为系统启动后初始预热的契机。其实Spring内部也是这样使用ContextRefreshedEvent的, 比如我们常用的Spring内置的调度器,就是在接收到该事件后,才进行调度器的执行的。

public class ScheduledAnnotationBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
   
  	@Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
   
      if (event.getApplicationContext() == this.applicationContext) {
   
        finishRegistration();
      }
    }
}

1.2.2 ContextStartedEvent

 在ConfigurableApplicationContextstart()执行完成时,会发出ContextStartedEvent事件。

@Override
public void start() {
   
    this.getLifecycleProcessor().start();
    this.publishEvent(new ContextStartedEvent(this));
}

ContextRefreshedEvent事件的触发是所有的单例bean创建完成后发布,此时实现了Lifecycle接口的bean还没有回调start(),当这些start()被调用后,才会发布ContextStartedEvent事件。

1.2.3 ContextClosedEvent

 在ConfigurableApplicationContextclose()执行完成时,会发出ContextStartedEvent事件。此时IOC容器已经关闭,但尚未销毁所有的bean。

@Override
public void close() {
   
    synchronized (this.startupShutdownMonitor) {
   
        this.doClose();
    }
}

protected void doClose() {
   
    // 发布ContextClosedEvent事件
    this.publishEvent(new ContextClosedEvent(this));
}

1.2.4 ContextStoppedEvent

 在ConfigurableApplicationContextstop()执行完成时,会发出ContextStartedEvent事件。

@Override
public void stop() {
   
    this.getLifecycleProcessor().stop();
    this.publishEvent(new ContextStoppedEvent(this));
}

 该事件在ContextClosedEvent事件触发之后才会触发,此时单例bean还没有被销毁,要先把他们都停掉才可以释放资源,销毁bean。

2. Spring事件是如何运转的

 经过第一章节的探讨,我们已经清楚Spring事件是如何使用的,然而这只是皮毛而已,我们的目标是把Spring事件机制脱光扒净的展示给大家看。所以这一章节我们深入探讨一下,Spring事件的运行机制,重点我们看一下:

  • 事件是怎么广播给监听器的?会不会发送阻塞?
  • 系统中bean那么多,ApplicationListener是被如何识别为监听器的?
  • 监听器处理事件的时候,是同步处理还是异步处理的?
  • 处理的时候发生异常怎么办,后面的监听器还能执行吗?

 乍一看是不是问题还挺多,没事,不要着急,让我们一起来开启愉快的探索路程,看看Spring是怎么玩转事件的吧。

2.1 事件发布

 在第一章节,我们直接通过applicationContext发布了事件,同时也提到了,它之所以能发布事件,是因为它是ApplicationEventPublisher的子类,因此是具备事件发布能力的。但按照接口隔离原则,如果我们只需要进行事件发布,applicationContext提供的能力太多,还是推荐直接使用ApplicationEventPublisher进行操作。

2.1.1 获取事件发布器的方式

 我们先来ApplicationEventPublisher的提供的能力,它是一个接口,结构如下:

@FunctionalInterface
public interface ApplicationEventPublisher {
   
    //发布ApplicationEvent事件
    default void publishEvent(ApplicationEvent event) {
   
        publishEvent((Object) event);
    }

    //发布PayloadApplicationEvent事件
    void publishEvent(Object event);
}

 通过源码我们发现ApplicationEventPublisher仅仅提供了事件发布的能力,支持自定义类型和PayloadApplicationEvent类型(如果没有定义事件类型,默认包装为该类型)。那我们如何获取该发布器呢,我们最常使用的@Autowired注入是否可以呢,试一下呗。

  • 通过@Autowired 注入 ApplicationEventPublisher
    在这里插入图片描述
     通过debug,我们可以直观的看到:是可以的,而且注入的就是ApplicationContext实例。也就是说注入ApplicationContext和注入ApplicationEventPublisher是等价的,都是一个ApplicationContext实例。

  • 通过ApplicationEventPublisherAware获取 ApplicationEventPublisher

     除了@Autowired注入,Spring还提供了使用ApplicationEventPublisherAware获取 ApplicationEventPublisher的方式,如果实现了这个感知接口,Spring会在合适的时机,回调setApplicationEventPublisher(),将applicationEventPublisher传递给我们。使用起来也很方便。代码所示:

public class UserService implements ApplicationEventPublisherAware {
   
    private ApplicationEventPublisher applicationEventPublisher;

    public void login(String username, String password){
   
        // 1: 进行登录处理
        ...
        // 2: 发送登录事件,用于记录操作
        applicationEventPublisher.publishEvent(new UserLoginEvent(userId));
    }

    // Aware接口回调注入applicationEventPublisher
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
   
            this.applicationEventPublisher = applicationEventPublisher;
    }
}

 现在我们已经知道通过@AutowiredApplicationEventPublisherAware回调都能获取到事件发布器,两种有什么区别吗? 其实区别不大,主要是调用时机的细小差别,另外就是默写特殊场景下,@Autowired注入可能无法正常注入,实际开发中完成可以忽略不计。所以优先推荐小伙伴们使用ApplicationEventPublisherAware,如果觉得麻烦,使用@Autowired也未尝不可。

在这里插入图片描述

如果使是自动注入模型,是无法通过setter()注入ApplicationEventPublisher的,因为在prepareBeanFactory时已经指定忽略此接口的注入了(beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class))。顺便说一句,@Autowired不算自动注入哦。

2.1.2 事件的广播方式

 现在我们已经知道,可以通过ApplicationEventPublisher发送事件了,那么这个事件发送后肯定是要分发给对应的监听器处理啊,谁处理这个分发逻辑呢?又是怎么匹配对应的监听器的呢?我们带着这两个问题来看ApplicationEventMulticaster

  • 事件是如何广播的

     要探查事件是如何广播的,需要跟随事件发布后的逻辑一起看一下:
@Override
public void publishEvent(ApplicationEvent event) {
   
    this.publishEvent(event, null);
}

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
   
    // ...省略部分代码
    if (this.earlyApplicationEvents != null) {
   
      this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
   
      // 将事件广播给Listener
      this.
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值