组里的实习生妹妹讲了一次Springboot的发动,

讲着讲着就讲到Springbean的生命周期去了,

我心想坏了,

这妮子估计把SpringbootSpring的相关逻辑给混杂了,

这必须得给她治一治。


前言

本文会对Springboot发动流程进行详细剖析。但是请注意,Springboot发动流程是Springboot的逻辑,请千万不要将Springboot发动流程相关逻辑与Spring的相关逻辑混在一同,比如把Springbean生命周期的逻辑混在Springboot发动流程中,那么整个体系就复杂且混乱了。

所以本文仅重点关注Springboot发动流程,触及Spring的部分,会略作阐明并越过。

全体的一个结构图如下。

详细分析Springboot启动流程

Springboot版别:2.4.1

正文

一. Springboot发动流程图及阐明

如下是Springboot的一个发动流程图。

详细分析Springboot启动流程

SpringApplication完结初始化后,就会调用SpringApplication目标的run() 办法,该办法便是Springboot发动的进口,也对应着全流程图中的开端。下面给出SpringApplication目标的run() 办法阐明,如下所示。

public ConfigurableApplicationContext run(String... args) {
    // 创立StopWatch,用于统计Springboot发动的耗时
    StopWatch stopWatch = new StopWatch();
    // 开端计时
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    // 获取运行时监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 调用运行时监听器的starting()办法
    // 该办法需要在Springboot一发动时就调用,用于特别早期的初始化
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        // 获取args参数目标
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 读取Springboot装备文件并创立Environment目标
        // 这儿创立的Environment目标实践为ConfigurableEnvironment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 打印Banner图标
        Banner printedBanner = printBanner(environment);
        // 创立ApplicationContext运用行下文,即创立容器
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 准备容器
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 初始化容器
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        // 停止计时
        stopWatch.stop();
        if (this.logStartupInfo) {
            // 打印发动耗时等信息
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 调用运行时监听器的started()办法
        // 该办法需要在运用程序发动后,CommandLineRunners和ApplicationRunners被调用前履行
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        // 调用运行时监听器的running()办法
        // 该办法需要在SpringApplication的run()办法履行完之前被调用
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

二. SpringApplication的初始化

一般,Springboot运用程序的发动类定义如下。

@SpringBootApplication
public class LearnStartApplication {
    public static void main(String[] args) {
        SpringApplication.run(LearnStartApplication.class, args);
    }
}

SpringApplication的静态run() 办法一路跟进,会发现如下的完结。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

也便是Springboot发动时会先创立SpringApplication,然后再经过SpringApplicationrun() 办法完结发动。所以下面剖析一下SpringApplication的初始化逻辑,其结构办法如下所示。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 设置源
    // 一般Springboot的发动类便是源
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 揣度并设置WEB运用程序类型
    // 依据classpath下的类来揣度
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加载并设置Bootstrapper
    this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    // 加载并设置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 加载并设置运用作业监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 揣度并设置运用程序主类的Class目标
    this.mainApplicationClass = deduceMainApplicationClass();
}

整理一下在SpringApplication的结构办法中,做了如下作业。

  1. 设置源。一般,Springboot中的源便是Springboot的发动类;
  2. 设置WEB运用程序类型。经过判别classpath下是否存在某些类,来揣度当时WEB运用程序的类型;
  3. 加载并设置BootstrapperApplicationContextInitializerApplicationListener。凭借SpringFactoriesLoader依据SPI机制完结BootstrapperApplicationContextInitializerApplicationListener的加载,然后设置到SpringApplication中;
  4. 设置运用程序主类的Class目标。

下面对上述作业进行剖析。

1. 设置源

这儿的源,也便是Spring容器发动时依靠的初始装备类,在Springboot中,初始装备类一般为发动类。下面能够经过调试看一下primarySources字段的值,如下所示。

详细分析Springboot启动流程

可见源便是Springboot的发动类的Class目标。

2. 设置WEB运用程序类型

WebApplicationType#deduceFromClasspath办法如下所示。

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS 
                    = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS 
                    = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        // classpath下存在DispatcherHandler,但不存在DispatcherServlet和ServletContainer
        // 则WEN运用程序类型揣度为REACTIVE,即呼应式WEB运用程序
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            // 非WEB运用程序
            return WebApplicationType.NONE;
        }
    }
    // 依据Servlet的WEB运用程序
    return WebApplicationType.SERVLET;
}

