Spring Boot技术内幕:架构设计与实现原理
上QQ阅读APP看书,第一时间看更新

4.2 SpringApplicationRunListener监听器

4.2.1 监听器的配置与加载

让我们忽略Spring Boot计时和统计的辅助功能,直接来看SpringApplicationRunListeners的获取和使用。SpringApplicationRunListeners可以理解为一个SpringApplicationRunListener的容器,它将SpringApplicationRunListener的集合以构造方法传入,并赋值给其listeners成员变量,然后提供了针对listeners成员变量的各种遍历操作方法,比如,遍历集合并调用对应的starting、started、running等方法。

SpringApplicationRunListeners的构建很简单,图4-1中调用的getRunListeners方法也只是调用了它的构造方法。SpringApplication中getRunListeners方法代码如下。


private SpringApplicationRunListeners getRunListeners(String[] args) {
    // 构造Class数组
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    // 调用SpringApplicationRunListeners构造方法
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListeners构造方法的第二个参数便是SpringApplicationRunListener的集合,SpringApplication中调用构造方法时该参数是通过getSpringFactoriesInstances方法获取的,代码如下。


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    // 获得类加载器
    ClassLoader classLoader = getClassLoader();
    // 加载META-INF/spring.factories中对应监听器的配置,并将结果存于Set中(去重)
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 实例化监听器
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    // 排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

通过方法名便可得知,getSpringFactoriesInstances是用来获取factories配置文件中的注册类,并进行实例化操作。

关于通过SpringFactoriesLoader获取META-INF/spring.factories中对应的配置,前面章节已经多次提到,这里不再赘述。SpringApplicationRunListener的注册配置位于spring-boot项目中的spring.factories文件内,Spring Boot默认仅有一个监听器进行了注册,关于其功能后面会专门讲到。


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

我们继续看实例化监听器的方法createSpringFactoriesInstances的源代码。


private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            // 获取有参构造器
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        ...
    }
    return instances;
}

在上面的代码中,实例化监听器时需要有一个默认的构造方法,且构造方法的参数为Class<?>[ ] parameterTypes。我们向上追踪该参数的来源,会发现该参数的值为Class数组,数组的内容依次为SpringApplication.class和String[ ].class。也就是说,SpringApplicationRunListener的实现类必须有默认的构造方法,且构造方法的参数必须依次为SpringApplication和String[ ]类型。

4.2.2 SpringApplicationRunListener源码解析

接口SpringApplicationRunListener是SpringApplication的run方法监听器。上节提到了SpringApplicationRunListener通过SpringFactoriesLoader加载,并且必须声明一个公共构造函数,该函数接收SpringApplication实例和String[ ]的参数,而且每次运行都会创建一个新的实例。

SpringApplicationRunListener提供了一系列的方法,用户可以通过回调这些方法,在启动各个流程时加入指定的逻辑处理。下面我们对照源代码和注释来了解一下该接口都定义了哪些待实现的方法及功能。


public interface SpringApplicationRunListener {
    // 当run方法第一次被执行时,会被立即调用,可用于非常早期的初始化工作
    default void starting(){};
    // 当environment准备完成,在ApplicationContext创建之前,该方法被调用
    default void environmentPrepared(ConfigurableEnvironment environment) {};
    // 当ApplicationContext构建完成,资源还未被加载时,该方法被调用
    default void contextPrepared(ConfigurableApplicationContext context) {};
    // 当ApplicationContext加载完成,未被刷新之前,该方法被调用
    default void contextLoaded(ConfigurableApplicationContext context) {};
    // 当ApplicationContext刷新并启动之后,CommandLineRunner和ApplicationRunner未被调用之前,该方法被调用
    default void started(ConfigurableApplicationContext context) {};
    // 当所有准备工作就绪,run方法执行完成之前,该方法被调用
    default void running(ConfigurableApplicationContext context) {};
    // 当应用程序出现错误时,该方法被调用
    default void failed(ConfigurableApplicationContext context, Throwable exception) {};
}

我们通过源代码可以看出,SpringApplicationRunListener为run方法提供了各个运行阶段的监听事件处理功能。需要注意的是,该版本中的接口方法定义使用了Java8的新特性,方法已采用default声明并实现空方法体,表示这个方法的默认实现,子类可以直接调用该方法,也可以选择重写或者不重写。

图4-2展示了在整个run方法的生命周期中SpringApplicationRunListener的所有方法所处的位置,该图可以帮助我们更好地学习run方法的运行流程。在前面run方法的代码中已经看到相关监听方法被调用,后续的源代码中也将涉及对应方法的调用,我们可参考此图以便理解和加深记忆。

图4-2 run方法中SpringApplicationRunListener位置

4.2.3 实现类EventPublishingRunListener

EventPublishingRunListener是Spring Boot中针对SpringApplicationRunListener接口的唯一内建实现。EventPublishingRunListener使用内置的SimpleApplicationEventMulticaster来广播在上下文刷新之前触发的事件。

