Spring Boot作为目前最流行的Java开发框架,秉承“约定优于配置”原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目。

本文基于Spring Boot 2.1.0.RELEASE版本了解Spring Boot如何启动

首先让我们看一下最简单的Spring Boot启动代码

@SpringBootApplication
public class DemoApplication {

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

每一个使用过Spring Boot的同学对于上面的代码应该都非常熟悉了,通过这段代码即可启动Spring Boot应用。那么SpringApplication.run(DemoApplication.class, args)内部到底做了什么事情呢?

在查看具体代码之前,我们先了解一下SpringApplication内部大概的执行流程,如下图

从上图中可以看出run()是整个应用的入口,接着初始化SpringApplicationRunListenerEnvironment等实例,然后创建应用上下文对象,“准备”并“刷新”上下文,到这里Spring容器已基本启动完成,最后发送事件通知各个组件作出相应动作。

源码分析

在了解完大概的流程之后,下面开始深入源码分析Spring Boot具体的启动过程,首先进入入口方法run

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();
    // ...

StopWatch主要是用来统计每项任务执行时长,例如Spring Boot启动占用总时长。

Started DemoApplication in 4.241 seconds (JVM running for 5.987)

getRunListeners()完成了SpringApplicationRunListener实例化工作,如何完成的呢?进入方法内部查看

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

SpringApplicationRunListenersSpringApplicationRunListener不是同一个类,它们名称非常相似

查看SpringApplicationRunListeners源码

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

public void starting() {
    for (SpringApplicationRunListener listener : this.listeners  {
        listener.starting();
    }
}
public void environmentPrepared() {
    // ....
}
public void contextPrepared() {
    // ....
}
public void contextLoaded() {
    // ....
}
public void started() {
    // ....
}
public void running() {
    // ....
}

它是SpringApplicationRunListener的一个集合

观察SpringApplicationRunListeners所有方法,可以看出,它实际是一个用来发送SpringApplicationRunListener相关事件的工具类

接着继续观察getSpringFactoriesInstances源码,看它是如何实例化对象的(此方法后续多处使用)

private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 加载对象名称
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type,classLoader));
    List<T> instances = createSpringFactoriesInstances(type parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

这里通过SpringFactoriesLoader.loadFactoryNames获取type对应的FactoryNames,不明白有什么用处?进入方法内部查看

public static List<String> loadFactoryNames(Class<?>factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefaul(factoryClassName, Collections.emptyList());
}

继续进入loadSpringFactories方法内部

public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactorie(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.ge(classLoader);
    if (result != null) {
        return result;
    }
    try {
        // 获取 META-INF/spring.factories 对应的资源
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResource(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResource(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 读取文件内容
            Properties properties =PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySe()) {
                String factoryClassName = ((String)entry.getKey()).trim();
                for (String factoryName :StringUtils.commaDelimitedListToStringArray(String) entry.getValue())) {
                    // 获取 factoryClassName 对应的多个valu(多个value用逗号分隔)
                    result.add(factoryClassName,factoryName.trim());
                }
            }
        }
        // 缓存已经读取到的内容
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to loadfactories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

看到这里可能会疑惑META-INF/spring.factories文件在哪里?文件里面有什么内容?

其实这个文件存放在Spring BootSpring Boot autoconfigure的jar包内部(有兴趣的同学可以自行下载jar包并解压查看),Spring Boot中的文件内容如下:

# 完整内容请查看原文件

# 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

可以看到SpringApplicationRunListener对应的值是EventPublishingRunListener

回到SpringFactoriesLoader.loadFactoryNames方法内部,可以发现方法获取的值实际上是factoryClassMETA-INF/spring.factories中对应的实现类的集合

明白这个方法之后,再回到getSpringFactoriesInstances方法

private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 获取 SpringApplicationRunListener 对应的实现类的名称集合
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type,classLoader));
    // 通过反射实例化对象
    List<T> instances = createSpringFactoriesInstances(type parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

到此为止getRunListeners完成了SpringApplicationRunListener对应实现类的实例化,并回调其starting方法

SpringApplicationRunListeners listeners getRunListeners(args);
listeners.starting();

从上面分析得知,实际上调用的是EventPublishingRunListenerstarting方法,那么方法内部做了什么呢?

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

发送了一个ApplicationStartingEvent事件

继续查找ApplicationStartingEvent事件的消费者,从spring.factories中可以找到所有预定义的事件消费者

# 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
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

接下来要做的就是从这些消费者中找出ApplicationStartingEvent事件的消费者(查找过程省略),找到以下两个消费者

  • LoggingApplicationListener
    初始化日志系统

  • LiquibaseServiceLocatorApplicationListener
    (参数liquibase.servicelocator.ServiceLocator)如果存在,则使用springboot相关的版本进行替代

了解完ApplicationStartingEvent事件之后,回到run方法继续往下探究prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 创建Environment对象
    ConfigurableEnvironment environment =getOrCreateEnvironment();
    configureEnvironment(environment,applicationArguments.getSourceArgs());
    // 发布ApplicationEnvironmentPreparedEvent事件
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverte(getClassLoader())
                .convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

这里又发布了一个ApplicationEnvironmentPreparedEvent事件,继续查找事件监听对象

  • FileEncodingApplicationListener
    检查系统文件编码格式是否符合环境变量中配置的文件编码格式(如果存在相关设置 - spring.mandatory-file-encoding),如果编码不符合,则抛出异常阻止Spring启动
  • AnsiOutputApplicationListener
    是否开启AnsiOutput
  • DelegatingApplicationListener
    代理context.listener.classes配置的监听者
  • ClasspathLoggingApplicationListener
    日志输出classpath
  • LoggingApplicationListener
    配置日志系统,logging.config, logging.level...等
  • ConfigFileApplicationListener
    这是一个比较重要的监听对象,具体的方法实现如下
private void onApplicationEnvironmentPreparedEvent(
        ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors =loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor :postProcessors) {
        postProcessor.postProcessEnvironmen(event.getEnvironment(),
                event.getSpringApplication());
    }
}

