0


SpringBoot源码解析(一):启动流程之SpringApplication构造方法

SpringBoot源码系列文章

SpringBoot源码解析(一):启动流程之SpringApplication构造方法


目录

前言

**在之前的文章中,我们深入研究了Tomcat、Spring、以及SpringMVC的源码。这次,我们终于来到SpringBoot的源码分析。接下来的几篇文章将重点关注SpringBoot的

启动原理

自动配置原理

。本篇文章将聚焦于SpringApplication的构造方法。基于

2.7.18

版本,这也是SpringBoot3发布前的最后一个版本。**

**

SpringApplication.run()

方法是启动SpringBoot应用的核心入口。我们从这个方法开始,逐步深入。**

@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class, args);}}

一、SpringApplication的run方法

  • 点进SpringApplication的静态run方法
publicstaticConfigurableApplicationContextrun(Class<?> primarySource,String... args){returnrun(newClass<?>[]{ primarySource }, args);}
  • 接下来进入SpringApplication的构造方法,run方法后面介绍
publicstaticConfigurableApplicationContextrun(Class<?>[] primarySources,String[] args){returnnewSpringApplication(primarySources).run(args);}
  1. 根据类路径来推断Web应用程序的类型
  2. 读取spring.factories文件- 查找引导注册组件初始化器BootstrapRegistryInitializer- 查询上下文初始化器ApplicationContextInitializer- 查询监听器ApplicationListener
  3. 推断启动类Class
privateSet<Class<?>> primarySources;// web应用类型privateWebApplicationType webApplicationType;// 引导注册组件初始化器privateList<BootstrapRegistryInitializer> bootstrapRegistryInitializers;// 上下文初始化器privateList<ApplicationContextInitializer<?>> initializers;// 监听器privateList<ApplicationListener<?>> listeners;// 启动类ClassprivateClass<?> mainApplicationClass;publicSpringApplication(Class<?>... primarySources){this(null, primarySources);}publicSpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources){this.resourceLoader = resourceLoader;// primarySources也就是SpringApplication.run(Application.class, args)的Application不能为空Assert.notNull(primarySources,"PrimarySources must not be null");// 将Application的Class对象添加进来this.primarySources =newLinkedHashSet<>(Arrays.asList(primarySources));// 1.根据类路径来推断 Web 应用程序的类型this.webApplicationType =WebApplicationType.deduceFromClasspath();// 2. 读取spring.factories文件// 2.1 查找并实例化BootstrapRegistryInitializer类型的工厂类this.bootstrapRegistryInitializers =newArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));// 2.2 查找上下文初始化器setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));// 2.3 查找监听器setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));// 3.推断启动类Classthis.mainApplicationClass =deduceMainApplicationClass();}

二、推断Web应用类型

SpringBoot应用程序的三种Web应用类型

  • NONE:- 表示该应用程序不是Web应用,不会启动嵌入式Web服务器
  • SERVLET:- 表示一个传统的基于Servlet的Web应用程序,将启动嵌入式ServletWeb服务器(如Tomcat)
  • REACTIVE:- 表示一个响应式风格的Web应用程序,将启动嵌入式响应式Web服务器(如Netty)
// WebApplicationType枚举类publicenumWebApplicationType{// 表示该应用程序不是 Web 应用,不会启动嵌入式 Web 服务器
    NONE,// 表示一个传统的基于 Servlet 的 Web 应用程序,将启动嵌入式 Servlet Web 服务器(如 Tomcat)
    SERVLET,// 表示一个响应式风格的 Web 应用程序,将启动嵌入式响应式 Web 服务器(如 Netty)
    REACTIVE;// 适合运行在基于 Servlet 的环境中privatestaticfinalString[] SERVLET_INDICATOR_CLASSES ={"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"};// 是 Spring MVC 应用程序的主要调度器privatestaticfinalString WEBMVC_INDICATOR_CLASS ="org.springframework.web.servlet.DispatcherServlet";// 用于 Spring WebFlux。这表明是一个响应式 Web 应用程序privatestaticfinalString WEBFLUX_INDICATOR_CLASS ="org.springframework.web.reactive.DispatcherHandler";// 用于基于 Jersey 的应用程序privatestaticfinalString JERSEY_INDICATOR_CLASS ="org.glassfish.jersey.servlet.ServletContainer";// 推断方法staticWebApplicationTypededuceFromClasspath(){if(ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS,null)&&!ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS,null)&&!ClassUtils.isPresent(JERSEY_INDICATOR_CLASS,null)){returnWebApplicationType.REACTIVE;}for(String className : SERVLET_INDICATOR_CLASSES){if(!ClassUtils.isPresent(className,null)){returnWebApplicationType.NONE;}}returnWebApplicationType.SERVLET;}}

