Springboot3.0源码揭秘 --启动流程之Startup的创建及应用

324 阅读4分钟

在上一篇,我们已经通过demo程序了解了怎么去自定义BootstrapRegistryInitializer,且通过控制台的输出,也能大概了解到其执行过程。这一篇,我们正式开始深入剖析springboot的运行流程。

springboot项目的真正运行逻辑,是放在初始化SpringApplicationContext之后,调用了run方法,在简单了解springboot启动流程那一篇,我们也简单的看了run方法的内部实现,这里再次贴下代码:

	public ConfigurableApplicationContext run(String... args) {
		// 创建startUp,springboot3.2引入了Crac,可用于检查点恢复,默认情况下,只返回StandardStartup,用于记录时间
		Startup startup = Startup.create();
		// 属性赋值,为后面refreshContext添加jvm钩子做准备,默认情况下是启用的
		if (this.registerShutdownHook) {
			SpringApplication.shutdownHook.enableShutdownHookAddition();
		}
		// 创建上下文,里面会调用BootstrapRegistryInitializer
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		// 设置java.awt.headless,当 java.awt.headless 设置为 true 时,Java 程序将不能使用依赖于 AWT 或 Swing 的 GUI 组件
		// 一般的springboot项目均不需要gui,所以这里设置为true
		configureHeadlessProperty();
		// 拿到所有的监听器,并调用starting方法
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			// 处理启动参数,仅是简单将启动参数封装成ApplicationArguments
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			// 打印启动banner
			Banner printedBanner = printBanner(environment);
			// 创建applicantContext
			context = createApplicationContext();
			// 设置启动使用 steps 控制应用程序启动阶段。
			context.setApplicationStartup(this.applicationStartup);
			// 准备各种context
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			// 刷新上下文,添加jvm shutdown钩子
			refreshContext(context);
			// 上下文刷新之后的处理
			afterRefresh(context, applicationArguments);
			// 设置启动完成的时间
			startup.started();
			// 打印启动耗时
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
			}
			// 应用启动后,执行listeners
			listeners.started(context, startup.timeTakenToStarted());
			// 执行runner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			throw handleRunFailure(context, ex, listeners);
		}
		try {
			if (context.isRunning()) {
				listeners.ready(context, startup.ready());
			}
		}
		catch (Throwable ex) {
			throw handleRunFailure(context, ex, null);
		}
		return context;
	}

这一篇,我们主要着手于Startup对象的创建以及其创建之后用到的场景

1 、Startup对象的创建

run方法的第一行,就是StartUp对象的创建

Startup startup = Startup.create();

我们看看create方法做了啥:

static Startup create() {
	// 获取类加载器
	ClassLoader classLoader = Startup.class.getClassLoader();
	// 用类加载器判断是否支持CRaC
	return (ClassUtils.isPresent("jdk.crac.management.CRaCMXBean", classLoader)
			&& ClassUtils.isPresent("org.crac.management.CRaCMXBean", classLoader))
					? new CoordinatedRestoreAtCheckpointStartup() : new StandardStartup();
}

代码非常简单:先获取类加载器,然后尝试加载jdk.crac.management.CRaCMXBean、org.crac.management.CRaCMXBean看看是否支持crac,如果支持,创建的CoordinatedRestoreAtCheckpointStartup,否则创建StandardStartup。

我们来看看抽象类Startup,主要定义了些啥?

abstract static class Startup {

		private Duration timeTakenToStarted;

		/**
		 * 获取启动时间
		 * @return
		 */
		protected abstract long startTime();

		/**
		 * 获取jvm启动耗时
		 * @return
		 */
		protected abstract Long processUptime();

		/**
		 * 获取处理动作,这里是返回字符串,crac的方式返回的不同
		 * @return
		 */
		protected abstract String action();

		/**
		 * 标志程序已启动,记录启动花费的时间 - 创建StartUp创建的时间
		 * @return
		 */
		final Duration started() {
			long now = System.currentTimeMillis();
			this.timeTakenToStarted = Duration.ofMillis(now - startTime());
			return this.timeTakenToStarted;
		}

		/**
		 * 获取启动过程所花费的时间
		 * @return
		 */
		Duration timeTakenToStarted() {
			return this.timeTakenToStarted;
		}

		/**
		 * 记录springboot可以对外响应的时间
		 * @return
		 */
		private Duration ready() {
			long now = System.currentTimeMillis();
			return Duration.ofMillis(now - startTime());
		}

		static Startup create() {
			// 获取类加载器
			ClassLoader classLoader = Startup.class.getClassLoader();
			// 用类加载器判断是否支持CRaC
			return (ClassUtils.isPresent("jdk.crac.management.CRaCMXBean", classLoader)
					&& ClassUtils.isPresent("org.crac.management.CRaCMXBean", classLoader))
							? new CoordinatedRestoreAtCheckpointStartup() : new StandardStartup();
		}

	}

代码很简单,猜也能猜出来是用来记录springboot启动各个阶段的耗时。实现类也比较简单,这里不过多累述。CoordinatedRestoreAtCheckpointStartup跟StandardStartup的区别在于引入crac之后,有些时间的计算不用。CoordinatedRestoreAtCheckpointStartup中也使用StandardStartup做兜底。在创建Startup对象的时候,会记录启动时间

private final Long startTime = System.currentTimeMillis();

2、Startup对象的使用

我们知道,Startup对象主要是用来记录springboot启动各个阶段的耗时。关注Startup其实作用不大 。我们主要是想通过Startup的使用,来明确springboot启动过程的各个阶段是怎样的?比如:什么时候springboot项目才算启动完成?什么时候才算准备好。所以我们继续往后看看用到Startup的地方。

我们可以看到,run方法调用后,会执行很多操作,直到刷新上下文、以及刷新上下文后的后置处理完成之后,才会认为项目已经启动完成。之后,记录日志,再执行应用启动完的listener、再执行runner,等到都执行完成了,才会认为springboot进入ready状态。这几个时机,在我们日常开发的去自定义listener、runner非常有用。

总的来说,Startup主要用来记录时间,方便最终打印整个应用的启动耗时(或用在其他地方,反正就是记录时间用到)