List<EnvironmentPostProcessor> loadPostProcessors() {
    return SpringFactoriesLoader.loadFactorie(EnvironmentPostProcessor.class,
            getClass().getClassLoader());
}

通过spring.factories,可以看到这里加载以下EnvironmentPostProcessor对象

  • CloudFoundryVcapEnvironmentPostProcessor
  • SpringApplicationJsonEnvironmentPostProcessor
  • SystemEnvironmentPropertySourceEnvironmentPostProcessor
  • ConfigFileApplicationListener

很多同学可能会疑问ConfigFileApplicationListener并不存在spring.factories文件中,这里为什么会有它呢?

实际上ConfigFileApplicationListeneronApplicationEnvironmentPreparedEvent方法中,将自身添加到EnvironmentPostProcessor对象列表中。

我们主要关注ConfigFileApplicationListenerpostProcessEnvironment方法

public void postProcessEnvironment(ConfigurableEnvironmentenvironment,
        SpringApplication application) {
    addPropertySources(environment,application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironmentenvironment,
        ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    // 读取applicaiton.yml, application.properties等配置文件
    new Loader(environment, resourceLoader).load();
}

ConfigFileApplicationListener监听到ApplicationEnvironmentPreparedEvent事件之后开始读取本地配置文件

关于Spring如何读取本地配置文件,请前往Spring Boot源码分析-配置文件加载原理

创建ApplicationContext对象

protected ConfigurableApplicationContextcreateApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            // 根据webApplicationType创建对应上下文对象
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forNam(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forNam(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forNam(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a defaultApplicationContext, "
                            + "please specify anApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

这里是根据webApplicationType决定创建什么类型的ApplicationContext对象,那么webApplicationType是何时赋值的呢?

public SpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must notbe null");
    this.primarySources = new LinkedHashSet<>(Arrays.asLis(primarySources));
    // 初始化webApplicationType
    this.webApplicationType =WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstance(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstance(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass(;
}

从上面可以看出是通过WebApplicationType.deduceFromClasspath方法初始化的webApplicationType,继续跟踪代码

private static final String WEBFLUX_INDICATOR_CLASS = "org."
            + "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
            + "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

从上面代码中可以看出Spring是通过当前classpath下是否存在相应的类,从而决定webApplicationType类型

初始化ApplicationContext对象

private void prepareContext(ConfigurableApplicationContextcontext,
        ConfigurableEnvironment environment,SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, BannerprintedBanner) {
    // 初始化context
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    // 发送ApplicationContextInitializedEvent消息
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory =context.getBeanFactory();
    beanFactory.registerSingleto("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner",printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverridin(this.allowBeanDefinitionOverriding);
    }
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 注册DemoApplication
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

这里注册了DemoApplicationSpring容器中,为后续bean扫描做准备

接下来继续深入refreshContext方法,可以发现实际上是执行了AbstractApplicationContext.refresh方法

public void refresh() throws BeansException,IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try {
            postProcessBeanFactory(beanFactory);
            // 完成bean的加载
            invokeBeanFactoryPostProcessors(beanFactory);
            registerBeanPostProcessors(beanFactory);
            initMessageSource();
            initApplicationEventMulticaster();
            onRefresh();
            registerListeners();
            finishBeanFactoryInitialization(beanFactory);
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered duringcontext initialization - " +
                        "cancelling refresh attempt: " + ex;
            }
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
        finally {
            resetCommonCaches();
        }
    }
}

refresh方法内部做了很多事情。比如:完成BeanFactory设置,BeanFactoryPostProcessorBeanPostProcessor接口回调,Bean加载,国际化配置等。

到此为止Spring基本完成了容器的初始化工作,最后在调用callRunners方法,执行ApplicationRunnerCommandLineRunner接口。

private void callRunners(ApplicationContext context,ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfTyp(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfTyp(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

整个启动过程的核心方法是refresh,此方法内部承载大部分容器启动所需的工作。由于篇幅原因,后续再进行refresh内部源码分析,了解Spring Boot加载Bean的整个过程。

本文由博客一文多发平台 OpenWrite 发布!

Spring Boot源码分析-启动过程的更多相关文章

  1. Spring Boot源码分析-配置文件加载原理

    在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...

  2. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  3. 精尽Spring Boot源码分析 - Jar 包的启动实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  4. 精尽Spring Boot源码分析 - 序言

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. 精尽Spring Boot源码分析 - 文章导读

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  8. 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  9. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

随机推荐

  1. CodeForces Good Bye 2016

    A题,水题略过. B题,也水,但是想复杂了.只要运动超出[0,20000]的范围就算不可能了. C题,我自己的方法是解不等式,然后取最大的答案即可.代码如下: #include <stdio.h ...

  2. 云闪付个人免签支付用xposed解决强制升级

    云闪付的xposed程序之前用的是6.18的版本,前段时间突然不能用了,提示要升级到最新的7.0版本.之前这个云闪付的个人免签支付程序一直跑的挺好,云闪付也是所有免签支付里面最能跑量的,不甘就这么放弃 ...

  3. vuecli3.0 webpack4 配置vuex

    废话不说,直接写步骤 1. npm install vux --save 2. npm install less less-loader --save-dev 3. npm install @vux/ ...

  4. cookies ,session,localstorage讲解

    一 .cookie (1)简介 因为HTTP协议是无状态的,服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现.在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两饮料.最 ...

  5. koa 项目实战(十)使用 validator 验证表单

    1.安装模块 npm install validator -D 2.验证注册参数 根目录/validation/register.js const Validator = require('valid ...

  6. PyTorch中使用深度学习(CNN和LSTM)的自动图像标题

    介绍 深度学习现在是一个非常猖獗的领域 - 有如此多的应用程序日复一日地出现.深入了解深度学习的最佳方法是亲自动手.尽可能多地参与项目,并尝试自己完成.这将帮助您更深入地掌握主题,并帮助您成为更好的深 ...

  7. DNA Sorting

    DNA Sorting Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 105159   Accepted: 42124 De ...

  8. 从数组中找出所有组合为s的数

    java版本 package numCombine; /** * 从数组中找出所有组合为s的数 * @author root * */ public class NumComberAll { publ ...

  9. 连接局域网mysql数据库

    1.先确认mysql服务端端口在防火墙是否开放外网连接(添加): windows防火墙 >高级设置 >入站规则 >新建规则 >选择端口 >添加端口 >后面直接下一步 ...

  10. 前端单元测试,以及给现有的vue项目添加jest + Vue Test Utils的配置

    文章原址:https://www.cnblogs.com/yalong/p/11714393.html 背景介绍: 以前写的公共组件,后来需要添加一些功能,添加了好几次,每次修改我都要测试好几遍保证以 ...