05--SpringBoot启动之事件监听机制

之前的文章分析了SpringBoot如何实例化SpringApplication对象,接下来分析run方法中的事件监听机制,在此之前先了解下Java的事件监听机制

1. Java的事件监听机制

Java自定义事件实现:

  • 自定义事件继承-->java.util.EventObject
  • 自定义事件监听器实现-->java.util.EventListener接口
    例:监听方法耗时

1.1 继承EventObject定义事件类型

package sample.simple.event;

import java.util.EventObject;

public class MethodMonitorEvent extends EventObject {

    public long timestamp;

    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public MethodMonitorEvent(Object source) {
        super(source);
    }
}

1.2 实现EventListener接口定义监听器

package sample.simple.event;

import java.util.EventListener;

public class MethodMonitorEventListener implements EventListener {

    public void onMethodBegin(MethodMonitorEvent event) {
        // 记录方法开始执行时的时间
        System.out.println("==记录方法耗时开始");
        event.timestamp = System.currentTimeMillis();
    }

    public void onMethodEnd(MethodMonitorEvent event) {
        // 计算方法耗时
        long duration = System.currentTimeMillis() - event.timestamp;
        System.out.println("==记录方法耗时结束");
        System.out.println("==耗时:" + duration);
    }
}

1.3 发布事件并测试

package sample.simple.event;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class MethodMonitorEventPublisher {
    private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>();


    public void methodMonitor() throws InterruptedException {
        MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
        publishEvent("begin", eventObject);
        // 模拟方法执行:休眠5秒钟
        TimeUnit.SECONDS.sleep(5);
        publishEvent("end", eventObject);

    }

    private void publishEvent(String status, MethodMonitorEvent event) {
        List<MethodMonitorEventListener> copyListeners = new ArrayList<MethodMonitorEventListener>(listeners);
        for (MethodMonitorEventListener listener : copyListeners) {
            if ("begin".equals(status)) {
                listener.onMethodBegin(event);
            } else {
                listener.onMethodEnd(event);
            }
        }
    }

    public void addEventListener(MethodMonitorEventListener listener) {
        listeners.add(listener);
    }


    public static void main(String[] args) throws InterruptedException {
        MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
        publisher.addEventListener(new MethodMonitorEventListener());
        publisher.methodMonitor();
    }

}

运行main函数

image.png

此例,了解了Java的事件监听机制,并简单的实现了一个观察者模式,有助于接下来对SpringBoot事件监听机制的了解

2. SpringBoot中的事件监听机制

2.1 SpringBoot中的事件发布者

接着看run方法

//根据args获取所有SpringApplicationRunListeners监听器
SpringApplicationRunListeners listeners = getRunListeners(args);

//这段代码大家应该已经熟悉了,获取SpringApplicationRunListeners扩展
private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

以上代码通过Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};类型加载对应的监听器,并创建SpringApplicationRunListener实例,看下'SpringApplicationRunListener'的构造方法

// 接受一个Log和Collection对象并赋给类成员变量
private final Log log;
private final List<SpringApplicationRunListener> listeners;

SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
    this.log = log;
    this.listeners = new ArrayList<>(listeners);
}

通过分析,SpringApplicationRunListeners类的主要作用就是存储监听器对象集合并发布各种监听事件,SpringApplicationRunListeners其本质上就是一个事件对象存储和发布者,它在SpringBoot应用启动的不同时间点委托给ApplicationEventMulticaster(下面有介绍)发布不同应用事件类型(ApplicationEvent)
SpringApplicationRunListeners会发布哪些事件呢,看源码

//首次启动run方法时立即调用。
public void starting() {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

// 一旦准备好环境,但在ApplicationContext创建环境之前调用 。
public void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}

// ApplicationContext在创建和准备之后调用,但在加载源之前调用。
public void contextPrepared(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.contextPrepared(context);
    }
}

// 在应用程序上下文加载之后但在刷新之前调用。
public void contextLoaded(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.contextLoaded(context);
    }
}

// 上下文已被刷新,并且应用程序已启动,且CommandLineRunners和ApplicationRunners未被调用。
public void started(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.started(context);
    }
}

// 在run方法完成之前立即调用,应用上下文已经被刷新,并且CommandLineRunners和ApplicationRunners已经被调用
public void running(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.running(context);
    }
}

// 在运行应用程序时发生故障时调用。
public void failed(ConfigurableApplicationContext context, Throwable exception) {
    for (SpringApplicationRunListener listener : this.listeners) {
        callFailedListener(listener, context, exception);
    }
}
2.2 SpringBoot中的事件类型

