当前位置: 首页 > news >正文

【SpringBoot】万字源码解析——启动流程

Spring Boot启动流程

Spring Boot 的入口类:

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

Spring Boot 的启动过程可以分为两方面,一个是 new SpringApplication(primarySources)初始化过程,一个是 SpringApplication.run()应用运行过程

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args);
}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 传入primarySources数组,构造SpringApplicationreturn (new SpringApplication(primarySources)).run(args);
}

执行构造函数

构造函数是 Spring Boot 启动过程的第一步,负责初始化各种属性、验证输入并设置必要的上下文,为后续的应用启动和上下文配置奠定基础。Spring Boot 通过一个重载构造函数来接收 null 值的资源加载器和主类数组:

public SpringApplication(Class<?>... primarySources) {this((ResourceLoader)null, primarySources);
}public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {....
}

1.初始化字段

重构的构造函数接收ResourceLoaderprimarySources参数,并为各种字段进行初始化,比如ResourceLoader, bannerMode, headless等。

this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;

2.设置主源

primarySources转换为LinkedHashSet,以确保启动过程中按顺序处理,避免重复。

Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));

3.推断应用类型

通过检查类路径classpath中的组件(如 "org.springframework.web.reactive.DispatcherHandler)来确定当前应用的类型:

  • REACTIVE:响应式 Web 应用(基于 Reactor 和 WebFlux)。
  • SERVLET:传统的 Servlet 类型 Web 应用(如使用 Spring MVC)。
  • NONE:非 Web 应用,通常是命令行应用。
this.webApplicationType = WebApplicationType.deduceFromClasspath();

4.加载工厂实例

META-INF/spring.factories 中加载配置的初始化器 ApplicationContextInitializerBootstrapRegistryInitializer监听器 ApplicationListener

  • BootstrapRegistryInitializer 允许Spring Boot 启动时自定义初始化 Bootstrap 注册表。Bootstrap 注册表是一个支持应用上下文的初始化过程的机制,通常用于配置与应用启动相关的共享资源。
  • ApplicationContextInitializer 允许用户ApplicationContext 刷新前进行进一步的初始化配置操作。这包括但不限于添加属性源、修改环境变量、设置 Bean 定义等。
  • ApplicationListener,用于监听 Spring Boot 的生命周期事件。这些监听器会在应用启动过程中响应不同的事件,如应用启动事件、环境准备事件、上下文刷新事件等。
// 加载Bootstrap注册表初始化器
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 加载应用上下文初始化器
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

getSpringFactoriesInstances() 方法的核心方法

  • loadFactoryNames():利用 SpringFactoriesLoader 获取指定工厂的实现类的 Set 集合
  • createSpringFactoriesInstances:通过反射机制实例化每个工厂的实现类
// 利用Spring工厂加载器获取指定工厂的实现类的set集合
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 通过反射机制实例化每个工厂的实现类
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

5.推断主类

通过 StackTraceElement 找到 main 方法,确定应用程序的入口类。这通常是标记了 @SpringBootApplication 注解的类。

this.mainApplicationClass = this.deduceMainApplicationClass();

执行run方法

1.启动计时器并初始化

  • 在执行run方法的开头,首先启动一个计时器以记录应用启动的总时长。

  • 接着,创建一个DefaultBootstrapContext,它会遍历并执行在Bootstrap注册表中的所有初始化器,以确保启动过程中的必要资源和设置得到正确配置。

  • 然后,ConfigurableApplicationContext的引用被声明为null,并配置无头属性,以便在没有用户界面的环境中正常运行。

// 1.1.启动计时器
long startTime = System.nanoTime();
// 1.2.初始化bootstrapContext上下文,该方法会遍历并执行BootStrap注册表中的初始化器
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
// 1.3.声明ApplicationContext为null
ConfigurableApplicationContext context = null;
// 1.4.设置无头属性
this.configureHeadlessProperty();

2.获取并启动监听器

// 2.1.获取监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
// 2.2.通知监听器,应用程序即将启动
listeners.starting(bootstrapContext, this.mainApplicationClass);

在获取监听器的方法 getRunListeners() 中,将所有的监听器封装为一个 SpringApplicationRunListeners 对象,由于在构造函数执行阶段已经加载了监听器对象,在调用方法 getSpringFactoriesInstances 时会直接查询缓存获取对象。

// 2.1.获取监听器
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class[]{SpringApplication.class, String[].class};// 封装从getSpringFactoriesInstances()方法中获得工厂的所有实现类return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
}

通知监听器应用即将启动。这一步骤确保所有监听器能够在应用启动的早期阶段参与并进行必要的初始化。

// 2.2.通知监听器
listeners.starting(bootstrapContext, this.mainApplicationClass)

3.装配环境参数

  • 将环境参数与**「引导上下文」**绑定,prepareEnvironment() 方法会加载应用的外部配置。这包括 application.propertiesapplication.yml 文件中的属性,环境变量,系统属性等。
// 3.1.创建命令行参数对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 3.2.加载应用的外部配置
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 3.3.配置忽略 Bean 信息
this.configureIgnoreBeanInfo(environment);