默认情况下,Spring Boot在初始化过程中触发的事件也是交由EventPublishingRunListener来代理实现的。EventPublishingRunListener的构造方法如下。


public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    // 创建SimpleApplicationEventMulticaster广播器
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    // 遍历ApplicationListener并关联SimpleApplicationEventMulticaster
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener);
    }
}

通过源代码可以看出,该类的构造方法符合SpringApplicationRunListener所需的构造方法参数要求,该方法依次传递了SpringApplication和String[ ]类型。在构造方法中初始化了该类的3个成员变量。

·application:类型为SpringApplication,是当前运行的SpringApplication实例。

·args:启动程序时的命令参数。

·initialMulticaster:类型为SimpleApplicationEventMulticaster,事件广播器。

Spring Boot完成基本的初始化之后,会遍历SpringApplication的所有ApplicationListener实例,并将它们与SimpleApplicationEventMulticaster进行关联,方便SimpleApplicationEvent-Multicaster后续将事件传递给所有的监听器。

EventPublishingRunListener针对不同的事件提供了不同的处理方法,但它们的处理流程基本相同。

图4-3 EventPublishingRunListener事件处理流程图

下面我们根据图4-3所示的流程图梳理一下整个事件的流程。

·程序启动到某个步骤后,调用EventPublishingRunListener的某个方法。

·EventPublishingRunListener的具体方法将application参数和args参数封装到对应的事件中。这里的事件均为SpringApplicationEvent的实现类。

·通过成员变量initialMulticaster的multicastEvent方法对事件进行广播,或通过该方法的ConfigurableApplicationContext参数的publishEvent方法来对事件进行发布。

·对应的ApplicationListener被触发,执行相应的业务逻辑。

下面是starting方法的源代码,可对照上述流程进行理解。该方法其他功能类似,代码不再做展示。


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

在上述源代码中你是否发现一个问题,某些方法是通过initialMulticaster的multicastEvent进行事件的广播,某些方法是通过context参数的publishEvent方法来进行发布的。这是为什么呢?在解决这个疑问之前,我们先看一个比较特殊的方法contextLoaded的源代码。


public void contextLoaded(ConfigurableApplicationContext context) {
    // 遍历application中的所有监听器实现类
    for (ApplicationListener<?> listener : this.application.getListeners()) {
        // 如果为ApplicationContextAware,则将上下文信息设置到该监听器内
        if (listener instanceof ApplicationContextAware) {
            ((ApplicationContextAware) listener).setApplicationContext(context);
        }
        // 将application中的监听器实现类全部添加到上下文中
        context.addApplicationListener(listener);
    }
    // 广播事件ApplicationPreparedEvent
    this.initialMulticaster.multicastEvent(
            new ApplicationPreparedEvent(this.application, this.args, context));
}

contextLoaded方法在发布事件之前做了两件事:第一,遍历application的所有监听器实现类,如果该实现类还实现了ApplicationContextAware接口,则将上下文信息设置到该监听器内;第二,将application中的监听器实现类全部添加到上下文中。最后一步才是调用事件广播。

也正是这个方法形成了不同事件广播形式的分水岭,在此方法之前执行的事件广播都是通过multicastEvent来进行的,而该方法之后的方法则均采用publishEvent来执行。这是因为只有到了contextLoaded方法之后,上下文才算初始化完成,才可通过它的publishEvent方法来进行事件的发布。

4.2.4 自定义SpringApplicationRunListener

上面我们一起学习了SpringApplicationRunListener的基本功能及实现类的源代码,现在我们自定义一个SpringApplicationRunListener的实现类。通过在该实现类中回调方法来处理自己的业务逻辑。

自定义实现类比较简单,可像通常实现一个接口一样,先创建类MyApplicationRunListener,实现接口SpringApplicationRunListener及其方法。然后在对应的方法内实现自己的业务逻辑,以下示例代码中只简单打印方法名称。与普通接口实现唯一不同的是,这里需要指定一个参数依次为SpringApplication和String[ ]的构造方法,不然在使用时会直接报错。


public class MyApplicationRunListener implements SpringApplicationRunListener {

    public MyApplicationRunListener(SpringApplication application, String[]  args){
        System.out.println("MyApplicationRunListener constructed function");
    }

    @Override
    public void starting() {
        System.out.println("starting...");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        System.out.println("environmentPrepared...");
    }
    // 在此省略掉其他方法的实现
}

当定义好实现类之后,像注册其他监听器一样,程序在spring.factories中进行注册配置。如果项目中没有spring.factories文件,也可在resources目录下先创建META-INF目录,然后在该目录下创建文件spring.factories。spring.factories中配置格式如下。


# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.secbro2.learn.listener.MyApplicationRunListener

启动Spring Boot项目,你会发现在不同阶段打印出不同的日志,这说明该实现类的方法已经被调用。