SpringBoot源码剖析之SpringBoot执行流程

简介: this.webApplicationType = WebApplicationType.deduceFromClasspath()用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路 径下是否存在某个特征类

SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?

执行流程剖析

在这里插入图片描述

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);
}

1、SpringApplication实例化

源码可以看出,SpringApplication的初始化过程主要包括4部分:

(1)this.webApplicationType = WebApplicationType.deduceFromClasspath()
用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路 径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)

(2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器 SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中 获取所有可用的应用初始化器类ApplicationContextInitializer

(3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))
用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样, 也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的监听器类ApplicationListener。

(4)this.mainApplicationClass = this.deduceMainApplicationClass() 用于推断并设置项目main()方法启动的主程序启动类

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");

        //项目启动类 SpringbootDemoApplication.class设置为属性存储起来
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

        //设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
        this.webApplicationType = WebApplicationType.deduceFromClasspath();

        // 设置初始化器(Initializer),最后会调用这些初始化器
        //所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

        // 设置监听器(Listener)
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

        // 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

在这里插入图片描述

2、项目初始化启动

public ConfigurableApplicationContext run(String... args) {
        // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 初始化应用上下文和异常报告集合
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 配置 headless 属性
        configureHeadlessProperty();


        //   (1)获取并启动监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            // 创建  ApplicationArguments 对象 初始化默认应用参数类
            // args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            //(2)项目运行环境Environment的预配置
            // 创建并配置当前SpringBoot应用将要使用的Environment
            // 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

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

            // (3)创建Spring容器
            context = createApplicationContext();
            // 获得异常报告器 SpringBootExceptionReporter 数组
            //这一步的逻辑和实例化初始化器和监听器的一样,
            // 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);


            // (4)Spring容器前置处理
            //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);

            // (5):刷新容器
            refreshContext(context);

            // (6):Spring容器后置处理
            //扩展接口,设计模式中的模板方法,默认为空实现。
            // 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
            afterRefresh(context, applicationArguments);
            // 停止 StopWatch 统计时长
            stopWatch.stop();
            // 打印 Spring Boot 启动的时长日志。
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // (7)发出结束执行的事件通知
            listeners.started(context);

            // (8):执行Runners
            //用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
            //Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
            //Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        //   (9)发布应用上下文就绪事件
        //表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
        // 这样整个Spring Boot项目就正式启动完成了。
        try {
            listeners.running(context);
        } catch (Throwable ex) {
            // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
         //返回容器
        return context;
    }

项目初始化启动过程大致包括以下部分

1、获取启动监听器

this.getRunListeners(args)和listeners.starting()方法主要用于
获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。

2、配置上下文环境

/**
    *加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、
    *servletContextInitParams、systemProperties、sytemEnvironment、random、
    * application.yml(.yaml/.xml/.properties)等;初始化日志系统。
    */
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {

        //获取或创建环境(存在就直接返回,不存在创建一个再返回)
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //配置环境:配置PropertySources和active Profiles
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
        listeners.environmentPrepared(environment);
        //将环境绑定到SpringApplication
        bindToSpringApplication(environment);
        //如果非web环境,将环境转换成StandardEnvironment
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
        }
        // 配置PropertySources对它自己的递归依赖
        // 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中。
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

3、创建IOC容器(默认是servlet而不是webFlex 响应式)

protected ConfigurableApplicationContext createApplicationContext() {
        // 根据 webApplicationType 类型,获得 ApplicationContext 类型
        // 这里创建容器的类型 还是根据webApplicationType进行判断的,
        // 该类型为SERVLET类型,所以会通过反射装载对应的字节码,
        // 也就是AnnotationConfigServletWebServerApplicationContext

        // 先判断有没有指定的实现类
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {

                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            } catch (ClassNotFoundException ex) {
                throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex);
            }
        }
        // 创建 ApplicationContext 对象
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

4、Spring容器前置处理 将启动类注入容器,为后续开启自动化配置奠定基础

在这里插入图片描述

5、刷新容器 本质上就是走到了Spring refresh方法 解析配置文件,扫描组件,实例化Bean等等

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 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);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

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

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

在这里插入图片描述

6、Spring容器初始化后进行后置处理

扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。
比如打印一些启 动结束log,或者一些其它后置处理

在这里插入图片描述

7、发出Application已经执行完成事件

在这里插入图片描述

在这里插入图片描述

8、执行Runners

用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。

其中,Spring Boot提供的执行器接口有ApplicationRunner和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立即执行这些特定程序。

注意:自定义的Runner 需要注册到IOC容器中,因为从容器中拿的

在这里插入图片描述

总结

SpringBoot执行流程

在这里插入图片描述

详细点的

在这里插入图片描述

相关文章
|
21天前
|
Web App开发 编解码 Java
B/S基层卫生健康云HIS医院管理系统源码 SaaS模式 、Springboot框架
基层卫生健康云HIS系统采用云端SaaS服务的方式提供,使用用户通过浏览器即能访问,无需关注系统的部署、维护、升级等问题,系统充分考虑了模板化、配置化、智能化、扩展化等设计方法,覆盖了基层医疗机构的主要工作流程,能够与监管系统有序对接,并能满足未来系统扩展的需要。
46 4
|
19天前
|
运维 监控 安全
云HIS医疗管理系统源码——技术栈【SpringBoot+Angular+MySQL+MyBatis】
云HIS系统采用主流成熟技术,软件结构简洁、代码规范易阅读,SaaS应用,全浏览器访问前后端分离,多服务协同,服务可拆分,功能易扩展;支持多样化灵活配置,提取大量公共参数,无需修改代码即可满足不同客户需求;服务组织合理,功能高内聚,服务间通信简练。
32 4
|
1天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
8 2
|
3天前
|
Java Spring 容器
深入理解Spring Boot启动流程及其实战应用
【5月更文挑战第9天】本文详细解析了Spring Boot启动流程的概念和关键步骤,并结合实战示例,展示了如何在实际开发中运用这些知识。
13 2
|
3天前
|
前端开发 Java 关系型数据库
Java医院绩效考核系统源码B/S架构+springboot三级公立医院绩效考核系统源码 医院综合绩效核算系统源码
作为医院用综合绩效核算系统,系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。
22 2
|
3天前
|
运维 监控 Java
springboot基层区域HIS系统源码
医疗(医院)机构正式使用云HIS系统之前,要先进行院内基础数据的配置,主要在数据管理模块中进行,由系统管理员来操作。
10 0
|
5天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
47 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
13天前
|
缓存 Java 开发者
10个点介绍SpringBoot3工作流程与核心组件源码解析
Spring Boot 是Java开发中100%会使用到的框架,开发者不仅要熟练使用,对其中的核心源码也要了解,正所谓知其然知其所以然,V 哥建议小伙伴们在学习的过程中,一定要去研读一下源码,这有助于你在开发中游刃有余。欢迎一起交流学习心得,一起成长。
|
15天前
|
JavaScript Java 大数据
springboot高精度UWB定位系统源码
UWB (ULTRA WIDE BAND,) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。UWB定位系统依托在移动通信,雷达,微波电路,云计算与大数据处理等专业领域的多年积累,自主研发,开发并产业化的一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的优点。
19 0
|
16天前
|
Java
创建SpringBoot流程
创建SpringBoot流程
21 1