开发者社区> 六月的雨在钉钉> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

SpringBoot启动流程大揭秘

简介: 通俗易懂读源码--SpringBoot启动流程详解
+关注继续查看

2000元阿里云代金券免费领取,2核4G云服务器仅664元/3年,新老用户都有优惠,立即抢购>>>

什么是SpringBoot

日常开发中采用的是开源的若依框架,也就是SpringBoot框架,那么什么是SpringBoot框架呢?

SpringBoot是一款开箱即用框架,提供各种默认配置来简化项目配置,让我们的Spring应用变的更轻量化、更快的入门,在主程序执行main函数就可以运行,也可以打包你的应用为jar并通过使用java -jar来运行你的Web应用。 使用SpringBoot只需很少的配置,大部分的时候直接使用默认的配置即可。同时后续也可以与Spring Cloud的微服务无缝结合。

SpringBoot启动流程

SpringBoot启动流程涉及到的步骤相对来说容易理解,这里我先准备一个启动类

image.png启动类需要标注@SpringBootApplication的注解,然后就可以直接以main函数的方式执行SpringApplication.run(DemoApplication.class, args);就可以启动项目,非常简单,下面我们再逐步分析每一步执行流程,main函数代码

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

SpringApplication.run

首先我们跟进SpringApplication.run(DemoApplication.class, args);方法可以看到

image.png

run方法是一个static方法,继续点击run(new Class[] { primarySource }, args);方法可以看到调用了另一个run方法

image.png

SpringApplication初始化

在第二个static静态run方法中可以看到new了一个SpringApplication对象,同时继续调用其的run方法来启动SpringBoot项目,下面我们先来看一下SpringApplication对象是如何构建的,进入new SpringApplication(primarySources)的构造方法可以看到

image.png

其中primarySources就是启动类的class对象,继续跟进可以看到SpringApplication构造类加载信息

image.png

WebApplicationType

在SpringApplication的构造类中通过WebApplicationType.deduceFromClasspath();判断当前应用程序的容器,一共有三种容器,更进去可以看到WebApplicationType如图

image.png

可以看到包含三种容器REACTIVE、NONE、SERVLET,默认用的是WebApplicationType.SERVLET容器。

加载spring.factories

再回到SpringApplication对象继续往下看,可以看到this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();方法getBootstrapRegistryInitializersFromSpringFactories();从spring.factories中获取BootstrapRegistryInitializer,源码

image.png

加入源码解析

@SuppressWarnings("deprecation")
private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
    ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
    //从spring.factories中获取Bootstrapper集合,遍历转化为BootstrapRegistryInitializer并存入initializers
    getSpringFactoriesInstances(Bootstrapper.class).stream()
            .map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
            .forEach(initializers::add);
    //从spring.factories中获取BootstrapRegistryInitializer集合并存入initializers,最后返回initializers集合
    initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    return initializers;
}

继续回到SpringApplication对象继续往下看会看到

image.png

其中getSpringFactoriesInstances(ApplicationContextInitializer.class)和getSpringFactoriesInstances(ApplicationListener.class)分别表示从spring.factories中获取容器上下文相关初始化ApplicationContextInitializer和容器监听器相关初始化ApplicationListener

image.png

获取完容器上下文初始化和监听器初始化器之后通过setInitializers((Collection)和setListeners((Collection)分别放入List initializers和List listeners,这里我们来启动一下程序看一下是否是这样

image.png

image.png

可以看到已经将spring.factories中的配置加载进来了。

deduceMainApplicationClass

后面继续跟进可以看到deduceMainApplicationClass()理解为推断推论主程序类,debug可以看到获取了main函数所在的主程序类

image.png

自定义spring.factories

增加一个类ApplicationInit实现上下文初始化接口ApplicationContextInitializer,重写initialize方法,

public class ApplicationInit implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("加载自定义ApplicationContextInitializer...");
    }
}

同时增加项目spring.factories配置

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
com.ruoyi.web.controller.common.ApplicationInit

image.png