WebApplicationType中预定义了若干种用于判别的类的全限定名,然后在deduceFromClasspath() 办法中运用ClassUtils来判别预定义的类是否存在,经过这样的办法终究能够揣度出当时WEB运用程序类型。在示例工程中,假如只引进spring-boot-starter包,那么揣度出来的WebApplicationTypeNONE,如下所示。

详细分析Springboot启动流程

假如再引进spring-boot-starter-web包,则揣度出来的WebApplicationTypeSERVLET,如下所示。

详细分析Springboot启动流程

3. 加载并设置Bootstrapper,ApplicationContextInitializer和ApplicationListener

这儿首要剖析一下是怎样加载BootstrapperApplicationContextInitializerApplicationListener的。它们的加载均运用了getSpringFactoriesInstances() 办法,下面看一下完结。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 经过SpringFactoriesLoader扫描classpath一切jar包的META-INF目录下的spring.factories文件
    // 将type全限定名对应的全限定名的调集获取到
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 实例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

首要便是依据SpringFactoriesLoader完结加载,加载机制和Springboot中的主动装配是一样,仅有的差异便是主动装配中在spring.factories文件中是依据@EnableAutoConfiguration的全限定名作为key去获取全限定名调集,而在这儿是依据BootstrapperApplicationContextInitializerApplicationListener的全限定名作为key去获取全限定名调集,以spring-boot-autoconfigure包中的spring.factories文件为例,阐明如下。

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

4. 设置运用程序主类的Class目标

获取运用程序主类的Class目标的SpringApplication#deduceMainApplicationClass办法如下所示。

private Class<?> deduceMainApplicationClass() {
    try {
        // 经过RuntimeException获取堆栈
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            // 判别堆栈元素的发生办法名是否为main
            if ("main".equals(stackTraceElement.getMethodName())) {
                // 经过反射获取到main办法地点类的Class目标
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
    }
    return null;
}

获取运用程序主类的Class目标是经过堆栈完结的,下面给出调试截图。

详细分析Springboot启动流程

三. Springboot作业机制

Springboot发动的一开端,有一步逻辑是获取运行时监听器,终究会获取到一个SpringApplicationRunListeners目标,下面看一下获取运行时监听器的getRunListeners() 办法的完结。

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    // 先依据SpringFactoriesLoader的SPI机制获取SpringApplicationRunListener的完结类调集
    // 然后创立SpringApplicationRunListeners目标
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
            this.applicationStartup);
}
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners,
    ApplicationStartup applicationStartup) {
    // SpringApplicationRunListeners的结构办法中只是进行简单赋值
    this.log = log;
    this.listeners = new ArrayList<>(listeners);
    this.applicationStartup = applicationStartup;
}

getRunListeners() 办法中会先依据SpringFactoriesLoaderSPI机制将SpringApplicationRunListener接口的完结类获取出来,在spring-boot包中供给了一个SpringApplicationRunListener接口的完结类,为EventPublishingRunListener,这也是Springboot供给的仅有一个内置运行时监听器,所以经过getRunListeners() 办法获取到的SpringApplicationRunListeners目标中持有一个SpringApplicationRunListener的调集,这个调集中默许情况下一定会包括一个EventPublishingRunListener的目标。

下面再以SpringApplicationRunListenersstarting() 办法为例,剖析一下SpringApplicationRunListeners是怎样作业的。

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
            (step) -> {
                if (mainApplicationClass != null) {
                    step.tag("mainApplicationClass", mainApplicationClass.getName());
                }
            });
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
        Consumer<StartupStep> stepAction) {
    StartupStep step = this.applicationStartup.start(stepName);
    // 调集中每个运行时监听器都会履行listenerAction函数
    this.listeners.forEach(listenerAction);
    if (stepAction != null) {
        stepAction.accept(step);
    }
        step.end();
}

结合SpringApplicationRunListenersstarting()doWithListeners() 办法,可知SpringApplicationRunListeners会将starting() 办法的调用传递给其持有的每个运行时监听器,所以SpringApplicationRunListeners是组合模式的一个运用。

那么Springboot中的作业机制按理应该由Springboot供给的仅有一个运行时监听器EventPublishingRunListener完结。下面剖析EventPublishingRunListener的逻辑,还是以EventPublishingRunListenerstarting() 办法为例,进行阐明。

public void starting(ConfigurableBootstrapContext bootstrapContext) {
    // 先创立一个ApplicationStartingEvent作业目标
    // 然后调用SimpleApplicationEventMulticaster来发布作业目标
    this.initialMulticaster
            .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}

