SpringBoot应用启动流程源码解析
作者:TomDu 发布时间:2023-11-25 00:03:24
标签:Spring,Boot,启动,流程
前言
Springboot应用在启动的时候分为两步:首先生成 SpringApplication 对象 ,运行 SpringApplication 的 run 方法,下面一一看一下每一步具体都干了什么
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
创建 SpringApplication 对象
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//保存主配置类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断当前是否一个web应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}
其中从类路径下获取到META-INF/spring.factories配置的所有ApplicationContextInitializer和ApplicationListener的具体代码如下
public final class SpringFactoriesLoader {
/**spring.factories的位置*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
/**
* 缓存扫描后的结果, 注意这个cache是static修饰的,说明是多个实例共享的
* 其中MultiValueMap的key就是spring.factories中的key(比如org.springframework.boot.autoconfigure.EnableAutoConfiguration),
* 其值就是key对应的value以逗号分隔后得到的List集合(这里用到了MultiValueMap,他是guava的一键多值map, 类似Map<String, List<String>>)
*/
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
private SpringFactoriesLoader() {
}
/**
* AutoConfigurationImportSelector及应用的初始化器和 * 里最终调用的就是这个方法,
* 这里的factoryType是EnableAutoConfiguration.class、ApplicationContextInitializer.class、或ApplicationListener.class
* classLoader是AutoConfigurationImportSelector、ApplicationContextInitializer、或ApplicationListener里的beanClassLoader
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
/**
* 加载 spring.factories文件的核心实现
*/
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存获取,如果获取到了说明之前已经被加载过
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 找到所有jar中的spring.factories文件的地址
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 循环处理每一个spring.factories文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载spring.factories文件中的内容到Properties对象中
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 遍历spring.factories内容中的所有的键值对
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获得spring.factories内容中的key(比如org.springframework.boot.autoconfigure.EnableAutoConfiguratio)
String factoryTypeName = ((String) entry.getKey()).trim();
// 获取value, 然后按英文逗号(,)分割得到value数组并遍历
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// 存储结果到上面的多值Map中(MultiValueMap<String, String>)
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
}
运行run方法
public ConfigurableApplicationContext run(String... args) {
//开始停止的监听
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//声明一个可配置的ioc容器
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
//配置awt相关的东西
configureHeadlessProperty();
//获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
//回调所有的获取SpringApplicationRunListener.starting()方法
listeners.starting();
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
Banner printedBanner = printBanner(environment);
//创建ApplicationContext;决定创建web的ioc还是普通的ioc,
//通过反射创建ioc容器((ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);)
context = createApplicationContext();
//出现异常之后做异常分析报告
analyzers = new FailureAnalyzers(context);
//准备上下文环境;将environment保存到ioc中;而且applyInitializers();
//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
//回调所有的SpringApplicationRunListener的contextPrepared();
//
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
//刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版
//扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
refreshContext(context);
//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
//ApplicationRunner先回调,CommandLineRunner再回调
afterRefresh(context, applicationArguments);
//所有的SpringApplicationRunListener回调finished方法
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//整个SpringBoot应用启动完成以后返回启动的ioc容器;
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
几个重要的事件回调机制
配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener
只需要放在ioc容器中
ApplicationRunner
CommandLineRunner
来源:https://www.cnblogs.com/tombky/p/12600191.html


猜你喜欢
- 最近在学习ssh框架时,照着网上做了一个商城系统,之前在一些需要用户存在的操作中,都是在每一个action中写重复的代码,这样做现在想起来并
- Result可以设定全局结果集,如:<struts> <constant name="struts
- 在传统的Java编程中,被广为人知的一个知识点是:java Interface接口中不能定义private私有方法。只允许我们定义publi
- 引用开源框架通过AsyncHttpClient进行文件上传,具体内容如下一、步骤:1.添加权限(访问网络权限和读写权限)2.获取上传文件路径
- 前言:Guarded Suspension意为保护暂停,其核心思想是仅当服务进程准备好时,才提供服务。设想一种场景,服务器可能会在很短时间内
- 1.object:匿名内部类在Android最常用的匿名内部类之一就是点击事件,用Java语言写的话就是下面这样:public interf
- 一、基本RPC框架简介在分布式计算中,远程过程调用(Remote Procedure Call,缩写 RPC)允许运行于一台计算机的程序调用
- Eureka注册中心/服务发现框架Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中
- 前言在日常编码中,有了ide的支持,我们已经很少直接在命令行中直接执行java XXX命令去启动一个项目了。然而我们有没有想过,一个简单的j
- 在对类访问使用时,常用到的有访问类的成员、方法。实例化在对类进行访问时,需要将类进行实例化。并产生一个对象。可以使用关键字new来实现。由于
- 曾经遇到过这样的问题,在我的代码中使用了通知栏,一切都正常,但是就是正在进行的通知栏中属于我的程序的那一条总是上下跳来跳去,一闪一闪的。感觉
- SpringBoot下载Excel文件文件损坏我把模板文件放在了resources目录下maven插件打包项目的时候,默认会压缩resour
- 我们在想对一个可枚举的对象集合进行去重操作时,一般第一个想到的就是就是Linq的Distinct方法。先定义一个类,然后使用Distinct
- 本文以实例形式讲述了C#生成Word记录的方法,具体实现代码如下:private void button1_Click(object sen
- 模拟新闻 APP 的界面1)写 ListView 之前先写布局: 这里有两种 Item 的布局:<?xml version=
- class 参数 {
- 一、前言:TCP原理简介首先,保证文章完整性,TCP的理论原理还是需要简介一下,略显枯燥๑乛◡乛๑。TCP(传输控制协议,Transmiss
- 好了 我们聊聊 Bean 的实例化过程的几个重要角色BeanDefinitionRegistryPostProcessor 接口Refres
- 一、背景介绍:我们在进行数据存储的时候,有时候会加入本地缓存、分布式缓存以及数据库存储 * 的结构,当我们取值的时候经常是像下面这样的流程:1
- 要求:用DateTimeFormatter实现: 用扫描器获取输入的时间(年月日时分),这个时间的格式是常用的格式,然后格式化这个时间,把格