查看org.springframework.boot.context.event包下类共定义了以下事件类型

  • ApplicationEnvironmentPreparedEvent.java
  • ApplicationFailedEvent.java
  • ApplicationPreparedEvent.java
  • ApplicationReadyEvent.java
  • ApplicationStartedEvent.java
  • ApplicationStartingEvent.java
  • SpringApplicationEvent.java
    上面类的定义和作用比较简答,可查看类注释
    SpringApplicationEvent类是SpringBoot事件类的抽象基类,查看其类图关系,可以发现,该类也是通过继承java.util.EventObject实现的
image.png

注意:在该包下还有EventPublishingRunListener接口,用来发布各种事件,下面我们会详细分析

此处要注意SpringApplicationRunListenersSpringApplicationRunListener的关系
  • SpringApplicationRunListeners中包含了private final List<SpringApplicationRunListener> listeners集合
  • 真正负责事件发布的是SpringApplicationRunListener
  • SpringApplicationRunListener中又维护了SimpleApplicationEventMulticaster对象,并通过该对象将事件广播给各个监听器
2.3 SpringBoot中的事件监听器

打开spring.factories文件查看

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
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
2.4 SpringBoot中的事件注册

SpringBoot的事件,监听器,以及其发布者,事件广播器已经有所介绍,并且SpringBoot委托ApplicationEventMulticaster进行事件广播,那么,事件是何时注册到广播器的呢,通过下面的代码逐步调用,实例化EventPublishingRunListener对象时会将事件注册到广播器

//入口是SpringApplication的run方法
SpringApplicationRunListeners listeners = getRunListeners(args);
 -getRunListeners(String[] args)
  -return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    -getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args)
    -T instance = (T) BeanUtils.instantiateClass(constructor, args);

打开EventPublishingRunListener源码

public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    //创建SimpleApplicationEventMulticaster对象
    //SimpleApplicationEventMulticaster-->AbstractApplicationEventMulticaster-->ApplicationEventMulticaster
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    //从application对象中获取所有已经加载的Listener对象,循环并注册至initialMulticaster对象
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener);
    }
}
2.5 SpringBoot中的事件发布与监听

EventPublishingRunListener作为事件的发布者已经在前面初始化,当程序执行到listeners.starting();时,调用了
SpringApplicationRunListenersstarting()方法

//首次启动run方法时立即调用。
public void starting() {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

this.listeners集合中包含了EventPublishingRunListener实例,那么这里将要调用其starting()方法

private final SimpleApplicationEventMulticaster initialMulticaster;

@Override
// 广播ApplicationStartingEvent事件
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

SimpleApplicationEventMulticaster继承了AbstractApplicationEventMulticaster,其作用如下

  • 将所有事件多播到所有已注册的侦听器,将其留给侦听器以忽略他们不感​​兴趣的事件。侦听器通常instanceof 会对传入的事件对象执行相应的检查。

  • 默认情况下,在调用线程中调用所有侦听器。这允许恶意侦听器阻塞整个应用程序的危险,但增加了最小的开销。指定备用任务执行程序以使侦听器在不同的线程中执行,例如从线程池中执行。

继续跟踪源码

@Override
public void multicastEvent(ApplicationEvent event) {
    multicastEvent(event, resolveDefaultEventType(event));
}

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //getApplicationListeners(event, type)-->通过给定的事件类型,返回监听器集合
    //此处获取到的监听器有LoggingApplicationListener,
    //DelegatingApplicationListener,
    //LiquibaseServiceLocatorApplicationListener等监听器,
    //以LoggingApplicationListener为例继续debug跟踪,至于符合获取对应的监听器,放在下面分析
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        //如果上下文中有线程池则使用线程池调用
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

//根据事件执行对应的监听器
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

//执行监听器
//-->本例分析的监听器为LoggingApplicationListener
//-->事件为ApplicationStartedEvent
//大家debug时要注意看自己的事件和监听器是什么,代码跟踪会进到不同的监听器中
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) {
            // Possibly a lambda-defined listener which we could not resolve the generic event type for
            // -> let's suppress the exception and just log a debug message.
            Log logger = LogFactory.getLog(getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Non-matching event type for listener: " + listener, ex);
            }
        }
        else {
            throw ex;
        }
    }
}

@Override
//-->LoggingApplicationListener类
//判断事件类型决定调用对应的事件
public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationStartingEvent) {
        //-->被调用
        onApplicationStartingEvent((ApplicationStartingEvent) event);
    } else if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    } else if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    } else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
        onContextClosedEvent();
    } else if (event instanceof ApplicationFailedEvent) {
        onApplicationFailedEvent();
    }
}

//实例化loggingSystem并将将日志记录系统重置为限制输出。
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
    this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    this.loggingSystem.beforeInitialize();
}

