SpringBoot系统学习 - 启动篇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37499059/article/details/82313948

1.前言

到这里,我们对springBoot对常用的工具整合都有一定的了解了,那我们是否想过:springboot启动的过程都干了些啥事情啊? 好好想一想?Bean注入容器,配置注入…?


2.启动流程示意图

SpringBoot将spring应用的启动流程进行了一个“模板化”的操作,所以我们才能通过SpringApplication.run(XXX.class, args)的方式来进行一站式的启动。其内部逻辑也是个较复杂的过程,下文将对执行流程进行阐述。本流程参考的SpringBoot版本为1.4.3.RELEASE。
这里写图片描述

先有个印象就好。


1)SpringApplicationRunListener
SpringApplicationRunListener是SpringBoot执行过程中,不同执行时间点时间通知的监听者,一般来说也没有必要自己实现一个SpringApplicationRunListener,即使是SpringBoot默认也只实现了一个org.springframework.boot.context.event.Event
PublishingRunListener。通过这个类,在SpringBoot启动时,在不同的时间点发布不同的应用事件类型ApplicationEvent。

SpringBoot初始化时加载的ApplicationListener如果对这些事件感兴趣,则可以接收并处理。

public interface SpringApplicationRunListener {
    void started();
    void environmentPrepared(ConfigurableEnvironment environment);
    void contextPrepared(ConfigurableApplicationContext context);
    void contextLoaded(ConfigurableApplicationContext context);
    void finished(ConfigurableApplicationContext context, Throwable exception);
}

2) ApplicationContextInitializer
通过这个类,可以在ApplicationContext调用refresh()方法前,对ApplicationContext对象做进一步的设置或者处理。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}

3) ApplicationRunner和CommandLineRunner
需要在容器启动的时候执行一些内容。比如读取配置文件,数据库连接之类的。SpringBoot给我们提供了两个接口来帮助我们实现这种需求。这两个接口分别为CommandLineRunner和ApplicationRunner。他们的执行时机为容器启动完成的时候。

public interface ApplicationRunner {
    void run(ApplicationArguments args) throws Exception;
}

public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

3.执行流程

这里写图片描述
这里写图片描述
这里写图片描述

new SpringApplication(primarySources)干了那些事情呢?
这里写图片描述
共干了4件事:

1.推断应用类型是Standard还是Web
这里写图片描述

private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null)) {
            return WebApplicationType.REACTIVE;
        } else {
            String[] var1 = WEB_ENVIRONMENT_CLASSES;
            int var2 = var1.length;
            for(int var3 = 0; var3 < var2; ++var3) {
                String className = var1[var3];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return WebApplicationType.NONE;
                }
            }
            return WebApplicationType.SERVLET;
        }
    }

可能会出现三种结果:
这里写图片描述

1) WebApplicationType.REACTIVE - 当类路径中存在REACTIVE_WEB_ENVIRONMENT_CLASS并且不存在MVC_
WEB_ENVIRONMENT_CLASS时
2) WebApplicationType.NONE - 也就是非Web型应用(Standard型),此时类路径中不包含WEB_ENVIRONMENT_CLASSES中定义的任何一个类时
3) WebApplicationType.SERVLET - 类路径中包含了WEB_ENVIRONMENT_CLASSES中定义的所有类型时


2.设置初始化器(Initializer)
这里写图片描述

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

// 这里的入参type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 使用Set保存names来避免重复元素
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 根据names来进行实例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    // 对实例进行排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

这里面首先会根据入参type读取所有的names(是一个String集合),然后根据这个集合来完成对应的实例化操作:
这里写图片描述
从类路径的META-INF/spring.factories处读取相应配置文件,然后进行遍历,读取配置文件中Key为:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure这个包为例,它的META-INF/spring.factories部分定义如下所示:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

初始化步骤很直观,没什么好说的,类加载,确认被加载的类确实是org.springframework.context.ApplicationContextInitializer的子类,然后就是得到构造器进行初始化,最后放入到实例列表中。

因此,所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,这个接口是这样定义的:
这里写图片描述
根据类文档,这个接口的主要功能是:
在Spring上下文被刷新之前进行初始化的操作。典型地比如在Web应用中,注册Property Sources或者是激活Profiles。Property Sources比较好理解,就是配置文件。Profiles是Spring为了在不同环境下(如DEV,TEST,PRODUCTION等),加载不同的配置项而抽象出来的一个实体。


3.设置监听器(Listener)
这里写图片描述

// 这里的入参type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

可以发现,这个加载相应的类名,然后完成实例化的过程和上面在设置初始化器时如出一辙,同样,还是以spring-boot-autoconfigure这个包中的spring.factories为例,看看相应的Key-Value:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

