springboot源码分析:启动流程

环境

window 10
Intellij IDEA:2021.1
springboot:2.4.3

流程图

在这里插入图片描述

启动步骤

根据上面的流程图,启动步骤我们可以简化为:

  1. jar包meta-inf/spring.factores获取键值对属性,并创建初始化器和监听器。
  2. 获取监听器并广播启动-事件。
  3. 准备容器环境,并广播环境准备完成-事件。
  4. 根据环境类型创建应用上下文(spring容器)。
  5. spring容器的前置处理。
  6. 刷新容器。
  7. spring容器的后置处理。
  8. 广播应用上下文启动完成-事件
  9. 执行callRunners
  10. 广播应用上下文可运行-事件

demo

package com.example.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootApplication {
	public static void main(String[] args) {
		SpringApplication.run(BootApplication.class, args);
	}
}

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

我们开始分析源码:

// primarySource就是BootApplication的Class对象
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

接着执行new SpringApplication(primarySources).run(args),我们先看下其构造器:

// 默认进来时,resourceLoader为null。
// 猜测:resourceLoader是用来指定加载器的。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	//项目启动类BootApplication.class设置为属性存储起来
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	//从加载路径中推断当前项目的类型;
	//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)
	//还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	//设置引导类
	this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
	//设置初始化器(Initializer),最后会调用这些初始化器
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	//设置监听器
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//记录main方法的类的class信息
	this.mainApplicationClass = deduceMainApplicationClass();
}

所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作。

构造方法执行完成后,就开始执行run方法了;

SpringApplication.run方法

启动方法

public ConfigurableApplicationContext run(String... args) {
// 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	//bootstrapContext上下文的作用是啥?
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	//初始化应用上下文和异常报告集合
	ConfigurableApplicationContext context = null;
	//设置无头模式:创建图像,不需要显示器和键盘
	configureHeadlessProperty();
	//获取并实例化SpringApplicationRunListener的实现类
	SpringApplicationRunListeners listeners = getRunListeners(args);
	//(1)-启动监听器
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
	//创建  ApplicationArguments 对象 初始化默认应用参数类
	// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
	//(2)-项目运行环境的预配置
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		//配置要忽略的bean信息
		configureIgnoreBeanInfo(environment);
	//准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
		Banner printedBanner = printBanner(environment);
	//(3)-创建Spring容器
		context = createApplicationContext();
	//ApplicationStartup在应用程序启动期间标记步骤,并收集有关执行上下文或其处理时间的数据
		context.setApplicationStartup(this.applicationStartup);
	//(4)-Spring容器前置处理
	//这一步主要是在容器刷新之前的准备动作。
	//包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
		prepareContext(bootstrapContext, 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) {
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}

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

注意:

PS2:遍历调用所有的SpringApplicationRunListenerenvironmentPrepared()方法。

loadSpringFactories方法

启动时缓存的类:

Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

其中Map<String, List<String>>key注解类名接口value就是具体的实现类。
初始化缓存cache的方法就是loadSpringFactories()方法。其会读取META-INF/spring.factories文件中官方事先配置的类。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
	return result;
}

result = new HashMap<>();
try {
	Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
	while (urls.hasMoreElements()) {
		URL url = urls.nextElement();
		UrlResource resource = new UrlResource(url);
		Properties properties = PropertiesLoaderUtils.loadProperties(resource);
		for (Map.Entry<?, ?> entry : properties.entrySet()) {
			String factoryTypeName = ((String) entry.getKey()).trim();
			String[] factoryImplementationNames =
					StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
			for (String factoryImplementationName : factoryImplementationNames) {
				result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
						.add(factoryImplementationName.trim());
			}
		}
	}

	// Replace all lists with unmodifiable lists containing unique elements
	result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
			.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
	cache.put(classLoader, result);
}
catch (IOException ex) {
	throw new IllegalArgumentException("Unable to load factories from location [" +
			FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}

在这里插入图片描述

总结

仅仅只是大致的聊了下,启动的大体流程,细节问题都没有讲,下篇讲。

有空做下下面几件事情:

  • 自定义的ApplicationListener
  • 自定义的BeanFactoryPostProcessor
  • 自定义的BeanPostProcessor

参考地址:

Spring Boot 2.2.6 源码之旅一SpringApplication启动流程一

SpringBoot 源码解析 (二)----- Spring Boot精髓:启动流程源码分析

SpringApplication.run 源码分析

https://www.jianshu.com/p/414d3e2f04e9

https://juejin.cn/post/6844904061733437448

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山鬼谣me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值