EventPublishingRunListenerstarting() 办法中会先创立ApplicationStartingEvent作业目标,然后经过EventPublishingRunListener持有的一个SimpleApplicationEventMulticaster目标来发布作业。

那么下面持续剖析SimpleApplicationEventMulticaster怎样发布作业,发布给谁,SimpleApplicationEventMulticastermulticastEvent() 办法如下所示。

public void multicastEvent(ApplicationEvent event) {
    multicastEvent(event, resolveDefaultEventType(event));
}
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    // 调用getApplicationListeners()办法将一切合适接纳当时作业的ApplicationListener获取出来
    // 然后依据异步或者同步的办法向契合条件的ApplicationListener发布作业
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            // 异步发布
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            // 同步发布
            invokeListener(listener, event);
        }
    }
}

SimpleApplicationEventMulticastermulticastEvent() 办法中会先将初始化SpringApplication时加载的ApplicationListener获取到,然后遍历其中合适接纳当时作业的ApplicationListener,然后异步或者同步的向ApplicationListener发布作业,持续看invokeListener() 办法,如下所示。

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    // 实践调用doInvokeListener()办法来向ApplicationListener发布作业
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        // ApplicationListener接口的完结类都会完结onApplicationEvent()办法
        // 在onApplicationEvent()办法中会处理当时接纳到的作业
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
            Log logger = LogFactory.getLog(getClass());
            if (logger.isTraceEnabled()) {
                logger.trace("Non-matching event type for listener: " + listener, ex);
            }
        }
        else {
            throw ex;
        }
    }
}

SimpleApplicationEventMulticasterinvokeListener() 办法中实践会调用到doInvokeListener() 办法,在doInvokeListener() 办法中会调用ApplicationListeneronApplicationEvent() 办法,所以在这儿就调用到了ApplicationListener实践处理作业的逻辑。

现在对Springboot中的作业监听机制进行小结。

  1. SpringApplication初始化时会加载一切的ApplicationListener
  2. Springboot发动的一开端,会调用到SpringApplication#getRunListeners办法创立一个SpringApplicationRunListeners目标;
  3. SpringApplicationRunListeners是组合模式的运用,其持有一个SpringApplicationRunListener的调集,调集中默许会存在一个Springboot供给的SpringApplicationRunListener的完结类EventPublishingRunListener,一切对SpringApplicationRunListeners的调用恳求都会被传递给调集中的每一个SpringApplicationRunListener
  4. EventPublishingRunListener中持有一个作业发布器SimpleApplicationEventMulticaster,在EventPublishingRunListener的结构函数中,会将SimpleApplicationEventMulticaster创立出来并将SpringApplication中的一切ApplicationListener设置给SimpleApplicationEventMulticaster。当EventPublishingRunListenerstarting()environmentPrepared() 等办法被调用时,EventPublishingRunListener会创立对应的作业(ApplicationStartingEventApplicationEnvironmentPreparedEvent)并经过SimpleApplicationEventMulticaster向合适接纳当时作业的ApplicationListener发布;
  5. SimpleApplicationEventMulticaster发布作业时,会先获取出一切合适接纳当时作业的ApplicationListener,然后调用这些ApplicationListeneronApplicationEvent() 办法,每一个ApplicationListener会在其完结的onApplicationEvent() 办法中完结对作业的处理。

图示如下。

详细分析Springboot启动流程

四. 外部化装备加载

Springboot发动时,会在调用运行时监听器的starting() 办法后创立DefaultApplicationArguments目标,然后就会开端加载外部化装备。

外部化装备一般由application.yml文件(或者application.properties文件)供给,在application.yml文件中增加装备项也是最常用的外部化装备办法。实践上为Springboot运用程序增加外部化装备的办法还有许多种,能够参考Springboot-外部化装备,下面是较为常用的外部化装备办法的优先级(由上到下优先级逐渐下降)。

  1. 命令行参数,即Command line arguments
  2. JAVA体系特点,即Java System propertiesSystem#getProperties);
  3. 操作体系环境变量,即OS environment variables
  4. 装备数据文件(例如application.yml文件),即Config datasuch as application.properties files
    1. jar包外指定了profile的装备数据文件:application-{profile}.yml
    2. jar包外的装备数据文件:application.yml
    3. jar包内指定了profile的装备数据文件:application-{profile}.yml
    4. jar包内的装备数据文件:application.yml
  5. 作用在由@Configuration注解润饰的类上的@PropertySource注解,即@PropertySource annotations on your @Configuration classes
  6. 默许特点,即Default propertiesspecified by setting SpringApplication#setDefaultProperties)。

