• Spring Boot 2.x源码系列【2】启动流程深入解析之SpringApplication类


    有道无术,术尚可求,有术无道,止于术。

    本系列Spring Boot版本2.7.0

    前言

    在使用Spring Boot开发应用时,我们只需要一个简单启动类,就能完成整个应用的加载启动,这个启动流程实际是很复杂,接下来我们就分几个篇章深入了解一下。

    首先本篇会着重介绍SpringApplication类。

    SpringApplication

    SpringApplication翻译过来就是Spring 应用程序的意思,它的主要作用就是在JAVA main 方法中引导和启动Spring应用程序。

    在源码中属于spring-boot 模块:
    在这里插入图片描述

    属性

    SpringApplication的成员属性描述如下:

    	// banner图的默认位置 banner.txt
    	public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;
    	// 配置banner 位置的属性名称: spring.banner.location
    	public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;
    	// 设置java.awt.headless模式 的属性名
    	private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
    	// 日志记录对象
    	private static final Log logger = LogFactory.getLog(SpringApplication.class);
    	//  关闭Spring Boot 应用程序钩子
    	static final SpringApplicationShutdownHook shutdownHook = new SpringApplicationShutdownHook();
    	// 主类
    	private Set<Class<?>> primarySources;
    	// 创建 ApplicationContext 的其他资源,可以是:类名、包名或 XML 资源位置
    	private Set<String> sources = new LinkedHashSet<>();
        // 主类的Class 类型
    	private Class<?> mainApplicationClass;
    	// Banner 图的模式,可以关闭开启
    	private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
    	// 是否 打印 Spring Boot 启动的时长日志
    	private boolean logStartupInfo = true;
    	// 是否通过命令行参数向application.properties中添加属性配置,比如 将java -jar 中设置的参数传递到 application.properties重
    	private boolean addCommandLineProperties = true;
    	// 是否添加类型转换服务
    	private boolean addConversionService = true;
    	// Banner 对象
    	private Banner banner;
    	// 资源加载器
    	private ResourceLoader resourceLoader;
    	// Bean名称生成器
    	private BeanNameGenerator beanNameGenerator;
    	// 配置属性环境
    	private ConfigurableEnvironment environment;
    	// Web 程序的类型,SERVLET、REACTIVE
    	private WebApplicationType webApplicationType;
    	// 设置headless 模式(告诉系统该程序没有显示器、鼠标能外设)
        this.headless = true;
        // 是否注册关闭钩子
    	private boolean registerShutdownHook = true;
    	// 设置ApplicationContextInitializer(可用在初始化的时候处理一些东西)
    	private List<ApplicationContextInitializer<?>> initializers;
    	// 监听器
    	private List<ApplicationListener<?>> listeners;
    	// 默认属性
    	private Map<String, Object> defaultProperties;
    	// 使用 {@link BootstrapRegistry} 之前对其进行初始化的回调接口
    	private List<BootstrapRegistryInitializer> bootstrapRegistryInitializers;
    	// 附加配置文件
    	private Set<String> additionalProfiles = Collections.emptySet();
    	// 允许 Bean 覆盖
    	private boolean allowBeanDefinitionOverriding;
    	// 允许循环引用
    	private boolean allowCircularReferences;
    	// 是否自定义环境
    	private boolean isCustomEnvironment = false;
    	// 是否延迟初始化
    	private boolean lazyInitialization = false;
    	// 环境前缀
    	private String environmentPrefix;
    	// 应用上下文工厂,默认ConfigurableApplicationContext
    	private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
    	// 程序启动器
    	private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
    
    • 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

    在这些属性中,可以看到很多和环境、初始化、IOC容器、Banner、日志、钩子有关的配置。

    构造函数

    SpringApplication的构造函数,会初始化很多属性,我们在启动时,也可以自己创建对象并设置这些属性,实现自定义启动。

    	// 参数
        public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {		
        	// 资源集合
            this.sources = new LinkedHashSet();
            // banner 图模式,可以设置在控制台、关闭、日志文件中显示
            this.bannerMode = Mode.CONSOLE;
            // 打印 Spring Boot 启动的时长日志
            this.logStartupInfo = true;
            // 外部命令
            this.addCommandLineProperties = true;
            // 对象间的转换的转换器
            this.addConversionService = true;
            // 设置headless 模式(告诉系统该程序没有显示器、鼠标能外设)
            this.headless = true;
            // 是否注册关闭钩子
            this.registerShutdownHook = true;
            //  配置额外的配置文件
            this.additionalProfiles = Collections.emptySet();
            // 是否自定义环境
            this.isCustomEnvironment = false;
            // 是否延迟初始化
            this.lazyInitialization = false;
            // 使用默认的IOC容器
            this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
            // 检测应用程序启动阶段,监控启动时的各种性能指标
            this.applicationStartup = ApplicationStartup.DEFAULT;
            // 资源加载时,这里为null 
            this.resourceLoader = resourceLoader;
            Assert.notNull(primarySources, "PrimarySources must not be null");
            // 启动类,也就是主方法传去的参数
            this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
            // Web程序类型,    NONE,SERVLET(默认), REACTIVE;
            this.webApplicationType = WebApplicationType.deduceFromClasspath();
            // 初始化bootstrapRegistryInitializers引导程序注册的初始化器
            // 将spring.factories中key为BootstrapRegistryInitializer对应的类型实例化后放到成员变量initializers里(
            this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
            //  将spring.factories中key为ApplicationContextInitializer对应的类型实例化后放到成员变量initializers里(调用默认构造函数,生成一个实例)
            this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
            // 将spring.factories中key为ApplicationListener对应的类型实例化后放到成员变量Listeners里(调用默认构造函数,生成一个实例)
            this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
            // 推断出当前启动的main函数所在的类。
            this.mainApplicationClass = this.deduceMainApplicationClass();
        }
    
    • 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

    可以在Debug 中,看到改对象创建后加载的一系列属性,创建完成后就开始调用run 方法启动程序了。
    在这里插入图片描述

    run 方法

    在大多数情况下,直接在main 方法中调用run 方法启动应用程序,run 方法会运行 Spring 应用程序,创建并刷新一个新的IOC 容器。

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

    也可以先创建SpringApplication 实例,然后设置属性,再调用run 方法启动,以实现更高级的配置。

    @SpringBootApplication
    public class App1 {
        public static void main(String[] args) {
               SpringApplication application = new SpringApplication(MyApplication.class);
       		   // ... customize application settings here
       		   application.run(args)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    程序启动都是通过SpringApplication.run方法作为入口的,run方法会new一个SpringApplication,然后再调用对象的run方法。

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

    接着调用到run(String... args)方法,Spring Boot在这里完成整个应用的加载、创建、运行,最后会返回一个IOC容器,具体的每一步流程后面介绍。

        public ConfigurableApplicationContext run(String... args) {
        	// 开始时间
            long startTime = System.nanoTime();
            // 1. 创建Bootstrap上下文
            DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
            ConfigurableApplicationContext context = null;
            // 2. 设置系统参数 java.awt.headless
            this.configureHeadlessProperty();
            // 3. 获取监听器,每个监听器都开启一个独立的线程去执行监听
            SpringApplicationRunListeners listeners = this.getRunListeners(args);
            listeners.starting(bootstrapContext, this.mainApplicationClass);
            try {
            	// 4. 封装命令行参数,并准备环境
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                // 5. 查找并设置配置文件信息
                this.configureIgnoreBeanInfo(environment);
                // 6. 打印 Banner
                Banner printedBanner = this.printBanner(environment);
                // 7. 创建应用上下文,并添加一个应用程序监视器
                context = this.createApplicationContext();
                context.setApplicationStartup(this.applicationStartup);
                // 8. 准备应用上下文
                this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
                // 9. 刷新应用上下文
                this.refreshContext(context);
                // 10.刷新完成后调
                this.afterRefresh(context, applicationArguments);
                // 11. 计算启动时间,JDK Duration类表示秒或纳秒时间间隔,
                Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
                // 12. 打印启动日志
                if (this.logStartupInfo) {
                    (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
                }
    			// 13. 开始执行监听器
                listeners.started(context, timeTakenToStartup);
                // 14. 从应用上下文中获取ApplicationRunner和CommandLinerRunner类型的Bean并调用他们的run方法
                this.callRunners(context, applicationArguments);
            } catch (Throwable var12) {
            	// 处理异常
                this.handleRunFailure(context, var12, listeners);
                throw new IllegalStateException(var12);
            }
    
            try {
                Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
                // 15. 准备监听器
                listeners.ready(context, timeTakenToReady);
                // 返回上下文
                return context;
            } catch (Throwable var11) {
                this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
                throw new IllegalStateException(var11);
            }
        }
    
    • 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

    其他方法

    SpringApplication 中,有1000多行代码,提供了很多私有方法,具体怎么执行的,后面一步步分析。
    在这里插入图片描述

  • 相关阅读:
    GlusterFS基本概念
    面试被问答3-5年职业规划,该怎么回答
    【React】React 基础
    [附源码]java毕业设计逸尘房屋销售管理系统
    树与堆(详解)
    mac 备忘录
    什么是博弈论?
    基于JSP和MySQL实现的易买网电商网站设计
    基于JAVA手办周边商城计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    性价比高的照明品牌,五款经济实惠的照明品牌推荐
  • 原文地址:https://blog.csdn.net/qq_43437874/article/details/125306209