启动应用程序可以看到

image.png

初始化完成SpringApplication之后就可以运行run方法了

SpringBoot启动run

初始化完成之后就可以正式进入run阶段了

image.png

结合run阶段的源码来看看启动流程

public ConfigurableApplicationContext run(String... args) {
    //实例化一个计时器,统计项目启动时间
    StopWatch stopWatch = new StopWatch();
    //启动计时器
    stopWatch.start();
    //初始化上下文对象
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    //定义可配置上下文
    ConfigurableApplicationContext context = null;
    //设置系统headless属性默认值是true
    configureHeadlessProperty();
    //从spring.factories中获取运行监听器 getRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //启动监听器
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        //在命令行下启动应用带的参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //准备环境 prepareEnvironment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        // 配置忽略的bean
        configureIgnoreBeanInfo(environment);
        //打印在src/main/resources下放入名字是banner的自定义文件
        Banner printedBanner = printBanner(environment);
        //根据webApplicationType调用工厂类创建应用程序上下文容器
        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() 方法
        listeners.started(context);
        //遍历所有的 runner,调用 run 方法
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        //异常处理,如果run过程发生异常
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        //所有的运行监听器调用running()方法,监听应用上下文
        listeners.running(context);
    }
    catch (Throwable ex) {
        //异常处理
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    //返回最终构建的容器对象ConfigurableApplicationContext
    return context;
}

run方法开始先初始化一个计时器,开启计时,之后会初始化一个上下文对象

createBootstrapContext

初始化上下文对象,这里将初始化SpringApplication是从spring.factories中获取的bootstrapRegistryInitializers进行初始化

image.png

之后继续向下看到configureHeadlessProperty();该方法主要设置系统headless属性默认值是true,查看源码

image.png

getRunListeners

从spring.factories中获取运行监听器EventPublishingRunListener

image.png

debug该方法可以看到从配置中加载的运行监听器方法

image.png

后续继续启动监听器listeners.starting(),调用starting()方法

image.png

prepareEnvironment

我们继续看prepareEnvironment方法,跟进去可以看到当前方法涉及getOrCreateEnvironment、configureEnvironment、ConfigurationPropertySources、DefaultPropertiesPropertySource、bindToSpringApplication、convertEnvironment,我们来看一下源码

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 创建和配置环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //配置 property sources 和 profiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    //将environment.getPropertySources()放在第一个位置
    ConfigurationPropertySources.attach(environment);
    //运行监听器通知所有监听器环境准备完成
    listeners.environmentPrepared(bootstrapContext, environment);
    //Move the 'defaultProperties' property source so that it's the last source
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
            "Environment prefix cannot be set via properties.");
    //Bind the environment to the {@link SpringApplication}
    bindToSpringApplication(environment);
    //环境转换成StandardEnvironment
    if (!this.isCustomEnvironment) {
        environment = convertEnvironment(environment);
    }
    ConfigurationPropertySources.attach(environment);
    //返回环境配置对象ConfigurableEnvironment
    return environment;
}

这里我们来debug看一下执行过程中环境加载情况

image.png

加载完成之后执行configureIgnoreBeanInfo方法配置忽略的bean信息,继续往下看

printBanner

打印配置的banner文本信息

image.png

banner文件路径在\src\main\resources\banner.txt,可以通过更该文件内容展示不同的启动成功信息。

继续向下看,看到createApplicationContext方法创建应用程序的上下文容器,创建完之后,继续看prepareContext,该方法主要配置容器的基本信息

prepareContext

prepareContext也是一个重要的方法,我们来看一下源码方法

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    //设置容器的环境变量
    context.setEnvironment(environment);
    //设置容器的ResourceLoader、ClassLoader、ConversionService
    postProcessApplicationContext(context);
    //获取所有初始化器调用initialize()初始化
    applyInitializers(context);
    //触发所有 SpringApplicationRunListener监听器的contextPrepared事件方法
    listeners.contextPrepared(context);
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
        //打印启动日志
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans 添加启动特定的单例bean
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources 加载所有的资源
    Set<Object> sources = getAllSources();
    //断言资源必须非空
    Assert.notEmpty(sources, "Sources must not be empty");
    //Load beans into the application context 加载启动类 the context to load beans into 将启动类注入容器
    load(context, sources.toArray(new Object[0]));
    //触发所有SpringApplicationRunListener监听器contextLoaded方法
    listeners.contextLoaded(context);
}

