Springboot事件监听机制详解

在SpringBoot启动的整个过程中,有一个很重要的机制,叫事件监听机制。
这个机制如他的名字,在启动的各个阶段,SpringBoot会发布一些事件,而对应的监听器监听到事件之后会做相应的处理。
监听器机制的优点是,当一个事件发布后,会有不同的监听器来处理,如果用户想在这个事件发生时做其他处理,就可以添加一个新的监听器监听这个事件,就有了很强的扩展性。
那么接下来我们就来了解一下SpringBoot监听事件的机制和流程,还有SpringBoot启动过程中所有的事件类型,和事件发布后哪些监听器做了什么处理。

1. 获取SpringFactory中的Listener

众所周知,SpringBoot启动时需要调用SpringApplication.run方法,或者自己new出来一个SpringApplication对象之后调用run方法。
和之前的ApplicationContextInitializer相同,SpringBoot所需要的SpringFactory中所有的ApplicationListener也是在new SpringApplication对象时加载进来的。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

上述代码中倒数第二行,setListeners方法,就是将所有配置在spring.factories文件中,key值为 org.springframework.context.ApplicationListener的类实例化后,设置到SpringApplication对象的listeners属性当中。
没有任何添加的情况下,会有11个Listener被实例化,分别从不同的spring依赖中获取:

  • spring-boot-autoconfigure
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
  • spring-boot
    org.springframework.boot.ClearCachesApplicationListener
    org.springframework.boot.builder.ParentContextCloserApplicationListener
    org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor
    org.springframework.boot.context.FileEncodingApplicationListener
    org.springframework.boot.context.config.AnsiOutputApplicationListener
    org.springframework.boot.context.config.ConfigFileApplicationListener
    org.springframework.boot.context.config.DelegatingApplicationListener
    org.springframework.boot.context.logging.ClasspathLoggingApplicationListener
    org.springframework.boot.context.logging.LoggingApplicationListener
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

以上的Listeners是真正监听到事件之后会去处理逻辑的监听器,但是想要发布事件并且让正确的监听器监听到对应事件,SpringApplicationRunListener是不可或缺的。

2. 获取SpringApplicationRunListener

在SpringApplication的核心,run方法之中,有一个getRunListeners方法

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    ......

进入getRunListeners方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

可以看到也是同样从SpringFactory获取到所有的以org.springframework.boot.SpringApplicationRunListener为key值的RunListener,直接放进了new出来的SpringApplicationRunListeners对象之中。
默认情况下,SpringBoot只有一个ApplicationRunListener,就是在spring-boot依赖中sping.factories定义的org.springframework.boot.context.event.EventPublishingRunListener

3. 发布事件

在SpringBoot启动过程中会在固定的阶段发布不同的时间,比如Starting事件,PrepareEnv事件,Started事件等等。这里我们用Starting事件举个例子:
在上面getRunListeners方法的下面一行 listeners.starting(); 这里就是进入了SpringApplicationRunListeners中对应的方法。

void starting() {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

在方法中我们可以看到,SpringApplicationRunListeners会遍历对象当中所有的RunListener,调用他们的starting方法。而在上一节中,我们已经知道默认情况下SpringBoot只会加载一个RunListener,就是org.springframework.boot.context.event.EventPublishingRunListener。所以循环遍历就只有一个类,我们进入EventPublishingRunListener的starting方法看一下。

@Override
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

这里有一个this.initialMulticaster,这是SpringBoot整个事件监听机制的核心之一,事件广播器,他是在什么时候被初始化的呢?我们看下面的code:

public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener);
    }
}

这是EventPublishingRunListener的构造方法,我们已经知道在getRunListeners方法中,EventPublishingRunListener被初始化成实例对象,在这个时候可以看到一个名为SimpleApplicationEventMulticaster的类被new出来赋给了EventPublishingRunListener的initialMulticaster属性。

我们再回到starting()方法当中,SimpleApplicationEventMulticaster的multicastEvent方法被调用,发布的事件是一个new出来的ApplicationStartingEvent事件。

接下来我们看一下发布一个事件的具体逻辑。

4. SimpleApplicationEventMulticaster的multicastEvent方法

核心逻辑如下:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

在这个方法中,有一个getApplicationListeners方法,这个方法是广播事件最核心的一个逻辑。我们已经知道在最开始的时候,SpringApplication通过SpringFactoryLoader将11个ApplicationListener加载了进来,这个方法他的作用就是找到这11中所有关注了这个事件的监听器,之后就能遍历这些监听器去调用监听器里面的逻辑。

这里面的具体逻辑比较复杂,我在这里就不把源码贴出来了,感兴趣的同学可以自行阅读。我在这里用两张图总结一下其中的逻辑。

image.png
image.png

其中的核心在supportEventType这个方法当中,如图所示有两种情况:

  1. 当前ApplicationListener实现了SmartApplicationListener接口
    1.1 判断supportEventType方法,如果支持该event,则进行下一步判断,否则直接返回false
    1.2 如果supportEventType支持该event,继续判断supportSourceType,即判断是否支持当前入口函数对象
  2. 当前ApplicationListener未实现SmartApplicationListener接口,只需判断supportEventType,如果返回true,则该监听器支持当前event,否则不支持。

这里有个小问题,这些ApplicationListener为什么会继承不同的接口,他们之间有什么区别呢?

  • 一般SpringBoot中的listener想要监听一个事件,会继承ApplicationListener接口,需要指定一个event的泛型,实现onApplicationEvent。泛型是为了指定这个监听器关注那个event事件,onApplicationEvent就是当监听到事件后的处理逻辑。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);
}
  • 如果在一个listener内想要监听多个事件,则需要继承SmartApplicationListener接口。SmartApplicationListener继承ApplicationListener并关注所有类型为ApplicationEvent的事件,所有使用他的时候必须实现supportEventType方法,去指定某几个event需要被你实现SmartApplicationListener关注。
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
    /**
     * Determine whether this listener actually supports the given event type.
     * @param eventType the event type (never {@code null})
     */
    boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
    /**
     * Determine whether this listener actually supports the given source type.
     * <p>The default implementation always returns {@code true}.
     * @param sourceType the source type, or {@code null} if no source
     */
    default boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }
    /**
     * Determine this listener's order in a set of listeners for the same event.
     * <p>The default implementation returns {@link #LOWEST_PRECEDENCE}.
     */
    @Override
    default int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容