prepareEnvironment() 方法中,首先会进行环境配置,还会执行监听器的 environmentPrepared() 方法,表明应用程序的环境已经准备好,最后再将环境绑定到应用程序中。

4.打印横幅

Banner printedBanner = this.printBanner(environment);

5.创建应用上下文

根据构造阶段推断出的 Web 应用类型,创建Spring容器

// 5.调用createApplicationContext方法创建Spring容器
context = this.createApplicationContext();

6.应用上下文准备阶段

this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

进入 prepareContext()方法,具体实现如下:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 6.1.将传入的环境参数应用到上下文中,并调用后处理方法context.setEnvironment(environment);this.postProcessApplicationContext(context);// 6.2.遍历并执行所有注册的初始化器,进一步配置应用上下文this.applyInitializers(context);// 6.3.通知监听器,上下文准备完毕listeners.contextPrepared(context);// 6.4.关闭启动阶段的引导上下文,释放与启动相关的资源bootstrapContext.close(context);// 6.5.打印日志启动信息if (this.logStartupInfo) {this.logStartupInfo(context.getParent() == null);this.logStartupProfileInfo(context);}// 6.6.将命令行参数和横幅注册为Bean,存放到应用上下文中ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}// 6.7.根据Bean工厂,允许配置循环引用和 bean 定义覆盖if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {((AbstractAutowireCapableBeanFactory)beanFactory).setAllowCircularReferences(this.allowCircularReferences);if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}// 6.8.设置懒初始化配置if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));// 6.9.加载源,确保应用上下文中定义的 bean 被正确创建。Set<Object> sources = this.getAllSources();Assert.notEmpty(sources, "Sources must not be empty");this.load(context, sources.toArray(new Object[0]));// 6.10.通知监听器,所有的Bean都已经加载但未进行实例化listeners.contextLoaded(context);
}

7.应用上下文刷新阶段

this.refreshContext(context);
  • 首先通过加锁确保线程安全,创建并配置 BeanFactory,这一过程包括注册 Bean 后处理器和事件监听器。
  • onRefresh() 方法中,还会启动 Web 服务器。
  • 最后,通过配置好的 BeanFactory 实例化所有的 Beans。在这个过程中,BeanFactory 会根据定义的元数据创建和初始化 Beans,并根据需求进行依赖注入,确保整个应用的组件能够顺利协作。

8.应用上下文收尾阶段

// 8.1.afterRefresh()无实际内容,后续版本被移除
this.afterRefresh(context, applicationArguments);
// 8.2.计算应用启动时间
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
// 8.3.通知监听器,应用程序启动完成
listeners.started(context, timeTakenToStartup);

9.回调运行器

// 9.1.回调运行器
this.callRunners(context, applicationArguments);
// 9.2.通知监听器
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);

该方法从上下文 context 中获取所有已注册的 ApplicationRunnerCommandLineRunner,并结合传入的应用参数 args执行这些运行器。

这些运行器允许开发者在应用程序启动后执行特定的逻辑,例如初始化数据、设置应用状态或执行启动任务,提供了灵活性和扩展性。

private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList();// 9.1.1.从上下文中获取运行器runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);Iterator var4 = (new LinkedHashSet(runners)).iterator();// 9.1.2.结合应用参数执行运行器while(var4.hasNext()) {Object runner = var4.next();if (runner instanceof ApplicationRunner) {this.callRunner((ApplicationRunner)runner, args);}if (runner instanceof CommandLineRunner) {this.callRunner((CommandLineRunner)runner, args);}}
}

10.异常处理

在整个启动过程中,如果出现任何异常,都会被捕获并通过handleRunFailure()方法进行处理,在该方法中,会通知监听器应用程序启动时出现异常。

该方法会记录错误信息,并通过监听器通知失败事件。最终,抛出IllegalStateException来中止应用启动,确保调用者能够识别到启动失败的状态。

catch (Throwable var12) {ex = var12;this.handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);
}

SpringApplicationRunListeners监听器

SpringApplicationRunListeners 是一个具体的类。它实现了 Spring Boot 中的监听器机制,用于在应用程序的不同启动阶段通知注册的监听器(SpringApplicationRunListener 接口的实现类)。通过这个类,Spring Boot 可以在应用启动过程中管理多个监听器,处理各种生命周期事件。

SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners, ApplicationStartup applicationStartup) {// 记录日志this.log = log;// 保存已注册的监听器列表,这些监听器会对应用程序生命周期事件做出响应this.listeners = new ArrayList(listeners);// 跟踪启动步骤,以便进行性能监控this.applicationStartup = applicationStartup;
}

