Spring Boot-4-监听器实现

上文中用一个简单的案例,来了解了一下监听器的实现,下面再来看一下监听器在Spring Boot框架中的实现,围绕以下四个方面来看以下:

  • 事件
  • 监听器
  • 广播器
  • 触发机制

监听器

ApplicationListener 这个类,就是系统监听器的实现,代码如下:

image

这里就是一个函数式接口,然后这里有一个继承自 ApplicationEvent 的泛型,可以在声明的时候,表示对哪种事件感兴趣,然后里面的一个方法就是,监听到感兴趣的事件时,要做的事情.

这个接口跟我们之前demo中的 WeatherListener 是类似的

广播器

ApplicationEventMulticaster 是系统广播器的接口, 看下源码:

image

这里的几个方法,其实就是添加监听器,移除监听器,广播事件这三种,和我们demo中的 EventMulticaster 也是对应的

事件

Spring Boot 的系统事件,类图如下:

image

这里一共有 7 个事件,作用分别如下:

  • ApplicationStartingEvent: ApplicationStartingEventSpringBoot 启动开始的时候执行的事件,在该事件中可以获取到 SpringApplication 对象,可做一些执行前的设置.
  • ApplicationEnvironmentPreparedEvent: 表示系统环境变量已经创建完成,但是上下文 context 还没有创建
  • ApplicationContextInitializedEvent: 表示 ApplicationContext 上下文已经准备好
  • ApplicationPreparedEvent: 表示 SpringBoot上下文 context 创建完成,但此时 Spring 中的 bean 还没有完全加载完成.这里可以将上下文传递出去做一些额外的操作,但是在该监听器中是无法获取自定义 bean 并进行操作的.
  • ApplicationStartedEvent: 表示所有的bean已经准备完成,但是还没有调用 ApplicationRunnerCommandLineRunner
  • ApplicationReadyEvent: ApplicationRunnerCommandLineRunner 已经完成调用了,也意味着 SpringBoot 加载已经完成.
  • ApplicationFailedEvent: 容器在启动的时候发生异常的时候发送

事件发发送顺序

ApplicationStartingEvent -> ApplicationEnvironmentPreparedEvent -> ApplicationContextInitializedEvent -> ApplicationPreparedEvent -> ApplicationStartedEvent -> ApplicationReadyEvent

监听器注册

Spring Boot中提供了很多监听器,下面来看一下这些监听器是如何被注册到容器中的

类似之前的系统初始化器,我们来看一下源码, 如下:

image

这里其实和之前的系统初始化器的加载方式是一样的,都是通过 SpringFactoriesLoader 来加载的,点进去看一下源码:

监听器注册2

这里和之前的系统初始化器是一样的,就不再详细介绍了

触发机制

上面了解了事件,监听器,广播器,然后四个要素中还有触发机制,这些事件是怎么被发送的,下面来详细看一下

以 starting 事件为例,这个是在框架启动,也就是调用run方法的时候,进入run方法看一下源码,如下:

image

这里会把所有的监听器都封装到一个 SpringApplicationRunListeners 对象中,这个其实就是 SpringApplicationRunListener 对象的一个集合
进入这个 starting 方法看一下,如下:

image

可以看到,这里是去调用的 SpringApplicationRunListenerstarting() 方法,继续往下一级去看,进入 SpringApplicationRunListener 如下:

image

这里其实就是对所有系统事件的一个封装,在我们之前的demo中,我们是手动创建广播器,然后创建事件发送, 那这里其实就是对这一步进行了一个封装, Spring 容器在运行过程中是不需要构建具体的事件,然后再调用广播器广播, 都是通过这个方法完成的, 以 starting() 方法为例, 我们进入子类看一下:

image

这里首先找到了广播器,然后发送了一个 ApplicationStartingEvent 事件

案例改造

以之前的天气案例为基础,我们也将代码改造成 Spring Boot 这种方式来试一下

首先,创建一个 WeatherRunListener 对应上面的 SpringApplicationRunListener, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class WeatherRunListener {

@Autowired
private WeatherEventMulticaster weatherEventMulticaster;

/**
* 发送下雪事件
*/
public void snow(){
weatherEventMulticaster.multicastEvent(new SnowEvent());
}

/**
* 发送下雨事件
*/
public void rain(){
weatherEventMulticaster.multicastEvent(new RainEvent());
}
}

这里需要给 WeatherEventMulticaster 加上 @Component 注解, 同样的 RainListenerSnowListener 也需要加上 @Component 注解
然后修改 AbstractEventMulticaster , 如下:

1
2
3
4
5
/**
* 监听器的集合
*/
@Autowired
private List<WeatherListener> listeners;

这里只修改存放 WeatherListener 的集合即可, RainListenerSnowListener 已经交给了Spring容器去管理,所以这里直接让Spring容器去注入就好了

然后写个单元测试:

1
2
3
4
5
@Test
public void test1(){
weatherRunListener.rain();
weatherRunListener.snow();
}

执行,控制台输出如下:

1
2
3
4
5
6
2020-02-02 18:28:21.830  INFO 16736 --- [    Test worker] c.z.s.e.m.WeatherEventMulticaster        : begin broadcast weather event...
2020-02-02 18:28:21.831 INFO 16736 --- [ Test worker] c.z.s.example.listener.RainListener : Its Rain
2020-02-02 18:28:21.835 INFO 16736 --- [ Test worker] c.z.s.e.m.WeatherEventMulticaster : end broadcast weather event ...
2020-02-02 18:28:21.835 INFO 16736 --- [ Test worker] c.z.s.e.m.WeatherEventMulticaster : begin broadcast weather event...
2020-02-02 18:28:21.836 INFO 16736 --- [ Test worker] c.z.s.example.listener.SnowListener : Its snow
2020-02-02 18:28:21.836 INFO 16736 --- [ Test worker] c.z.s.e.m.WeatherEventMulticaster : end broadcast weather event ...

这种方式就和 Spring Boot 发送事件的方式一样,做了一次封装

事件广播源码

还是以 starting 方法为例,看一下广播器具体是怎么发送事件的

image

这里点击进入广播器的发送方法

image

这里的重点是 getApplicationListeners 这个方法,获取对当前事件感兴趣的监听器的集合,点进去往下看

image

这里主要是从缓存找,然后找不到的话再去计算,计算完成后放入缓存中,供下次使用,再进入具体计算的方法

image

进入 supportsEvent() 继续看

image

这里做了一次转换,如果不是 就做个适配 new GenericApplicationListenerAdapter(listener) ,把监听器都转成 GenericApplicationListener 然后做判断

image

总之这里是要转成一个 GenericApplicationListener 然后再做判断

image

image

这里打个断点走一下会更清晰

总结

  • 了解监听器是如何注册到 Spring 容器中的
  • Spring Boot 中的事件发送机制
  • 如何获取事件的所有感兴趣的监听器