至此,已经分析了SpringBoot中的事件类型,监听器类型,事件发布与监听的过程

继续上文未完成分析

2.6 SpringBoot根据事件类型获取对应的监听器集合

上面代码中有getApplicationListeners(event, type)一句话,SpringBoot是如何根据不同的事件来获取不同的监听器呢,看源码

//返回与给定事件类型匹配的ApplicationListeners集合。不匹配的Listeners会尽早被排除在外。
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
    //获取事件源,并封装至ListenerCacheKey对象
    Object source = event.getSource();
    Class<?> sourceType = (source != null ? source.getClass() : null);
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

    // Quick check for existing entry on ConcurrentHashMap...
    //尝试从缓存中获取事件
    ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
    if (retriever != null) {
        return retriever.getApplicationListeners();
    }
    //检查给定类在给定上下文中是否是缓存安全的,即它是由给定的ClassLoader还是由其父类加载。
    if (this.beanClassLoader == null || (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                    (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
        // 加锁
        synchronized (this.retrievalMutex) {
            ////抢到锁之后再做一次判断,因为有可能在前面BLOCK的时候,另一个抢到锁的线程已经设置好了缓存
            retriever = this.retrieverCache.get(cacheKey);
            if (retriever != null) {
                return retriever.getApplicationListeners();
            }
            //前面都没有能从缓存中获取到,则创建ListenerRetriever对象,
            retriever = new ListenerRetriever(true);
            //根据事件源检索对应的监听器,详解见下面
            Collection<ApplicationListener<?>> listeners = retrieveApplicationListeners(eventType, sourceType, retriever);
            //加入到retrieverCache缓存中
            this.retrieverCache.put(cacheKey, retriever);
            //返回监听
            return listeners;
        }
    }
    else {
        // No ListenerRetriever caching -> no synchronization necessary
        return retrieveApplicationListeners(eventType, sourceType, null);
    }
}

//根据事件源检索对应的监听器,以下例分析
//eventType-->org.springframework.boot.context.event.ApplicationStartingEvent
//sourceType-->class org.springframework.boot.SpringApplication
//retriever-->Helper类,它封装一组特定的目标监听器,允许有效地检索预先过滤的监听器。
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.retrievalMutex) {
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }
    //循环上下文初始化加载的所有监听器
    for (ApplicationListener<?> listener : listeners) {
        //判断监听器是否支持给定的事件,下面详解
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                retriever.applicationListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }
    //此段代码不知道干啥的...
    if (!listenerBeans.isEmpty()) {
        BeanFactory beanFactory = getBeanFactory();
        for (String listenerBeanName : listenerBeans) {
            try {
                Class<?> listenerType = beanFactory.getType(listenerBeanName);
                if (listenerType == null || supportsEvent(listenerType, eventType)) {
                    ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                        if (retriever != null) {
                            retriever.applicationListenerBeans.add(listenerBeanName);
                        }
                        allListeners.add(listener);
                    }
                }
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Singleton listener instance (without backing bean definition) disappeared -
                // probably in the middle of the destruction phase
            }
        }
    }
    //排序并返回
    AnnotationAwareOrderComparator.sort(allListeners);
    return allListeners;
}

//判断监听器是否支持给定的事件
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
    //如果监听器是GenericApplicationListener实例,则直接返回
    //否则创建GenericApplicationListenerAdapter实例
    //GenericApplicationListenerAdapter-->事件适配器
    GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
            (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
    return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

至此,我们已经分析了SpringBoot根据事件类型获取对应的监听器集合,细节还很多,还有很多没分析到的地方,篇幅限制,不能把所有的代码都粘贴出来,大家谅解!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,273评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,349评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,709评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,520评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,515评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,158评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,755评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,660评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,203评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,287评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,427评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,122评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,801评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,272评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,393评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,808评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,440评论 2 359

推荐阅读更多精彩内容

  • /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home...
    光剑书架上的书阅读 3,888评论 2 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,693评论 18 139
  • 阿菱:好大的雨呀。 阿球:来也冲冲,去也匆匆。多姿多彩,滑盖缤纷。 阿菱:别站着说话不腰疼,还不都是为了生活?哎,...
    云水坡头阅读 301评论 0 0
  • 凶杀案 下雨的早晨 (细节刻画,小清新,注重时间,结尾突兀。) 失控的双重人格患者 郁郁的寻找亲人栏目主持人 所谓...
    美女一号阅读 176评论 0 1
  • 忙碌的一天。 床上瞬间回顾过去十年,再次感到立场的重要性。中午、晚上餐桌,不知不觉又吃多了,如果说喝酒是氛围的事情...
    天之心语阅读 144评论 0 1