ApplicationListener接口,它是Spring框架中一个相当基础的接口了,代码如下:
这里写图片描述
这个接口基于JDK中的EventListener接口,实现了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型需要是ApplicationEvent类型的子类,而这个类同样是继承自JDK中的EventObject类。


4) 推断应用入口类
这里写图片描述

这里写图片描述

至此,对于SpringApplication实例的初始化过程就结束了。

构造完了,我们执行run方法咯
这里写图片描述
SpringApplication.run方法

// 运行run方法
public ConfigurableApplicationContext run(String... args) {
  // 计时工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    // 设置java.awt.headless系统属性为true - 没有图形化界面
    configureHeadlessProperty();

    // KEY 1 - 获取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);

    // 发出开始执行的事件
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);

        // KEY 2 - 根据SpringApplicationRunListeners以及参数来准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
        Banner printedBanner = printBanner(environment);

        // KEY 3 - 创建Spring上下文
        context = createApplicationContext();

        // 准备异常报告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // KEY 4 - Spring上下文前置处理
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);

        // KEY 5 - Spring上下文刷新
        refreshContext(context);

        // KEY 6 - Spring上下文后置处理
        afterRefresh(context, applicationArguments);

        // 发出结束执行的事件
        listeners.finished(context, null);

        // 停止计时器
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
}

这个run方法包含的内容也是有点多的,根据上面列举出的关键步骤逐个进行分析:

1) 第一步 - 获取所谓的run listeners:
2)第二步 - 根据SpringApplicationRunListeners以及参数来准备环境
3)第三步 - 创建Spring上下文
4) 第四步 - Spring上下文前置处理
5) 第五步 - Spring上下文刷新
6) 第六步 - Spring上下文后置处理

1) 第一步 - 获取所谓的run listeners:
这里仍然利用了getSpringFactoriesInstances方法来获取实例:所以这里还是故技重施,从META-INF/spring.factories中读取Key为org.springframework.boot.SpringApplicationRunListener的Values:
比如在spring-boot包中的定义的spring.factories:

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

2)第二步 - 根据SpringApplicationRunListeners以及参数来准备环境
对于Web应用而言,得到的environment变量是一个StandardServletEnvironment的实例。得到实例后,会调用前面RunListeners中的environmentPrepared方法:

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
            this.application, this.args, environment));
}

在这里,定义的广播器就派上用场了,它会发布一个ApplicationEnvironmentPreparedEvent事件。

那么有发布就有监听,在构建SpringApplication实例的时候不是初始化过一些ApplicationListeners嘛,其中的Listener就可能会监听ApplicationEnvironmentPreparedEvent事件,然后进行相应处理。

**3) 第三步 - 创建Spring上下文
对于我们的Web应用,上下文类型就是DEFAULT_WEB_CONTEXT_CLASS。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

// WEB应用的上下文类型
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

4) 第四步 - Spring上下文前置处理
配置Bean生成器以及资源加载器(如果它们非空)
调用初始化器:创建SpringApplication实例时设置的初始化器了,依次对它们进行遍历,并调用initialize方法。

5) 第五步 - Spring上下文刷新
注册关闭容器时的钩子函数的默认实现是在AbstractApplicationContext类中:
如果没有提供自定义的shutdownHook,那么会生成一个默认的,并添加到Runtime中。默认行为就是调用它的doClose方法,完成一些容器销毁时的清理工作。

6) 第六步 - Spring上下文后置处理
所谓的后置操作,就是在容器完成刷新后,依次调用注册的Runners。Runners可以是两个接口的实现类:

org.springframework.boot.ApplicationRunner
org.springframework.boot.CommandLineRunner

至此,SpringApplication的run方法就分析完毕了。


总结

本文分析了Spring Boot启动时的关键步骤,主要包含以下两个方面:
SpringApplication实例的构建过程
其中主要涉及到了初始化器(Initializer)以及监听器(Listener)这两大概念,它们都通过META-INF/spring.factories完成定义。

SpringApplication实例run方法的执行过程
其中主要有一个SpringApplicationRunListeners的概念,它作为Spring Boot容器初始化时各阶段事件的中转器,将事件派发给感兴趣的Listeners(在SpringApplication实例的构建过程中得到的)。这些阶段性事件将容器的初始化过程给构造起来,提供了比较强大的可扩展性。

如果从可扩展性的角度出发,应用开发者可以在Spring Boot容器的启动阶段,扩展哪些内容呢:

初始化器(Initializer)
监听器(Listener)
容器刷新后置Runners(ApplicationRunner或者CommandLineRunner接口的实现类)
启动期间在Console打印Banner的具体实现类

猜你喜欢

转载自blog.csdn.net/m0_37499059/article/details/82313948
今日推荐