接着上篇继续分析 SpringBoot 的启动过程。

    SpringBoot的版本为:2.1.0 release,最新版本。



一.时序图

    一样的,我们先把时序图贴上来,方便理解:

springboot 如何源码调试 springboot源码构建_python

 



二.源码分析

    回顾一下,前面我们分析到了下面这步:

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
        // 前面分析了前一部分,SpringApplication的构造方法,本文分析后一部分的run()方法
		return new SpringApplication(primarySources).run(args);
	}

    SpringApplication#run方法的内容较多,准备刷屏了:

public ConfigurableApplicationContext run(String... args) {
        // StopWatch是Spring中一个任务执行时间控制的类,记录了任务的执行时间
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();// 开始时间
		ConfigurableApplicationContext context = null;
        // 创建一个SpringBootExceptionReporter的List,记录启动过程中的异常信息
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 设置系统属性 "java.awt.headless" 为 true
		configureHeadlessProperty();
        // 获取所有启动监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
        // 调用所有监听器的starting()
		listeners.starting();
		try {
            // 根据入参创建 ApplicationArguments 对象
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
            // 准备应用上下文 environment 对象
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            // 设置系统属性 "spring.beaninfo.ignore",是否忽略bean信息
			configureIgnoreBeanInfo(environment);
            // 获取打印 Banner 对象,也就是应用启动时候看到控制台输出的那个很大的 "SpringBoot"
			Banner printedBanner = printBanner(environment);
            // 创建IOC容器
			context = createApplicationContext();
            // 获取异常报告,在异常时报告异常信息
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            // 准备上下文
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
            // 刷新上下文
			refreshContext(context);
            // 刷新完后
			afterRefresh(context, applicationArguments);
			stopWatch.stop();// 启动完成,记录启动结束时间
			if (this.logStartupInfo) {// 打印详细启动时间信息
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
            // 调用所有监听器的started(ctx)
			listeners.started(context);
            // 调用实现了 ApplicationRunner、CommandLineRunner 的对象的 run 方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);// 调用所有监听器的running()
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;// 返回上下文
	}

    上面从大体上介绍了SpringApplication在run()中做的事情,下面详细分析每步具体的操作。

    SpringApplication#getRunListeners,获取所有启动监听器,一样的套路,通过SPI机制,通过工厂创建SpringApplicationRunListener的实例:

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        // 注意这个地方,我们第二次看到了getSpringFactoriesInstances这个方法,作用就是从类路径下 META-INF/spring.factories 下加载 SpringFactory 实例,最后传入 SpringApplicationRunListener.class,创建实例
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}

    从 spring.factories 里面找到如下内容:也就是最后拿到SpringApplicationRunListener的实例是EventPublishingRunListener的对象。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

    SpringApplication#configureHeadlessProperty,设置系统属性 "java.awt.headless" 。headless是系统的一种配置模式,在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式,也就是告诉服务器,没有这些硬件设施,当需要这是设备信息的时候,别慌,我可以使用awt组件通过计算模拟出这些外设的特性。

private void configureHeadlessProperty() {
        // this.headless在初始化的时候值为 "true"
		System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
				SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
	}

    SpringApplication#prepareEnvironment,准备应用上下文 environment 对象,像设置当前使用的配置文件profile,就在该方法内完成:

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 添加 ConversionService 转换器,保存启动参数
		configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 调用所有监听器的environmentPrepared(env)
		listeners.environmentPrepared(environment);
        // 将 environment 绑定到当前Spring上下文
		bindToSpringApplication(environment);
        // 判断是否用户自定义Environment
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

    SpringApplication#configureEnvironment,添加 ConversionService 转换器,保存启动参数,即最外面main的入参args数组:

protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService
					.getSharedInstance();
			environment.setConversionService(
					(ConfigurableConversionService) conversionService);
		}
        // 合并命令行启动参数到当前 enviroment,这个args就是最外层App启动传入的main方法的参数
		configurePropertySources(environment, args);
        // 设置当前运行环境的配置文件(dev/uat/prod)
		configureProfiles(environment, args);
	}

    SpringApplication#configureProfiles ,设置当前运行环境的配置文件,会去查看"spring.profiles.active"中指定的是什么:

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        // 去找当前激活的Profile是哪个 "spring.profiles.active"
		environment.getActiveProfiles(); // ensure they are initialized
		// But these ones should go first (last wins in a property key clash)
		Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
		profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
		environment.setActiveProfiles(StringUtils.toStringArray(profiles));
	}

    不小心有陷入进去了,再次回到run()。

    接着看后面是获取Banner。根据环境找到Banner,默认找classpath下面的 banner.txt,Gitee上面很多管理系统打印出各种自定义名称。实现很简单,只要你提前制作好banner.txt,放到resources下面,启动的时候,就会加载你的,覆盖原始的Banner信息。这部分代码比较简单,只要跟进去看下就能看明白,考虑篇幅问题,省略过了。分享一个在线设计Banner.txt的网站。

    如果需要关闭Banner输出,在App里面调用:

SpringApplication.setBannerMode(Banner.Mode.OFF);// 关掉Banner

    接下来重点看一下 SpringApplication#prepareContext,分析SpringBoot如何准备上下文的:

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
        // 给当前 context 设置 environment
		context.setEnvironment(environment);
        // 设置 beanNameGenerator、resourceLoader、ConversionService
		postProcessApplicationContext(context);
        // 获取所有 ApplicationContextInitializer,调用其 initialize(ctx) 方法
		applyInitializers(context);
        // 调用所有监听器的contextPrepared(ctx)
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
            // 打印启动进程信息
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);// 打印 profile 信息
		}
		// Add boot specific singleton beans 注册 SpringBoot 特有的单实例
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
            // 设置是否允许重写 BeanDefinition
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
        // 创建 BeanDefinitionLoader,用于加载 BeanDefinition,此处加载应用启动类有@SpringBootApplication 注解的主类
		load(context, sources.toArray(new Object[0]));
        // 调用所有监听器的contextLoaded(ctx)
		listeners.contextLoaded(context);
	}

    稳住,快启动完了。接着看SpringApplication#refreshContext,完成刷新上下文的操作:

private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);// 刷新上下文,最后使用了AbstractApplicationContext#refresh方法,进入了Spring的启动流程
		if (this.registerShutdownHook) {
			try {
                // 注册停止钩子,为了在应用程序shutdown的时候销毁所有 bean 实例
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

    然后是到了SpringApplication#afterRefresh,这是一个模板方法,父类不提供实现,留给子类发挥想象实现,在context刷新好之后需要做的事情可以在此方法实现中完成。

    最后就是 stopWatch.stop() 停止计时,打印启动耗时信息,回调监听器的started(ctx)方法,返回上下文context。

 

    至此,SpringBoot启动过程分析完成。