• SpringBoot启动流程大揭秘


    什么是SpringBoot

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

    SpringBoot启动流程

    SpringBoot启动流程涉及到的步骤相对来说容易理解,这里我先准备一个启动类
    在这里插入图片描述
    类需要标注@SpringBootApplication的注解,然后就可以直接以main函数的方式执行SpringApplication.run(DemoApplication.class, args);就可以启动项目,非常简单,下面我们再逐步分析每一步执行流程,main函数代码

    @SpringBootApplication
    public class DemoApplication
    {
        public static void main(String[] args)
        {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    SpringApplication.run

    首先我们跟进SpringApplication.run(DemoApplication.class, args);方法可以看到
    在这里插入图片描述
    run方法是一个static方法,继续点击run(new Class[] { primarySource }, args);方法可以看到调用了另一个run方法
    在这里插入图片描述

    SpringApplication初始化

    在第二个static静态run方法中可以看到new了一个SpringApplication对象,同时继续调用其的run方法来启动SpringBoot项目,下面我们先来看一下SpringApplication对象是如何构建的,进入new SpringApplication(primarySources)的构造方法可以看到
    在这里插入图片描述
    其中primarySources就是启动类的class对象,继续跟进可以看到SpringApplication构造类加载信息
    在这里插入图片描述

    WebApplicationType

    在SpringApplication的构造类中通过WebApplicationType.deduceFromClasspath();判断当前应用程序的容器,一共有三种容器,更进去可以看到WebApplicationType如图
    在这里插入图片描述
    可以看到包含三种容器REACTIVE、NONE、SERVLET,默认用的是WebApplicationType.SERVLET容器。

    加载spring.factories

    再回到SpringApplication对象继续往下看,可以看到this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();方法getBootstrapRegistryInitializersFromSpringFactories();从spring.factories中获取BootstrapRegistryInitializer,源码
    在这里插入图片描述
    加入源码解析

    @SuppressWarnings("deprecation")
    private List getBootstrapRegistryInitializersFromSpringFactories() {
        ArrayList 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    继续回到SpringApplication对象继续往下看会看到
    在这里插入图片描述
    其中getSpringFactoriesInstances(ApplicationContextInitializer.class)和getSpringFactoriesInstances(ApplicationListener.class)分别表示从spring.factories中获取容器上下文相关初始化ApplicationContextInitializer和容器监听器相关初始化ApplicationListener
    在这里插入图片描述
    获取完容器上下文初始化和监听器初始化器之后通过setInitializers((Collection)和setListeners((Collection)分别放入List initializers和List listeners,这里我们来启动一下程序看一下是否是这样
    在这里插入图片描述
    在这里插入图片描述
    可以看到已经将spring.factories中的配置加载进来了。

    deduceMainApplicationClass

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

    自定义spring.factories

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

    public class ApplicationInit implements ApplicationContextInitializer {
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            System.out.println("加载自定义ApplicationContextInitializer...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    同时增加项目spring.factories配置

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

    在这里插入图片描述
    启动应用程序可以看到
    在这里插入图片描述
    初始化完成SpringApplication之后就可以运行run方法了

    SpringBoot启动run

    初始化完成之后就可以正式进入run阶段了
    在这里插入图片描述
    结合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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

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

    createBootstrapContext

    初始化上下文对象,这里将初始化SpringApplication是从spring.factories中获取的bootstrapRegistryInitializers进行初始化
    在这里插入图片描述
    之后继续向下看到configureHeadlessProperty();该方法主要设置系统headless属性默认值是true,查看源码
    在这里插入图片描述

    getRunListeners

    从spring.factories中获取运行监听器EventPublishingRunListener
    在这里插入图片描述
    debug该方法可以看到从配置中加载的运行监听器方法
    在这里插入图片描述
    后续继续启动监听器listeners.starting(),调用starting()方法
    在这里插入图片描述

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这里我们来debug看一下执行过程中环境加载情况
    在这里插入图片描述
    加载完成之后执行configureIgnoreBeanInfo方法配置忽略的bean信息,继续往下看

    printBanner

    打印配置的banner文本信息
    在这里插入图片描述
    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 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    refreshContext

    配置完容器基本信息后,刷新容器上下文refreshContext方法,
    在这里插入图片描述
    继续跟进去可以看到
    在这里插入图片描述
    这里我们看web容器的类,
    在这里插入图片描述
    源码如下,注释比较容易理解,这里不再详细介绍里面的每一步加载了,先写主流程

    @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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    刷新容器上下文refreshContext方法之后看到afterRefresh是一个空方法,主要用于开发者拓展使用
    在这里插入图片描述

    listeners.started(context)

    容器配置都完成之后,这时监听应用上下文启动完成所有的运行监听器调用 started() 方法,发布监听应用的启动事件,如图
    在这里插入图片描述
    后续继续执行callRunners方法遍历所有runner,调用run方法
    在这里插入图片描述
    上述都完成之后到了最后一步,执行listener.running方法
    在这里插入图片描述
    运行所有运行监听器,该方法执行以后SpringApplication.run(DemoApplication.class, args)也就算执行完成了,那么SpringBoot的ApplicationContext也就启动完成了。

    总结

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

  • 相关阅读:
    速盾:dns和cdn区别?
    【阅读】One-bit Active Query with Contrastive Pairs
    JSON对象、字符串之间的相互转换
    内存泄露
    优维低代码实践:图片和搜索
    .NET ASPIRE 预览版 7 发布
    python经典百题之画圆形
    在对接自有账户体系时,FinClip 怎么做的?
    索引的基础使用
    基于libmpv内核设计开发的视频播放器-高级版(四)
  • 原文地址:https://blog.csdn.net/csdn565973850/article/details/126854262