Springboot在发动过程中的SpringApplication#prepareEnvironment办法中会加载上述的外部化装备为EnvironmentEnvironmentSpringboot外部化装备的进口,经过Environment能够获取到Springboot加载的一切外部化装备。

下图给出了SpringApplication#prepareEnvironment办法履行完后Environment的详细信息。

详细分析Springboot启动流程

可见Environment的实践类型为StandardServletEnvironment,这是和Springboot的运用程序类型挂钩,这点后面再说。StandardServletEnvironment内部持有一个MutablePropertySources目标,该目标持有一个PropertySource的调集,Springboot加载的每一种外部化装备都会终究被解析为一个PropertySource的完结类并存放在MutablePropertySourcesPropertySource调集中,PropertySource便是每一种外部化装备源在Springboot中的表现,其供给了对外部化装备的各种操作。依据上图为例,给出一部分外部化装备源与PropertySource的完结类的对应联系。

外部化装备 PropertySource
命令行参数 SimpleCommandLinePropertySource
JAVA体系特点 PropertiesPropertySource
操作体系环境变量 OriginAwareSystemEnvironmentPropertySource
装备数据文件 OriginTrackedMapPropertySource

发动程序时经过命令行指定的运用程序参数(args)会被先创立为DefaultApplicationArguments目标,然后再被解析为SimpleCommandLinePropertySource,例如经过IDEA进行如下装备。

详细分析Springboot启动流程

那么对应的SimpleCommandLinePropertySource如下所示。

详细分析Springboot启动流程

假如在resources目录创立一个application.yml文件,且内容如下。

server:
  port: 8080
  address: 127.0.0.1

那么对应的OriginTrackedMapPropertySource如下所示。

详细分析Springboot启动流程

下面将从SpringApplication#prepareEnvironment办法为进口,对Springboot发动流程中的外部化装备加载进行扼要剖析。SpringApplication#prepareEnvironment办法如下所示。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 创立ConfigurableEnvironment目标
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 将命令行参数解析为PropertySource并加载到Environment中
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 发布Environment准备好的作业
    // 进一步加载更多的外部化装备到Environment中
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    configureAdditionalProfiles(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

SpringApplication#prepareEnvironment办法中,首要会调用getOrCreateEnvironment() 办法创立ConfigurableEnvironment目标,创立出来的ConfigurableEnvironment的实践类型会依据SpringApplication初始化时揣度出来的WEB运用程序类型而定,假如WEB运用程序类型为SERVLET,则创立出来的ConfigurableEnvironment实践类型为StandardServletEnvironment,并且在初始化StandardServletEnvironment时还会一并将JAVA体系特点和操作体系环境变量这两个外部化装备加载到StandardServletEnvironment中。

在创立好StandardServletEnvironment后,会再将命令行参数解析为PropertySource并加载到StandardServletEnvironment中,随后就经过Springboot作业机制向ApplicationListener发布Environment准备好的作业,这儿会接纳该作业的ApplicationListenerEnvironmentPostProcessorApplicationListener2.4.0版别以前为ConfigFileApplicationListener,该监听器从2.4.0版别起被抛弃)。

接下来先剖析一下getOrCreateEnvironment() 办法的完结。

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    // 依据WEB运用程序类型创立不同的ConfigurableEnvironment
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}

StandardServletEnvironment的类图如下所示。

详细分析Springboot启动流程

StandardServletEnvironment在初始化时会先调用到其父类AbstractEnvironment的结构办法,如下所示。

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
}

实践会调用到StandardServletEnvironment完结的customizePropertySources() 办法,如下所示。

protected void customizePropertySources(MutablePropertySources propertySources) {
    // Servlet相关的外部化装备的加载
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    // 调用父类StandardEnvironment完结的customizePropertySources()办法
    super.customizePropertySources(propertySources);
}

持续看StandardEnvironment完结的customizePropertySources() 办法,如下所示。