refreshContext

配置完容器基本信息后,刷新容器上下文refreshContext方法,

image.png

继续跟进去可以看到

image.png

这里我们看web容器的类,

image.png

源码如下,注释比较容易理解,这里不再详细介绍里面的每一步加载了,先写主流程

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishrefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

刷新容器上下文refreshContext方法之后看到afterRefresh是一个空方法,主要用于开发者拓展使用

image.png

listeners.started(context)

容器配置都完成之后,这时监听应用上下文启动完成所有的运行监听器调用 started() 方法,发布监听应用的启动事件,如图

image.png

后续继续执行callRunners方法遍历所有runner,调用run方法

image.png

上述都完成之后到了最后一步,执行listener.running方法

image.png

运行所有运行监听器,该方法执行以后SpringApplication.run(DemoApplication.class, args)也就算执行完成了,那么SpringBoot的ApplicationContext也就启动完成了。

总结

SpringBoot的执行流程整体上分为两个部分,也就是SpringApplication的初始化和SpringApplication.run方法,所有的启动加载过程都在这两个方法中,一篇文章写的太多不方便阅读,另外个人觉得太长的文章也没有人有耐心看完,所以中间一些细节没有细究,后面会继续补充里面细节的内容,感谢大家的阅读,欢迎有问题的评论区留言,共同学习共同成长。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
SpringBoot启动全流程源码解析
SpringBoot启动全流程源码解析
41 0
SpringBoot的启动流程是怎样的?SpringBoot源码(七)
SpringBoot的启动流程是怎样的?SpringBoot源码(七)
909 0
SpringBoot启动流程简析(四)
在我们之前的web开发中,通常都是将应用打成war包或者将编译之后的应用放到tomcat的webapps目录下(其他的web服务器放到相应的目录下),但是我们在用SpringBoot进行web...
1174 0
SpringBoot启动流程简析(三)
我们在上一节中说了SpringBoot的应用上下文的对象是AnnotationConfigEmbeddedWebApplicationContext,通过名字直译就是注解配置的可嵌入的web应用上下文。
1116 0
SpringBoot启动流程简析(二)
在这篇文章中,我们接着上一篇的内容接着分析。 public ConfigurableApplicationContext run(String.
1170 0
SpringBoot启动流程简析(一)
我想很多人已经在项目中使用SpringBoot做项目开发的工作了,创建SpringBoot和启动SpringBoot应用都会较简单一点,下面我以SpringBoot官网上的Demo来简单的分析...
1151 0
Spring Boot的快速启动和部署
1.Create stand-alone Spring applications 创建独立的Spring应用 2.Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)内嵌了Tomcat,Jetty或者Undertow,并且不需要部署 3.
4973 0
u-boot-2011.06启动流程分析
?u-boot支持许多CPU,以及一些常见的开发板。本文以u-boot-2011.06这个最新版本为例,简要介绍一下u-boot在smdk2410上的启动流程。 首先系统是从arch/arm/cpu/arm920t目录下的start.
758 0
u-boot-2011.06启动流程分析
?u-boot支持许多CPU,以及一些常见的开发板。本文以u-boot-2011.06这个最新版本为例,简要介绍一下u-boot在smdk2410上的启动流程。 首先系统是从arch/arm/cpu/arm920t目录下的start.
568 0
+关注
六月的雨在钉钉
从事java行业8年之久,热爱技术,热爱以博文记录日常工作,csdn博主,工作座右铭是:让技术不再枯燥,让每一位技术人爱上技术
227
文章
121
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载
http://www.vxiaotou.com