SpringApplicationRunListener 是 Spring Boot 中的一个接口,用于在应用启动过程的不同阶段提供回调。实现这个接口允许监听并响应应用生命周期中的关键事件。该接口定义了多个方法,每个方法对应启动过程中的特定阶段,包括:

  1. starting(): 在运行开始时调用,此时尚未开始任何处理,可以用于初始化在启动过程中需要的资源。
  2. environmentPrepared(): 当 SpringApplication 准备好 Environment 但在创建 ApplicationContext 之前调用,这是修改应用环境属性的好时机。
  3. contextPrepared(): 当 ApplicationContext 准备好但在加载之前调用,可以用于对上下文进行预处理。
  4. contextLoaded(): 当 ApplicationContext 被加载但在刷新之前调用,此时所有的 Bean 定义都已加载,但尚未实例化。
  5. started(): 在 ApplicationContext 刷新之后、任何应用运行器和命令行运行器被调用之前调用,此时应用已经准备好接收请求。
  6. running(): 在运行器被调用之后、应用启动完成之前调用,这是在应用启动并准备好服务请求时执行某些动作的好时机。
  7. failed(): 如果启动过程中出现异常,则调用此方法。

SpringFactoriesLoader原理

SpringFactoriesLoader方法会根据传入的工厂类类加载器,从 META-INF/spring.factories 文件中加载**「指定类型对应的工厂类名称」**。

loadFactoryNames()

loadFactoryNames 方法是一个高级 API,它通过获取入参中的全限定类名 factoryTypeName,在内部调用 loadSpringFactories() 方法获取返回的 Map 集合,并根据 factoryTypeName 获取了 Map 中的实现类的 List 集合

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}// 获取工厂类的全限定名String factoryTypeName = factoryType.getName();// 从 loadSpringFactories 返回的 Map 中获取指定类型工厂的实现类return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories()

loadSpringFactories 方法是更加底层的方法,通过缓存机制类加载器获取 spring.factories 文件中所有配置的工厂及其实现类,将这些信息封装为 Map 集合后返回给上游的 API。

缓存机制

方法会检查是否已经通过当前类加载器加载过 spring.factories 文件。如果缓存 (cache) 中已经存在相应的工厂信息,直接返回缓存的 Map<String, List<String>>,避免重复加载。

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

加载 META-INF/spring.factories

方法会通过类加载器查找所有路径下名为 META-INF/spring.factories 的文件。由于每个 JAR 包都可能包含一个 META-INF/spring.factories 文件,方法会返回一个 Enumeration<URL> 对象,表示找到的所有相关资源文件。

Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

解析spring.factories文件

通过迭代逐个读取每个找到的 spring.factories 文件。对于每个文件,使用 PropertiesLoaderUtils.loadProperties() 将文件内容解析为 Properties 对象。

每个 Properties 对象对应一个 spring.factories 文件的内容,其中 key 是工厂类型(例如 org.springframework.context.ApplicationContextInitializer),value 是逗号分隔的工厂实现类列表。

while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);

将工厂类型和实现类存入Map

遍历 PropertiesentrySet()。对于每个 entrykey 是工厂类型的全限定类名,value 是对应的工厂实现类名(逗号分隔)。

工厂类型名称通过 entry.getKey() 获取,并使用 String.trim() 去除可能的空白字符。工厂实现类则将逗号分隔的字符串转换为实现类的数组。

while(var6.hasNext()) {Map.Entry<?, ?> entry = (Map.Entry)var6.next();String factoryTypeName = ((String)entry.getKey()).trim();String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());String[] var10 = factoryImplementationNames;int var11 = factoryImplementationNames.length;for(int var12 = 0; var12 < var11; ++var12) {String factoryImplementationName = var10[var12];((List)result.computeIfAbsent(factoryTypeName, (key) -> {return new ArrayList();})).add(factoryImplementationName.trim());}
}
http://www.lryc.cn/news/469805.html

相关文章:

  • Nginx 配置初步 下
  • 可视化ETL平台-Kettle的安装及简单使用
  • java8 动态加载jar包至系统的classpath
  • C++二级题 计算好数:1数大于0数(二进制的位运算)
  • 数字孪生城市:智慧城市的未来蓝图
  • Java篇图书管理系统
  • BUUCTF之web篇
  • 010——二叉树(2)线索化
  • 鸿蒙拍照小助手02
  • lua while循环
  • JAVA篇之类和对象
  • IO流详解_CoderLix
  • 241023-RHEL非管理员安装Docker并开放指定宿主机端口部署Gitlab
  • python ubuntu安装加速
  • 100种算法【Python版】第12篇——快速幂算法
  • Java多线程详解②(全程干货!!!)Thread Runnable
  • 机器学习——图神经网络
  • 一、在cubemx下RTC配置调试实例测试
  • 【Nas】X-DOC:Mac mini Docker部署中国特供版Jellyfin
  • 合合信息:生成式Al时代的内容安全与系统构建加速,开启智能文档的全新潜能
  • 京东双十一高并发场景下的分布式锁性能优化
  • 华为ICT题库-AI 人工智能部分
  • React Native 修改安卓应用图片和名称
  • 普推知产:商标初审已下,商标申请通过如何高些!
  • HICP--2
  • sheng的学习笔记-AI基础-正确率/召回率/F1指标/ROC曲线
  • Linux -- 共享内存(2)
  • 云函数实现发送邮件,以qq邮箱为例
  • Kafka如何控制消费的位置?
  • python爬虫——Selenium的基本使用