protected void customizePropertySources(MutablePropertySources propertySources) {
    // 将JAVA体系特点解析为PropertiesPropertySource,并加载到PropertySource调集中
    propertySources.addLast(
            new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    // 将操作体系环境变量解析为SystemEnvironmentPropertySource,并加载到PropertySource调集中
    propertySources.addLast(
            new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

到这儿getOrCreateEnvironment() 办法做的作业剖析完毕。

下面再剖析一下EnvironmentPostProcessorApplicationListener接纳到Environment准备好的作业(ApplicationEnvironmentPreparedEvent)后的履行流程,EnvironmentPostProcessorApplicationListeneronApplicationEvent() 办法如下所示。

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        // 作业event的实践类型为ApplicationEnvironmentPreparedEvent
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }
    if (event instanceof ApplicationFailedEvent) {
        onApplicationFailedEvent((ApplicationFailedEvent) event);
    }
}

持续看onApplicationEnvironmentPreparedEvent() 办法。

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    SpringApplication application = event.getSpringApplication();
    // 遍历一切EnvironmentPostProcessor的完结类,每个EnvironmentPostProcessor的完结类都会对相应的外部化装备做后置处理
    // 处理装备数据文件的EnvironmentPostProcessor的实践类型为ConfigDataEnvironmentPostProcessor
    for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
        postProcessor.postProcessEnvironment(environment, application);
    }
}

EnvironmentPostProcessor的承继树如下所示。

详细分析Springboot启动流程

每一个EnvironmentPostProcessor的完结类都会对相应的外部化装备做后置处理,例如RandomValuePropertySourceEnvironmentPostProcessor会加载一个RandomValuePropertySourceEnvironment中,SystemEnvironmentPropertySourceEnvironmentPostProcessor会将Environment中的SystemEnvironmentPropertySource替换为SystemEnvironmentPropertySource的子类OriginAwareSystemEnvironmentPropertySource

EnvironmentPostProcessor的完结类中,有一个较为重要的完结类叫做ConfigDataEnvironmentPostProcessor,其能够将装备数据文件(application.yml等)加载为OriginTrackedMapPropertySource并设置到Environment中。

至此,Springboot发动流程中的外部化装备加载剖析完毕,下面是小结。

  1. 首要会依据初始化SpringApplication时揣度出来的WEB运用程序类型创立不同的Environment,例如WEB运用程序类型为SERVLET时,创立的Environment的实践类型为StandardServletEnvironment
  2. 在创立StandardServletEnvironment时,就会向StandardServletEnvironment中加载一部分外部化装备,在这个阶段加载的外部化装备首要是JAVA体系特点和操作体系环境变量;
  3. 在创立StandardServletEnvironment后,还会经过Springboot作业机制向EnvironmentPostProcessorApplicationListener发布ApplicationEnvironmentPreparedEvent作业,EnvironmentPostProcessorApplicationListener中收到ApplicationEnvironmentPreparedEvent作业后,会调用EnvironmentPostProcessor的完结类完结对Environment的后置处理,即会持续向Environment加载外部化装备,装备数据文件(application.yml等)的加载就在这个阶段完结;
  4. StandardServletEnvironment内部持有一个MutablePropertySources目标,该目标持有一个PropertySource的调集,Springboot加载的每一种外部化装备都会终究被解析为一个PropertySource的完结类并存放在MutablePropertySourcesPropertySource调集中,PropertySource便是每一种外部化装备源在Springboot中的表现,其供给了对外部化装备的各种操作。

总结

Springboot发动时,第一件重要作业便是初始化SpringApplication,并首要完结如下作业。

  1. 设置源。实践便是设置Spring容器发动时依靠的初始装备类,也便是Springboot中的发动类;
  2. 设置WEB运用程序类型。例如能够是SERVLETREACTIVE等;
  3. 加载并设置BootstrapperApplicationContextInitializerApplicationListener
  4. 设置运用程序主类的Class目标。

然后Springboot发动时还会敞开作业机制,首要便是经过运行时监听器EventPublishingRunListener创立作业并分发给对应的ApplicationListener

再然后会加载外部化装备,也便是得到很重要的Environment目标,经过Environment目标就能够拿到Springboot加载的一切外部化装备。

再然后会完结容器改写,也便是履行Spring中的各种扩展点,初始化各种bean,这部分逻辑属所以Spring的逻辑,故本文并未详细介绍。除此之外,在容器改写时,还会完结WEB容器的发动,例如发动Springboot内嵌的Tomcat,这部分内容比较多,会在后面独自进行剖析。

最终,Springboot在整个发动流程中,会凭借作业机制来发布各种作业,发布作业便是凭借于上述说到的EventPublishingRunListener,这是一个运行时监听器,是Springboot中供给的监听器,不要和Spring中的ApplicationListener混杂了。

假如觉得本篇文章对你有帮助,求求你点个赞,加个保藏最终再点个关注吧。创造不易,感谢支撑!


本文正在参加「金石方案」