**先介绍下

ClassUtils.isPresent

方法,用它来检查类路径中是否有特定类。使用

Class.forName('类路径')

方法,成功返回true,表示存在此类,报错返回fals,表示没有此类。**

publicstaticbooleanisPresent(String className,@NullableClassLoader classLoader){try{forName(className, classLoader);returntrue;}catch(IllegalAccessError err){thrownewIllegalStateException("Readability mismatch in inheritance hierarchy of class ["+
                className +"]: "+ err.getMessage(), err);}catch(Throwable ex){// Typically ClassNotFoundException or NoClassDefFoundError...returnfalse;}}
  • 回到WebApplicationType.deduceFromClasspath()推断方法- 存在reactive.DispatcherHandler类,当不存在servlet.DispatcherServlet类表示响应式Web应用- 如果不是响应式应用,ServletConfigurableWebApplicationContext都存在表示传统Web应用

三、spring.factories文件

1、spring.factories介绍

**

spring.factories

是 Spring 框架中的一个关键配置文件,通常位于类路径下的

META-INF

目录中。它的主要功能是提供一种自动装配机制,用于在应用启动时自动加载指定的类。通过

spring.factories

文件,开发者可以将特定的配置类、监听器、过滤器等组件注册到Spring上下文中。**

文件格式

  • 通常以键值对的形式表示- 键为接口或抽象类的全限定名- 值为实现类的全限定名,多个类可以用逗号分隔

示例

  • 使用\表示续行符,用来将长行分成多行写
# 自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.MyAutoConfiguration1,\
com.example.config.MyAutoConfiguration2

# 自定义监听器
org.springframework.context.ApplicationListener=\
com.example.listener.MyApplicationListener

2、读取spring.factories文件

  • 用于从所有依赖的JAR文件的META-INF/spring.factories文件中加载指定类型的多个工厂类名称
  • 通过Class.forName反射获取实例化多个对象,并根据对象上@Order注解排序
// SpringApplication类方法private<T>Collection<T>getSpringFactoriesInstances(Class<T> type){returngetSpringFactoriesInstances(type,newClass<?>[]{});}private<T>Collection<T>getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes,Object... args){ClassLoader classLoader =getClassLoader();// 用于从所有依赖的 JAR 文件的 META-INF/spring.factories 文件中加载指定类型的工厂类名称Set<String> names =newLinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 根据权限定类名字符串,Class.forName反射实例化对象List<T> instances =createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);// 它能够根据@Order注解和Ordered接口的优先级来对对象进行排序,从而决定对象的执行顺序AnnotationAwareOrderComparator.sort(instances);return instances;}
  • 在spring.factories文件中,键是接口或抽象类型的全限定名,值是实现类的全限定名列表。读取结果为Map<String, List<String>>
  • getOrDefault方法会获取与指定type(类或接口)匹配的所有工厂类名称(就是通过接口获取多个实现类
// SpringFactoriesLoader类方法publicstaticList<String>loadFactoryNames(Class<?> factoryType,@NullableClassLoader classLoader){ClassLoader classLoaderToUse = classLoader;if(classLoaderToUse ==null){
        classLoaderToUse =SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();// 加载spring.factories文件,获取Map,通过key获取多个实现returnloadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName,Collections.emptyList());}// 读取spring.factories文件的缓存结果staticfinalMap<ClassLoader,Map<String,List<String>>> cache =newConcurrentReferenceHashMap<>();privatestaticMap<String,List<String>>loadSpringFactories(ClassLoader classLoader){// 每次先从缓存中获取,这样,只需要加载一次就可以Map<String,List<String>> result = cache.get(classLoader);if(result !=null){return result;}

    result =newHashMap<>();try{// String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while(urls.hasMoreElements()){URL url = urls.nextElement();UrlResource resource =newUrlResource(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 ->newArrayList<>()).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){thrownewIllegalArgumentException("Unable to load factories from location ["+
                FACTORIES_RESOURCE_LOCATION +"]", ex);}return result;}

通过debug发现会加载三个jar的META-INF/spring.factories文件

  • spring-boot-2.7.18.jar

在这里插入图片描述

  • spring-boot-autoconfigure-2.7.18.jar

在这里插入图片描述

  • spring-beans-5.3.31.jar

在这里插入图片描述

查询引导注册组件初始化器、上下文初始化器、监听器就是从以上三个spring.factories文件中获取BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationListener这三个接口的实现类。

2.1、引导注册组件初始化器BootstrapRegistryInitializer

**BootstrapRegistryInitializer在

ApplicationContext创建之前

对注册表进行配置,并注册一些启动时的关键组件。它主要应用于

SpringCloud

的场景中,用来初始化那些在应用上下文加载之前需要配置的组件,比如

配置中心

服务注册和发现

等。**

@FunctionalInterfacepublicinterfaceBootstrapRegistryInitializer{voidinitialize(BootstrapRegistry registry);}

从以上三个jar的spring.factories文件没有获取到初始化器,表示这里也没用到它,等以后解析SpringCloud源码时候再做深究。

2.2、上下文初始化器ApplicationContextInitializer

**ApplicationContextInitializer主要作用是在Spring应用上下文 (ApplicationContext)

刷新之前

进行自定义的初始化操作。它允许开发者在应用

上下文完全启动和加载所有Bean定义之前

进行特定的配置和设置。**

@FunctionalInterfacepublicinterfaceApplicationContextInitializer<CextendsConfigurableApplicationContext>{voidinitialize(C applicationContext);}

从以上三个jar的spring.factories文件获取到7个上下文初始化器,前5个来自spring-boot-2.7.18.jar,最后2个来自spring-boot-autoconfigure-2.7.18.jar。

  1. org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer- 作用:检查配置中的常见错误或不推荐的配置项,并在检测到这些问题时发出警告,帮助开发者尽早发现潜在的配置问题,确保配置的正确性
  2. org.springframework.boot.context.ContextIdApplicationContextInitializer- 作用:为ApplicationContext设置一个唯一的上下文ID,尤其在多上下文应用程序中有助于区分和管理不同的上下文实例。默认情况下,ID 会基于应用的环境和属性生成
  3. org.springframework.boot.context.config.DelegatingApplicationContextInitializer- 作用:代理一组ApplicationContextInitializer 的初始化任务,实现灵活组合多个初始化任务。此组件将任务委派给自定义的ApplicationContextInitializer列表,使初始化更灵活和可定制
  4. org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer- 作用:将 RSocket 服务器的端口信息暴露在应用上下文环境中,使应用程序的其他组件能够访问该端口信息。这对于需要动态访问 RSocket 服务的端口信息的场景非常有用
  5. org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer- 作用:将 Web 服务器的端口信息暴露在应用上下文环境中,使其他组件可以动态访问该端口信息。适用于需要动态确定服务器端口的情况(例如在随机端口上启动时)
  6. org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer- 作用:在应用上下文中共享一个MetadataReaderFactory实例,以便于Spring扫描类路径和读取类元数据,减少I/O操作和开销,提高性能
  7. org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener- 作用:记录条件评估报告,将自动配置条件的匹配或不匹配详情输出到日志中,帮助开发者理解SpringBoot自动配置的过程和条件匹配的结果,便于调试和优化

后续篇章会单独解析每一个初始化器。

2.3、监听器ApplicationListener

**ApplicationListener作用是监听Spring框架中内置的各种事件(如上下文刷新事件、上下文关闭事件等),也可以监听自定义的事件。基于事件触发执行逻辑,常用于对

生命周期事件的监听以及自定义事件的处理

,帮助实现松耦合的事件驱动架构。**

@FunctionalInterfacepublicinterfaceApplicationListener<EextendsApplicationEvent>extendsEventListener{// 处理应用程序事件voidonApplicationEvent(E event);static<T>ApplicationListener<PayloadApplicationEvent<T>>forPayload(Consumer<T> consumer){return event -> consumer.accept(event.getPayload());}}

从以上三个jar的spring.factories文件获取到8个监听器,前7个来自spring-boot-2.7.18.jar,最后一个来自spring-boot-autoconfigure-2.7.18.jar。

  1. org.springframework.boot.ClearCachesApplicationListener- 作用:用于清除SpringBoot内部的缓存,通常在应用程序重新加载或重新初始化时触发,以确保数据一致性- 触发时机:当应用上下文刷新或重新启动时
  2. org.springframework.boot.builder.ParentContextCloserApplicationListener- 作用:监听子上下文的关闭事件,并在子上下文关闭时关闭其父上下文。这通常用于父子上下文组合应用场景中,以确保上下文的关闭顺序正确- 触发时机:当子上下文关闭时
  3. org.springframework.boot.context.FileEncodingApplicationListener- 作用:确保应用程序以指定的文件编码运行。如果系统的文件编码与SpringBoot配置中的编码不匹配,它会强制设置为指定编码,确保编码一致性- 触发时机:应用上下文刷新时
  4. org.springframework.boot.context.config.AnsiOutputApplicationListener- 作用:控制 ANSI 输出的设置,允许在控制台中使用 ANSI 彩色输出(如日志输出中的彩色显示)- 触发时机:应用上下文刷新时,根据配置启用或禁用 ANSI 彩色输出
  5. org.springframework.boot.context.config.DelegatingApplicationListener- 作用:充当其他 ApplicationListener 的代理,将事件转发给多个监听器。这使得可以集中管理多个监听器- 触发时机:在监听器列表中注册的事件触发时
  6. org.springframework.boot.context.logging.LoggingApplicationListener- 作用:用于初始化日志系统,根据 application.properties 或环境配置设置日志级别和格式。Spring Boot 的日志系统初始化通常是由该监听器负责- 触发时机:应用启动时,最早被触发的监听器之一
  7. org.springframework.boot.env.EnvironmentPostProcessorApplicationListener- 作用:在 Environment 准备阶段后调用 EnvironmentPostProcessor,允许对环境变量进行进一步处理,例如动态配置属性值- 触发时机:在应用上下文刷新之前
  8. org.springframework.boot.autoconfigure.BackgroundPreinitializer- 作用:在后台线程中异步初始化一些资源或任务,减少主线程的阻塞时间。此操作通常是提前加载一些可能需要时间初始化的资源,以优化启动时间- 触发时机:在应用启动阶段,通过后台线程异步执行

后续篇章会单独解析每一个监听器器。

四、推断启动类Class

**通过创建一个异常栈追踪来找到调用

main

方法的类。以下是对它的逐步分析**

  1. 获取栈追踪信息:new RuntimeException().getStackTrace() 获取当前执行线程的堆栈追踪信息- 堆栈追踪中包含了方法调用的顺序,每个元素都是一个StackTraceElement对象- 记录了方法名、类名、文件名和代码行号等信息
  2. 遍历栈追踪:通过for循环遍历每个StackTraceElement,查找方法名为main的元素- 如果找到了方法名为main的元素,则可以通过stackTraceElement.getClassName()获取该类的全限定名,然后使用Class.forName()加载该类- 如果类加载失败(可能是类不存在),会抛出ClassNotFoundException,但这里捕获了异常并选择继续执行
  3. 最终,如果未找到主类或类加载失败,则返回null
// SpringApplication类方法privateClass<?> mainApplicationClass;...this.mainApplicationClass =deduceMainApplicationClass();...privateClass<?>deduceMainApplicationClass(){try{// 获取当前执行线程的堆栈追踪信息StackTraceElement[] stackTrace =newRuntimeException().getStackTrace();// 遍历每个元素for(StackTraceElement stackTraceElement : stackTrace){// 获取方法名为main的元素if("main".equals(stackTraceElement.getMethodName())){// 通过元素类权限定名的Class.forName()获取Class对象returnClass.forName(stackTraceElement.getClassName());}}}catch(ClassNotFoundException ex){// Swallow and continue}returnnull;}

总结

  1. SpringApplication.run():作为SpringBoot应用的启动入口,它负责创建SpringApplication对象,并调用其run方法进行启动
  2. 推断Web应用类型:SpringBoot应用分为三种类型:NONESERVLETREACTIVE,根据类路径中的特定类进行推断
  3. 读取spring.factories文件:在SpringBoot启动过程中,从META-INF/spring.factories文件加载初始化器和监听器,以便实现自动配置和事件处理
  4. 推断启动类:通过堆栈追踪找到调用main方法的类,即应用的主启动类
标签: spring boot java 后端

本文转载自: https://blog.csdn.net/qq_35512802/article/details/143275477
版权归原作者 冬天vs不冷 所有, 如有侵权,请联系我们删除。

“SpringBoot源码解析(一):启动流程之SpringApplication构造方法”的评论:

还没有评论