springboot启动原理总结(springboot启动过程是)
本文目录
- springboot启动过程是
- Springboot 启动加载机制
- Spring boot Batch 的启动原理- Configuration
- SpringBoot核心原理:自动配置、事件驱动、Condition
- springboot自动配置原理
- SpringBoot Stater原理
- SpringBoot启动原理分析
- [Spring boot源码解析] 2 启动流程分析
- SpringBoot启动分析
- SpringBoot应用启动原理(二) 扩展URLClassLoader实现嵌套jar加载
springboot启动过程是
SpringBoot的启动主要是通过实例化SpringApplication来启动的。
在了解SpringBoot的启动流程的时候,我们先看一下一个SpringBoot应用是如何启动的,如下是一个简单的SpringBoot程序,非常的简洁,他是如何做到的呢,我们接下来就将一步步分解。
SpringBoot在启动时,通过*****完成对依赖jar包中XxAutopConfiguration类的注册,自然DubboAutoConfiguration也会被注册到容器内部。
Springboot 启动加载机制
Spring boot最大的好处就是遵从了java 约定大于配置 不用面对一大堆的配置文件,spring boot是根据你用的包来决定提供什么配置。
springBoot的启动可以分为两个部分,第一部分:SpringApplication的实例化;第二部分:调用该实例运行run方法(springboot初始化)。
ApplicationContextInitializer:当springboot上下文Context初始化完成后会调用
ApplicationListener:当springboot启动时事件change后都会触发
spring boot 初始化内容还是很多的,但是总结起来就四点:
即, 初始化BeanFactory,加载bean,启动内置web容器;
发布ContextRefreshedEvent事件;
Spring boot Batch 的启动原理- Configuration
Spring boot 整合了web 和batch ,但是他们肯定不是同一条路, 在spring boot 中,会推断当前的运行环境。 ***** = *****();
从上文可以看出,Spring 尝试从classpath 里找到特征类,来判断当前app 是什么类型。当然这种判断是有局限性的,有可能是transitive 带进来一个带有servlet 的类被当成了 *****, 实际上是个*****;。如果不想以web 运行就是想运行batch 可以在***** 强行指定WebApplicationType
具体发生作用的请看下面的stack trace
当一个batch application需要启动,需要 配置JobRepository, Datasource 等等,所有的开始都来自一个annotation @EnableBatchProcessing
当加入@EnableBatchProcessing 时, BatchConfigurationSelector 开始启动,怎么启动的大家可以参考下面的stack trace。
import 类主要是由ConfigurationClassPostProcessor 来实现的。 当BatchConfigurationSelector 被调用的时候,我们可以看到他有两条支路。
那么这两条路有啥不同呢。 主要是job 定义的方式不同。
modular = true 的情况下,下面是一个例子
可以有多个子ApplicationContextFactory ,这样好处是在除了job 大家不可以重复,因为是在不同的context 里,其他的step,reader, writer,processor,mapper ,以及所有的bean等等都可以重名。
那为什么Job 不可以重复,是因为虽然可以重复,但是如果job 也重复,对用户来讲太不友好了。用户可能不知道自己配的是哪个context 的job。具体为什么可以重名要看看GenericApplicationContextFactory 的实现。
当GenericApplicationContextFactory::createApplicationContext, 会触发 ApplicationContextHelper 的构造函数 从而调用 loadConfiguration(config) 把定义的bean 加入到context 里。那么有个问题,parent 在哪里设置,createApplicationContext 是什么时候调用的。
我们继续看ModularConfiguration
从Modular 的角度来看 首先他可以外部注入一个Configurer,如果没有就选择DefaultBatchConfigurer, 如果有多个选择则会抛出。
当然Datasource也可以选择外部注入,或者由 DefaultBatchConfigurer::initialize 方法 SimpleJobLauncher , JobRepository 和 JobExplorer 都是 由FactoryBean 的方式实现的。
在这个DefaultBatchConfigurer 中可以看到JobLauncher 的类型是
initialize 里MapJobRepositoryFactoryBean 这个可以重点读一下。 首先用FactoryBean的模式实现了一个ProxyBean,如果想了解FactoryBean 的用法,这是个典型的例子。但是这个FactoryBean 是以api 行为直接调用的,并没有注册到Spring 的context 中。
配置好job 需要的 jobRepository ,jobLauncher等 那么重点来了,下面的AutomaticJobRegistrar 就是来处理ApplicationContextFactory
这个逻辑是把所有的ApplicationContextFactory 的bean instance 放入到AutomaticJobRegistrar 里去。 这就回到了第一个问题,parent context 是什么时候放进去的。就是在
*****(*****) 请看下面的调用栈,在ApplicationContextAwareProcessor 里会自动把parent context 注入。
但是放进去啥时候用呢?我们看一下AutomaticJobRegistrar
原来AutomaticJobRegistrar 是个Smartlifecycle, 从Smartlifecycle的细节可以从 Springboot Smartlifecycle 来得知。它就是在所有bean都初始化结束后开始进行的一个阶段。在这个start 方法中,开始遍历所有的ApplicationContextFactory, 来进行加载。 从上文这个jobLoader 是DefaultJobLoader。
那么可以看看DefaultJobLoader::doLoad 方法
这里有几个关键调用 第一个是createApplicationContext, 把context 里定义的全部加载到Spring context里去,这就满足了GenericApplicationContextFactory 工作的两个条件。第二个是doRegister(context, job) 里 *****(jobFactory);
我们看一下JobRepository的实现MapJobRegistry::register 方法,在这里就把jobname 和jobFactory 的键值对存储起来了。
这样就可以通过JobRepository 这个bean 拿到所有注册的job 了。
咱们再回来看@EnableBatchProcessing 这个annotation,当没有设定modular 的时候是比较简单的,只是实现了一个proxy based 的Job的bean。
同样也只有BatchConfigurer 来配置。 这个逻辑和Modular 是一样的。从这两个配置知道, Modular 注册了在子context的配置,并且加载。 但是当以正常bean 的方式存在的,是怎么读进来的呢。这时候就要看 JobRegistryBeanPostProcessor
这个 postProcessAfterInitialization 方法里, 对每个job 类型的bean, jobRegistry 加入了ReferenceJobFactory。 这样所有的以bean 的方式定义的都可以通过jobRegistry获得。
SpringBoot核心原理:自动配置、事件驱动、Condition
SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本非常低,但是学习其实现原理的成本大大增加,需要先了解熟悉Spring原理。
如果还不清楚Spring原理的,可以先查看博主之前的文章,本篇主要分析SpringBoot的启动、自动配置、Condition、事件驱动原理。
SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可:
可以看到第一种是最简单的,也是最常用的方式,需要注意类上面需要标注 @SpringBootApplication 注解,这是自动配置的核心实现,稍后分析,先来看看SpringBoot启动做了些什么?
在往下之前,不妨先猜测一下,run方法中需要做什么?对比Spring源码,我们知道,Spring的启动都会创建一个 ApplicationContext 的应用上下文对象,并调用其refresh方法启动容器,SpringBoot只是Spring的一层壳,肯定也避免不了这样的操作。
另一方面,以前通过Spring搭建的项目,都需要打成War包发布到Tomcat才行,而现在SpringBoot已经内置了Tomcat,只需要打成Jar包启动即可,所以在run方法中肯定也会创建对应的Tomcat对象并启动。以上只是我们的猜想,下面就来验证,进入run方法:
SpringBoot的启动流程就是这个方法,先看 getRunListeners 方法,这个方法就是去拿到所有的 SpringApplicationRunListener 实现类,这些类是用于SpringBoot事件发布的,关于事件驱动稍后分析,这里主要看这个方法的实现原理:
一步步追踪下去可以看到最终就是通过SPI机制根据接口类型从 META-INF/***** 文件中加载对应的实现类并实例化,SpringBoot的自动配置也是这样实现的。
为什么要这样做呢?通过注解扫描不可以么?当然不行,这些类都在第三方jar包中,注解扫描实现是很麻烦的,当然你也可以通过 @Import 注解导入,但是这种方式不适合扩展类特别多的情况,所以这里采用SPI的优点就显而易见了。
回到run方法中,可以看到调用了 createApplicationContext 方法,见名知意,这个就是去创建应用上下文对象:
注意这里通过反射实例化了一个新的没见过的上下文对象 AnnotationConfigServletWebServerApplicationContext ,这个是SpringBoot扩展的,看看其构造方法:
如果你有看过Spring注解驱动的实现原理,这两个对象肯定不会陌生,一个实支持注解解析的,另外一个是扫描包用的。
上下文创建好了,下一步自然就是调用refresh方法启动容器:
这里首先会调用到其父类中 ServletWebServerApplicationContext :
可以看到是直接委托给了父类:
这个方法不会陌生吧,之前已经分析过了,这里不再赘述,至此SpringBoot的容器就启动了,但是Tomcat启动是在哪里呢?run方法中也没有看到。
实际上Tomcat的启动也是在refresh流程中,这个方法其中一步是调用了onRefresh方法,在Spring中这是一个没有实现的模板方法,而SpringBoot就通过这个方法完成了Tomcat的启动:
这里首先拿到 TomcatServletWebServerFactory 对象,通过该对象再去创建和启动Tomcat:
***隐藏网址***
如果想要扩展的话则可以对 additionalTomcatConnectors 属性设置值,需要注意这个属性没有对应的setter方法,只有 addAdditionalTomcatConnectors 方法,也就是说我们只能通过实现 BeanFactoryPostProcessor 接口的 postProcessBeanFactory 方法,而不能通过 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法,因为前者可以通过传入的BeanFactory对象提前获取到 TomcatServletWebServerFactory 对象调用 addAdditionalTomcatConnectors 即可;而后者只能拿到BeanDefinition对象,该对象只能通过setter方法设置值。
这段代**在控制台打印所有的事件名称,按照顺序如下:
以上是正常启动关闭,如果发生异常还有发布 ApplicationFailedEvent 事件。事件的发布遍布在整个容器的启动关闭周期中,事件发布对象刚刚我们也看到了是通过SPI加载的 SpringApplicationRunListener 实现类 EventPublishingRunListener ,同样事件**器也是在 ***** 文件中配置的,默认实现了以下**器:
可以看到有用于文件编码的( FileEncodingApplicationListener ),有加载日志框架的( LoggingApplicationListener ),还有加载配置的( ConfigFileApplicationListener )等等一系列**器,SpringBoot也就是通过这系列**器将必要的配置和组件加载到容器中来,这里不再详细分析,感兴趣的读者可以通过其实现的 onApplicationEvent 方法看到每个**器究竟是**的哪一个事件,当然事件发布和**我们自己也是可以扩展的。
SpringBoot最核心的还是自动配置,为什么它能做到开箱即用,不再需要我们手动使用 @EnableXXX 等注解来开启?这一切的答案就在 @SpringBootApplication 注解中:
这里重要的注解有三个: @SpringBootConfiguration 、 @EnableAutoConfiguration 、 @ComponentScan 。 @ComponentScan 就不用再说了, @SpringBootConfiguration 等同于 @Configuration ,而 @EnableAutoConfiguration 就是开启自动配置:
@AutoConfigurationPackage 注解的作用就是将该注解所标记类所在的包作为自动配置的包,简单看看就行,主要看 AutoConfigurationImportSelector ,这个就是实现自动配置的核心类,注意这个类是实现的 DeferredImportSelector 接口。
在这个类中有一个 selectImports 方法。这个方法在我之前的文章这一次搞懂Spring事务注解的解析也有分析过,只是实现类不同,它同样会被 ConfigurationClassPostProcessor 类调用,先来看这个方法做了些什么:
追踪源码最终可以看到也是从 META-INF/***** 文件中拿到所有 EnableAutoConfiguration 对应的值(在 spring-boot-autoconfigure 中)并通过反射实例化,过滤后包装成 AutoConfigurationEntry 对象返回。
看到这里你应该会觉得自动配置的实现就是通过这个 selectImports 方法,但实际上这个方法通常并不会被调用到,而是会调用该类的内部类 AutoConfigurationGroup 的process和selectImports方法,前者同样是通过 getAutoConfigurationEntry 拿到所有的自动配置类,而后者这是过滤排序并包装后返回。
下面就来分析 ConfigurationClassPostProcessor 是怎么调用到这里的,直接进入 processConfigBeanDefiniti*** 方法:
前面一大段主要是拿到合格的 Configuration 配置类,主要逻辑是在 ***** 方法中,该方法完成了对 @Component 、 @Bean 、 @Import 、 @ComponentScans 等注解的解析,这里主要看对 @Import 的解析,其它的读者可自行分析。一步步追踪,最终会进入到 processConfigurationClass 方法:
这里需要注意 ***** 方法的调用,这个方法就是进行Bean加载过滤的,即根据 @Condition 注解的匹配值判断是否加载该Bean,具体实现稍后分析,继续跟踪主流程 doProcessConfigurationClass :
这里就是完成对一系列注解的支撑,我省略掉了,主要看 processImports 方法,这个方法就是处理 @Import 注解的:
刚刚我提醒过 AutoConfigurationImportSelector 是实现 DeferredImportSelector 接口的,如果不是该接口的实现类则是直接调用 selectImports 方法,反之则是调用 ***** 方法:
首先创建了一个 DeferredImportSelectorHolder 对象,如果是第一次执行则是添加到 deferredImportSelectors 属性中,等到 ***** 的最后调用process方法:
反之则是直接执行,首先通过register拿到 AutoConfigurationGroup 对象:
然后在 processGroupImports 方法中进行真正的处理:
在 getImports 方法中就完成了对process和 selectImports 方法的调用,拿到自动配置类后再递归调用调用 processImports 方法完成对自动配置类的加载。至此,自动配置的加载过程就分析完了,下面是时序图:
在自动配置类中有很多Condition相关的注解,以AOP为例:
这里就能看到 @ConditionalOnProperty 、 @ConditionalOnClass 、 @ConditionalOnMissingClass ,另外还有 @ConditionalOnBean 、 @ConditionalOnMissingBean 等等很多条件匹配注解。
这些注解表示条件匹配才会加载该Bean,以 @ConditionalOnProperty 为例,表明配置文件中符合条件才会加载对应的Bean,prefix表示在配置文件中的前缀,name表示配置的名称, havingValue 表示配置为该值时才匹配, matchIfMissing 则是表示没有该配置是否默认加载对应的Bean。其它注解可类比理解记忆,下面主要来分析该注解的实现原理。
这里注解点进去看会发现每个注解上都标注了 @Conditional 注解,并且value值都对应一个类,比如 OnBeanCondition ,而这些类都实现了 Condition 接口,看看其继承体系:
上面只展示了几个实现类,但实际上Condition的实现类是非常多的,我们还可以自己实现该接口来扩展 @Condition 注解。Condition接口中有一个matches方法,这个方法返回true则表示匹配。该方法在 ConfigurationClassParser 中多处都有调用,也就是刚刚我提醒过的shouldSkip方法,具体实现是在 ConditionEvaluator 类中:
再来看看matches的实现,但 OnBeanCondition 类中没有实现该方法,而是在其父类 SpringBootCondition 中:
getMatchOutcome 方法也是一个模板方法,具体的匹配逻辑就在这个方法中实现,该方法返回的 ConditionOutcome 对象就包含了是否匹配和日志消息两个字段。进入到 OnBeanCondition 类中:
可以看到该类支持了 @ConditionalOnBean 、 @ConditionalOnSingleCandidate 、 @ConditionalOnMissingBean 注解,主要的匹配逻辑在 getMatchingBeans 方法中:
这里逻辑看起来比较复杂,但实际上就做了两件事,首先通过 getNamesOfBeansIgnoredByType 方法调用 ***** 拿到容器中对应的Bean实例,然后根据返回的结果判断哪些Bean存在,哪些Bean不存在(Condition注解中是可以配置多个值的)并返回MatchResult对象,而MatchResult中只要有一个Bean没有匹配上就返回false,也就决定了当前Bean是否需要实例化。
本篇分析了SpringBoot核心原理的实现,通过本篇相信读者也将能更加熟练地使用和扩展SpringBoot。
另外还有一些常用的组件我没有展开分析,如事务、MVC、**器的自动配置,这些我们有了Spring源码基础的话下来看一下就明白了,这里就不赘述了。
最后读者可以思考一下我们应该如何自定义starter启动器,相信看完本篇应该难不倒你。
springboot自动配置原理
springboot自动配置原理是基于条件判断来配置Bean。
pring Boot的自动配置原理是基于Spring框架的条件化配置(Conditional Configuration)机制实现的。在Spring Boot中,自动配置类都是使用@Configuration注解标注的Java配置类,并且使用了多种条件注解来控制自动配置的条件和范围。
当指定的类在类路径中存在时,才会创建Bean或执行配置;当容器中不存在指定的Bean时,才会创建Bean或执行配置;当指定的配置属性存在时,才会创建Bean或执行配置;当应用是Web应用时,才会创建Bean或执行配置。
如果符合条件,Spring Boot就会自动创建Bean并注入到容器中,完成自动配置的过程。这样,应用就可以在不需要手动编写配置的情况下,快速地完成常见的功能配置,提高开发效率。
使用Spring Boot进行配置的注意事项
1、版本兼容性:Spring Boot的不同版本可能存在一些兼容性问题,需要注意选择相应的版本,以确保项目能够正常运行。
2、依赖管理:Spring Boot支持自动配置和依赖管理,需要注意导入的依赖是否正确,以避免出现冲突或版本不兼容等问题。
3、配置文件:Spring Boot的配置文件有多种格式,如properties、yaml、json等,需要根据项目的需要选择相应的格式,并将配置文件放置在正确的位置。
4、配置项:Spring Boot的配置项较多,需要注意配置项的正确使用和设置,如数据库连接、端口号、日志级别等。
5、自动配置:Spring Boot的自动配置能力较强,但也需要注意自动配置的正确性,如是否需要关闭自动配置、是否需要修改默认配置等。
SpringBoot Stater原理
一.SpringBoot的好处
1.依赖管理:可插拔式的组件管理,当需要某个组件时,只需要引入相关stater即可,不需要再手动引入各个jar包,避免了包遗漏、包冲突等不必要的问题。开发人员可以专注于业务开发,
2.自动配置:遵从"约定优于配置"的原则,开发人员可以在少量配置或者不配置的情况下,使用某组件。
大大降低项目搭建及组件引入的成本,开发人员可以专注于业务开发,避免繁杂的配置和大量的jar包管理。
二.实现原理
要引入某组件,无非要做两件事。一是引入jar包即pom文件引入stater;二就是编写配置文件,使用Java配置的情况下就是编写一系列@Configuration注解标注的类。那么SpringBoot是怎么来引入这些配置类的呢?就需要我们深入SpringBoot启动类一探究竟。
SpringBoot启动类上面会有@SpringBootApplication注解,这是SpringBoot中最重要的一个注解,是实现自动配置的关键。@SpringBootApplication是一个租合注解,主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三部分组成。
@SpringBootConfiguration表明该类是一个配置类。
@EnableAutoConfiguration由@AutoConfigurationPackage和@Import(*****)组成。@AutoConfigurationPackage由@Import(*****)组成,向Bean容器中注册一个AutoConfigurationPackages类,该类持有basePackage,目前我发现的作用是在MyBatis扫描注册Mapper时作为包扫描路径。
@AutoConfigurationPackage的执行流程如下图:
@Import(*****)是启动自动配置的核心。这里还有一个小插曲,一直在看AutoConfigurationImportSelector的selectImports方法,却发现没有被调用,以为是demo项目问题,去真实项目中debug断点,发现也没有进入,瞬间懵逼。。。继续跟踪发现执行流程如下:
SpringBoot启动原理分析
自动配置核心类SpringFactoriesLoader
*****文件,那么这个文件是怎么被spring加载到的呢,其实就是SpringFactoriesLoader类。
SpringFactoriesLoader是一个供Spring内部使用的通用工厂装载器,SpringFactoriesLoader里有两个方法,
在这个SpringBoot应用启动过程中,SpringFactoriesLoader做了以下几件事:
加载所有META-INF/*****中的Initializer
加载所有META-INF/*****中的Listener
加载EnvironmentPostProcessor(允许在Spring应用构建之前定制环境配置)
接下来加载Properties和YAML的PropertySourceLoader(针对SpringBoot的两种配置文件的加载器)
各种异常情况的FailureAnalyzer(异常解释器)
加载SpringBoot内部实现的各种AutoConfiguration
模板引擎TemplateAvailabilityProvider(如Freemarker、Thymeleaf、Jsp、Velocity等)
总得来说,SpringFactoriesLoader和@EnableAutoConfiguration配合起来,整体功能就是查找*****文件,加载自动配置类。
整体启动流程
在我们执行入口类的main方法之后,运行SpringApplication***n,后面new了一个SpringApplication对象,然后执行它的run方法。
初始化SpringApplication类
创建一个SpringApplication对象时,会调用它自己的initialize方法
执行核心run方法
初始化initialize方法执行完之后,会调用run方法,开始启动SpringBoot。
首先遍历执行所有通过SpringFactoriesLoader,在当前classpath下的META-INF/*****中查找所有可用的SpringApplicationRunListeners并实例化。调用它们的starting()方法,通知这些**器SpringBoot应用启动。
创建并配置当前SpringBoot应用将要使用的Environment,包括当前有效的PropertySource以及Profile。
遍历调用所有的SpringApplicationRunListeners的environmentPrepared()的方法,通知这些**器SpringBoot应用的Environment已经完成初始化。
打印SpringBoot应用的banner,SpringApplication的showBanner属性为true时,如果classpath下存在*****文件,则打印其内容,否则打印默认banner。
根据启动时设置的applicationContextClass和在initialize方法设置的webEnvironment,创建对应的applicationContext。
创建异常解析器,用在启动中发生异常的时候进行异常处理(包括记录日志、释放资源等)。
设置SpringBoot的Environment,注册Spring Bean名称的序列化器BeanNameGenerator,并设置资源加载器ResourceLoader,通过SpringFactoriesLoader加载ApplicationContextInitializer初始化器,调用initialize方法,对创建的ApplicationContext进一步初始化。
调用所有的SpringApplicationRunListeners的contextPrepared方法,通知这些Listener当前ApplicationContext已经创建完毕。
最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
调用所有的SpringApplicationRunListener的contextLoaded方法,加载准备完毕的ApplicationContext。
调用refreshContext,注册一个关闭Spring容器的钩子ShutdownHook,当程序在停止的时候释放资源(包括:销毁Bean,关闭SpringBean的创建工厂等)
注: 钩子可以在以下几种场景中被调用:
1)程序正常退出
2)使用*****()
3)终端使用Ctrl+C触发的中断
4)系统关闭
5)使用Kill pid命令杀死进程
获取当前所有ApplicationRunner和CommandLineRunner接口的实现类,执行其run方法
遍历所有的SpringApplicationRunListener的finished()方法,完成SpringBoot的启动。
[Spring boot源码解析] 2 启动流程分析
在了解 Spring Boot 的启动流程的时候,我们先看一下一个Spring Boot 应用是如何启动的,如下是一个简单的 SpringBoot 程序,非常的简洁,他是如何做到的呢,我们接下来就将一步步分解。
我们追踪 SpringApplication***n() 方法,其实最终它主要的逻辑是新建一个 SpringApplication ,然后调用他的 run 方法,如下:
我们先来看一下创建 SpringApplication 的方法:
在将Main class 设置 primarySources 后,调用了 *****() 方法,该方法是为了检查当前的应用类型,并设置给 webApplicationType 。 我们进入 deduceFromClasspath 方法 :
这里主要是通过类加载器判断是否存在 REACTIVE 相关的类信息,假如有就代表是一个 REACTIVE 的应用,假如不是就检查是否存在 Servelt 和 ConfigurableWebApplicationContext ,假如都没有,就代表应用为非 WEB 类应用,返回 NONE ,默认返回 SERVLET 类型,我们这期以我们目前最常使用的 SERVLET 类型进行讲解,所以我们在应用中引入了 spring-boot-starter-web 作为依赖:
他会包含 Spring-mvc 的依赖,所以就包含了内嵌 tomcat 中的 Servlet 和 Spring-web 中的 ConfigurableWebApplicationContext ,因此返回了 SERVLET 类型。
回到刚才创建 SpringApplication 的构建方法中,我们设置完成应用类型后,就寻找所有的 Initializer 实现类,并设置到 SpringApplication 的 Initializers 中,这里先说一下 getSpringFactoriesInstances 方法,我们知道在我们使用 SpringBoot 程序中,会经常在 META-INF/***** 目录下看到一些 EnableAutoConfiguration ,来出发 config 类注入到容器中,我们知道一般一个 config 类要想被 SpringBoot 扫描到需要使用 @CompnentScan 来扫描具体的路径,对于 jar 包来说这无疑是非常不方便的,所以 SpringBoot 提供了另外一种方式来实现,就是使用 ***** ,比如下面这个,我们从 Springboot-test 中找到的例子,这里先定义了一个ExampleAutoConfiguration,并加上了 Configuration 注解:
然后在 ***** 中定义如下:
那这种方式是怎么实现的你,这就要回到我们刚才的方法 getSpringFactoriesInstances :
我们先来看一下传入参数,这里需要注意的是 args,这个是初始化对应 type 的时候传入的构造参数,我们先看一下 SpringFactoriesLoader#loadFactoryNames 方法:
首先是会先检查缓存,假如缓存中存在就直接返回,假如没有就调用 classLoader#getResources 方法,传入 META-INF/***** ,即获取所有 jar 包下的对应文件,并封装成 UrlResource ,然后使用 PropertiesLoaderUtils 将这些信息读取成一个对一对的 properties,我们观察一下 ***** 都是按 properties 格式排版的,假如有多个就用逗号隔开,所以这里还需要将逗号的多个类分隔开来,并加到 result 中,由于 result 是一个 LinkedMultiValueMap 类型,支持多个值插入,最后放回缓存中。最终完成加载 META-INF/***** 中的配置,如下:
我们可以看一下我们找到的 initializer 有多少个:
在获取到所有的 Initializer 后接下来是调用 createSpringFactoriesInstances 方法进行初始化。
这里的 names 就是我们上面通过类加载器加载到的类名,到这里会先通过反射生成 class 对象,然后判断该类是否继承与 ApplicationContextInitializer ,最后通过发射的方式获取这个类的构造方法,并调用该构造方法,传入已经定义好的构造参数,对于 ApplicationContextInitializer 是无参的构造方法,然后初始化实例并返回,回到原来的方法,这里会先对所有的 ApplicationContextInitializer 进行排序,调用 AnnotationAwareOrderComparator#sort(instances) 方法,这里就是根据 @Order 中的顺序进行排序。
接下来是设置 ApplicationListener ,我们跟进去就会发现这里和上面获取 ApplicationContextInitializer 的方法如出一辙,最终会加载到如图的 15 个 listener (这里除了 EnableEncryptablePropertiesBeanFactoryPostProcessor 外,其他都是 SpringBoot 内部的 Listener):
在完成 SpringApplication 对象的初始化后,我们进入了他的 run 方法,这个方法几乎涵盖了 SpringBoot 生命周期的所有内容,主要分为九个步骤,每一个步骤这里都使用注解进行标识:
主要步骤如下:
第一步:获取 SpringApplicationRunListener, 然后调用他的 staring 方法启动**器。
第二步:根据 SpringApplicationRunListeners以及参数来准备环境。
第三步:创建 Spring 容器。
第四步:Spring 容器的前置处理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的后置处理器。
第七步:通知所有 listener 结束启动。
第八步:调用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我们接下来一一讲解这些内容。
我们首先看一下第一步,获取 SpringApplicationRunListener :
这里和上面获取 initializer 和 listener 的方式基本一致,都是通过 getSpringFactoriesInstances , 最终只找到一个类就是: ***** ,然后调用其构造方法并传入产生 args , 和 SpringApplication 本身:
我们先看一下构造函数,首先将我们获取到的 ApplicationListener 集合添加到initialMulticaster 中, 最后都是通过操作 SimpleApplicationEventMulticaster 来进行广播,我,他继承于 AbstractApplicationEventMulticaster ,我们先看一下他的 addApplicationListener 方法:
我们可以看出,最后是放到了 applicationListenters 这个容器中。他是 defaultRetriever 的成员属性, defaultRetriever 则是 AbstractApplicationEventMulticaster 的私有类,我们简单看一下这个类:
我们只需要看一下这里的 getApplicationListeners 方法,它主要是到 beanFactory 中检查是否存在多的 ApplicationListener 和旧的 applicationListeners 组合并返回,接着执行 listener 的 start 方法,最后也是调用了 AbstractApplicationEventMulticaster 的 multicastEvent 查找支持对应的 ApplicationEvent 类型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,这里除了会:
筛选的方法如下,都是调用了对应类型的 supportsEventType 方法 :
如图,我们可以看到对 ***** 感兴趣的有5个 Listener
环境准备的具体方法如下:
首先是调用 getOrCreateEnvironment 方法来创建 environment ,我们跟进去可以发现这里是根据我们上面设置的环境的类型来进行选择的,当前环境会创建 StandardServletEnvironment
我们先来看一下 StandardServletEnvironment 的类继承关系图,我们可以看出他是继承了 AbstractEnvironment :
他会调用子类的 customizePropertySources 方法实现,首先是 StandardServletEnvironment 的实现如下,他会添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三种 properties,当前调试环境没有配置 jndi properties,所以这里不会添加。接着调用父类的 customizePropertySources 方法,即调用到了 StandardEnvironment 。
我们看一下 StandardEnvironment#customizePropertySources 方法,与上面的三个 properties 创建不同,这两个是会进行赋值的,包括系统环境变量放入 systemEnvironment 中,jvm 先关参数放到 systemProperties 中:
这里会添加 systemEnvironment 和 systemProperties 这两个 properties,最终拿到的 properties 数量如下 4个:
在创建完成 Environment 后,接下来就到了调用 configureEnvironment 方法:
我们先看一下 configurePropertySources 方法,这里主要分两部分,首先是查询当前是否存在 defaultProperties ,假如不为空就会添加到 environment 的 propertySources 中,接着是处理命令行参数,将命令行参数作为一个 CompositePropertySource 或则 SimpleCommandLinePropertySource 添加到 environment 的 propertySources 里面,
接着调用 ConfigurationPropertySources#attach 方法,他会先去 environment 中查找 configurationProperties , 假如寻找到了,先检查 configurationProperties 和当前 environment 是否匹配,假如不相等,就先去除,最后添加 configurationProperties 并将其 sources 属性设置进去。
回到我们的 prepareEnvironment 逻辑,下一步是通知观察者,发送 ApplicationEnvironmentPreparedEvent 事件,调用的是 SpringApplicationRunListeners#environmentPrepared 方法,最终回到了 SimpleApplicationEventMulticaster#multicastEvent 方法,我们通过 debug 找到最后对这个时间感兴趣的 Listener 如下:
其主要逻辑如下:
这个方法最后加载了 PropertySourceLoader , 这里主要是两种,一个是用于 Properties 的,一个是用于 YAML 的如下:
其中 apply 方法主要是加载 defaultProperties ,假如已经存在,就进行替换,而替换的目标 PropertySource 就是 load 这里最后的一个 c***umer 函数加载出来的,这里列一下主要做的事情:
1、加载系统中设置的所有的 Profile 。
2、遍历所有的 Profile ,假如是默认的 Profile , 就将这个 Profile 加到 environment 中。
3、调用load 方法,加载配置,我们深入看一下这个方法:
他会先调用 getSearchLocati*** 方法,加载所有的需要加载的路径,最终有如下路径:
其核心方法是遍历所有的 propertySourceLoader ,也就是上面加载到两种 propertySourceLoader ,最红 loadForFileExtension 方法,加载配置文件,这里就不展开分析了,说一下主要的作用,因为每个 propertySourceLoader 都有自己可以加载的扩展名,默认扩展名有如下四个 properties, xml, yml, yaml,所以最终拿到文件名字,然后通过 - 拼接所有的真实的名字,然后加上路径一起加载。
接下来,我们分析 BackgroundPreinitializer ,这个方法在接收 ApplicationPrepareEnvironment 事件的时候真正调用了这份方法:
1、 ConversionServiceInitializer 主要负责将包括 日期,货币等一些默认的转换器注册到 formatterRegistry 中。
2、 ValidationInitializer 创建 validation 的匹配器。
***隐藏网址***
4、 JacksonInitializer 主要用于生成 xml 转换器的。
接着回到我们将的主体方法, prepareEnvironment 在调用完成 *****(environment) 方法后,调用 bindToSpringApplication(environment) 方法,将 environment 绑定到 SpirngApplication 中。
接着将 enviroment 转化为 StandardEnvironment 对象。
最后将 configurationProperties 加入到 enviroment 中, configurationProperties 其实是将 environment 中其他的 PropertySource 重新包装了一遍,并放到 environment 中,这里主要的作用是方便 PropertySourcesPropertyResolver 进行解析。
它主要是检查是否存在 ***** 配置,这个配置的主要作用是设置 javaBean 的内省模式,所谓内省就是应用程序在 Runtime 的时候能检查对象类型的能力,通常也可以称作运行时类型检查,区别于反射主要用于修改类属性,内省主要用户获取类属性。那么我们什么时候会使用到内省呢,java主要是通过内省工具 Introspector 来完成内省的工作,内省的结果通过一个 Beaninfo 对象返回,主要包括类的一些相关信息,而在 Spring中,主要是 BeanUtils#copyProperties 会使用到,Spring 对内省机制还进行了改进,有三种内省模式,如下图中红色框框的内容,默认情况下是使用 USE_ALL_BEANINFO。假如设置为true,就是改成第三中 IGNORE_ALL_BEANINFO
首先是检查 Application的类型,然后获取对应的 ApplicationContext 类,我们这里是获取到了 ***** 接着调用 *****(contextClass); 方法进行对象的初始化。
最终其实是调用了 AnnotationConfigServletWebServerApplicationContext 的默认构造方法。我们看一下这个方法做了什么事情。这里只是简单的设置了一个 reader 和一个 scanner,作用于 bean 的扫描工作。
我们再来看一下这个类的继承关系
这里获取 ExceptionReporter 的方式主要还是和之前 Listener 的方式一致,通过 getSpringFactoriesInstances 来获取所有的 SpringBootExceptionReporter 。
其主要方法执行如下:
SpringBoot启动分析
初始化initialize方法执行完之后,会调用run方法,开始启动SpringBoot。首先遍历执行所有通过SpringFactoriesLoader,在当前classpath下的META-INF/*****中查找所有可用的SpringApplicationRunListeners并实例化。
SpringBootx只区分web环境和非web环境,而在x版本中引入了Reactive环境,即响应式环境.那么现在SpringBoot支持三种环境:Servlet的web环境、Reactive的web环境以及非web环境。
首先贴一张很不错的图,SpringBoot启动结构图,图片出自SpringBoot启动流程解析。本文的分析基于SpringBoot5,非Spring的代码只有下面这个启。提供大量优秀的Web框架方便开发等等。
springboot启动流程如下:启动流程主要分为三个部分,第一部分进行、SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、**器,第二部分实现了应用具体的启动方案,包括启动流程的**模块、加载配置环境模块。
启动:每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication***n()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解。
SpringBoot应用启动原理(二) 扩展URLClassLoader实现嵌套jar加载
在上篇文章《SpringBoot应用启动原理(一) 将启动脚本嵌入jar》中介绍了SpringBoot如何将启动脚本与Runnable Jar整合为Executable Jar的原理,使得生成的jar/war文件可以直接启动
本篇将介绍SpringBoot如何扩展URLClassLoader实现嵌套jar的类(资源)加载,以启动我们的应用。
首先,从一个简单的示例开始
*****
*****
执行 gradle build 构建jar包,里面包含 应用程序 、 第三方依赖 以及SpringBoot 启动程序 ,其目录结构如下
查看*****的内容(*****文件的作用请自行GOOGLE)
可以看到,jar的启动类为 ***** ,而并不是我们的 ************* ,应用程序入口类被标记为了Start-Class
jar启动并不是通过应用程序入口类,而是通过JarLauncher代理启动。其实SpringBoot拥有3中不同的Launcher: JarLauncher 、 WarLauncher 、 PropertiesLauncher
SpringBoot使用Launcher代理启动,其最重要的一点便是可以自定义ClassLoader,以实现对jar文件内(jar in jar)或其他路径下jar、class或资源文件的加载
关于ClassLoader的更多介绍可参考 《深入理解JVM之ClassLoader》
SpringBoot抽象了Archive的概念,一个Archive可以是jar(JarFileArchive),可以是一个文件目录(ExplodedArchive),可以抽象为统一访问资源的逻辑层。
上例中,*****既为一个JarFileArchive,*****!/BOOT-INF/lib下的每一个jar包也是一个JarFileArchive
将*****解压到目录spring-boot-theory-1.***,则目录spring-boot-theory-1.***为一个ExplodedArchive
按照定义,JarLauncher可以加载内部 /BOOT-INF/lib 下的jar及 /BOOT-INF/classes 下的应用class
其实JarLauncher实现很简单
其主入口新建了JarLauncher并调用父类Launcher中的launch方法启动程序
再创建JarLauncher时,父类ExecutableArchiveLauncher找到自己所在的jar,并创建archive
在Launcher的launch方法中,通过以上archive的getNestedArchives方法找到/BOOT-INF/lib下所有jar及/BOOT-INF/classes目录所对应的archive,通过这些archives的url生成LaunchedURLClassLoader,并将其设置为线程上下文类加载器,启动应用
至此,才执行我们应用程序主入口类的main方法,所有应用程序类文件均可通过/BOOT-INF/classes加载,所有依赖的第三方jar均可通过/BOOT-INF/lib加载
在分析LaunchedURLClassLoader前,首先了解一下URLStreamHandler
java中定义了URL的概念,并实现多种URL协议(见 URL ) ****隐藏网址*** * file* * ftp* * jar* 等,结合对应的URLConnection可以灵活地获取各种协议下的资源
对于jar,每个jar都会对应一个url,如
jar:file:/data/spring-boot-theory/BOOT-INF/lib/*****!/
jar中的资源,也会对应一个url,并以’!/’分割,如
jar:file:/data/spring-boot-theory/BOOT-INF/lib/*****!/org/springframework/aop/*****
对于原始的JarFile URL,只支持一个’!/’,SpringBoot扩展了此协议,使其支持多个’!/’,以实现jar in jar的资源,如
jar:file:/data/*****!/BOOT-INF/lib/*****!/org/springframework/aop/*****
自定义URL的类格式为.Handler,在运行Launcher的launch方法时调用了 *****() 以注册自定义的 Handler
在处理如下URL时,会循环处理’!/’分隔符,从最上层出发,先构造*****的JarFile,再构造*****的JarFile,最后构造指向*****的
JarURLConnection ,通过JarURLConnection的getInputStream方法获取*****内容
从一个URL,到读取其中的内容,整个过程为
URLClassLoader可以通过原始的jar协议,加载jar中从class文件
LaunchedURLClassLoader 通过扩展的jar协议,以实现jar in jar这种情况下的class文件加载
构建war包很简单
构建出的war包,其目录机构为
*****内容为
此时,启动类变为了 ***** ,查看WarLauncher实现,其实与JarLauncher并无太大差别
差别仅在于,JarLauncher在构建LauncherURLClassLoader时,会搜索BOOT-INF/classes目录及BOOT-INF/lib目录下jar,WarLauncher在构建LauncherURLClassLoader时,则会搜索WEB-INFO/classes目录及WEB-INFO/lib和WEB-INFO/lib-provided两个目录下的jar
如此依赖,构建出的war便支持两种启动方式
PropretiesLauncher 的实现与 JarLauncher WarLauncher 的实现极为相似,通过PropretiesLauncher可以实现更为轻量的thin jar,其实现方式可自行查阅源码
本文相关文章:
springboot启动原理总结(springboot自动配置原理)
2026年3月30日 05:00
bootstrap复杂表格(bootstrap table轻松实现数据表格)
2026年5月3日 01:00
更多文章:
webstorm好用吗(webstrom和hbuilder到底哪个好哪个开发起来效率高、稳定性好)
2026年5月6日 07:20
diameter是什么意思中文(factually; diameter; immediately 这英语用谐音怎么读)
2026年5月6日 06:40
springboot启动原理总结(springboot启动过程是)
2026年5月6日 05:20
sapabap开发技术详解(这么多ABAP技术,兄弟们掌握了多少)
2026年5月6日 05:00
随机生成数字3个数(excel 给定平均值,随机生成3个数)
2026年5月6日 04:40



