- 1、项目的初始化启动
- 2、在以上的代码中,启动过程中的重要步骤共分为六步
- 1、第一步:获取并启动监听器
- 2、第二步:构造应用上下文环境
- 3、第三步:初始化应用上下文
- 4、第四步:刷新应用上下文前的准备阶段
- 1、进入prepareContext()
- 2、prepareContext的源码如下
- 3、获取到启动类
- 4、看如何通过load加载,将Bean定义注册到IOC 容器当中的
- 5、先获取第一个参数
- 5.1、强转为BeanDefinitionRegistry
- 5.2、创建createBeanDefinitionLoader对象
- 5.3、创建bean定义加载器对象
- 5.4、加入BeanDefinitionLoader构造方法
- 6、继续调用load方法
- 6.1、继续调用load方法
- 6.2、继续调用load方法
- 6.3、这个load方法isComponent判断是否有该注解
- 6.4、将 启动类的 BeanDefinition注册进 beanDefinitionMap
- 6.5 调用注册Bean
- 6.6、继续进入
- 6.7、调用registerBeanDefinition进行注册
- 6.8、继续调用registerBeanDefinition
- 6.9、最终将其存到容器的Map集合中
- 7、得到的注解
- 8、在beanDefinitionMap中存入的是实例对象
- 5、第五步: 刷新应用上下文(IOC容器的初始化过程)
- 1、进入refreshContext方法
- 2、进入refresh(context)方法
- 3、继续进入refresh方法
- 4、重点—捋一捋进到重要的去处理Bean工厂后置处理器前的思路
- 5、进入BeanFactory的后置处理器
- 6、继续调用重载方法
- 7、调用Bean定义的注册器
- 8、选择解析注解所标注的类
- 9、继续调用processConfigBeanDefinitions
- 10、生成解析器并对相应的类进行解析
- 11、继续调用parse方法
- 12、继续调用processConfigurationClass
- 13、调用doProcessConfigurationClass方法
- 14、进入重要解析的方法
- 14.1、ComponentScans方法的解析
- 14.2、为什么会扫描主类所在的包及子包的原因
- 14.3、进入doScan方法
- 14.4、
- 14.4.1、
- 14.4.2、拼接路径获取资源
- 14.4.3、通过isCandidateComponent判断是否含有@Component注解
- 14.4.4、然后封装成Bean定义
- 15、注册Bean定义
- 15.1、继续调用
- 15.2、继续调用
- 15.3、进入具体的实现类
- 15.4、将controller的bean定义都存入容器中
- 16、继续往下走
- 17、如果通过@Import注解或SPI方式如何处理
- 17.1、进入
- 17.2、第一次拿到主类上面标的注解
- 17.3、递归调用
- 17.4、第二次拿组合注解(三个)
- 17.5、解析出标有@import注解的类
- 18、还是回到11点的最后一行,继续进入
- 18.1、进入processGroupImports方法
- 18.2、进入getImport方法
- 18.3、调用process方法
- 18.3.1、选择它的实现类
- 18.3.2、获取配置文件去解析自动配置类
- 18.4、回到18.3方法,继续走this.group.selectImports()
- 18.4.1、选择实现的类
- 18.4.2、选择SelectImport方法
- 18.4.3、最终得到的结果如下
- 19、一路返回回到第10点那个方法中。
- 20、走使自动配置类生效赶关键代码
- 21、看看效果
- 5、第六步: 刷新应用上下文后的扩展接口
上节已经查看了SpringApplication 类的实例化过程,这一节总结SpringBoot启动流程最重要的 部分run方法。通过run方法梳理出SpringBoot启动的流程, 经过深入分析后,就发现SpringBoot也就是给Spring包了一层皮,就是事先准备好Spring所需要 的环境及一些基础 。
1、项目的初始化启动
分析完(newSpringApplication(primarySources)).run(args)源码前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码具体如下:
1、进入构建SpringApplication后的run方法
2、进入run方法后的源码
2、在以上的代码中,启动过程中的重要步骤共分为六步
第一步:获取并启动监听器
第二步:构造应用上下文环境
第三步:初始化应用上下文
第四步:刷新应用上下文前的准备阶段
第五步:刷新应用上下文
第六步:刷新应用上下文后的扩展接口
SpringBoot的启动流程分析,就根据这6大步骤进行详细解读。最总要的是第四,五 步。要会着重的分析。
1、第一步:获取并启动监听器
事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象 处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。
1、进入获取监听器的方法
2、进入到监听器的源码如下
3、看如何获取配置文件并转为工厂实例
4、根据哪个key去获取实例呢?
5、看如何在loadSpringFactories方法获取的
6、debug启动,看获取到是哪个监听器
当代码停在断点处,点击右键,运行 Evaluate
7、开启监听事件
2、第二步:构造应用上下文环境
应用上下文环境包括什么呢?包括计算机的环境,Java环境,Spring的运行环境,Spring项目的配 置(在SpringBoot中就是那个熟悉的application.properties/yml)等等
1、进入prepareEnvironment方法
2、构建上下文环境的源码
方法中主要完成的工作,
首先是创建并按照相应的应用类型配置相应的环境,
然后根据用户的配置,配置系统环境,
然后启动监听器,并加载系统配置文件。
3、进入getOrCreateEnvironment方法
3.1、根据应用类型进行实例化
(系统环境类型的获取,在SpringApplication构建和实例化哪里就有详解)
4、看如何配置 environment系统环境
4.1、设置项目环境的配置文件
(就是启动多个实例用的)
4.2、获取配置环境文件
在configurePropertySources(environment, args);中将args封装成了 SimpleCommandLinePropertySource并加入到了environment中。
configureProfiles(environment, args);根据启动参数激活了相应的配置文件。
5、进入environmentPrepared方法
5.1、继续调用重载方法
5.2、点进去
5.3、进入multicastEvent方法
5.4、继续进入multicastEvent方法
5.5、进入Spring中的multicastEvent源码
5.6、选中,右键,看其返还结果
5.7、debug进来后
5.8、进入invokeListener方法
3、第三步:初始化应用上下文
在SpringBoot工程中,应用类型分为三种,如下代码所示。
1、先创建一个空的context
2、看其结构
3、进入createApplicationContext方法看如何创建上下文
4、创建上下文的源码如下
5、具体的三种对象全类名如下
6、选中SERVLET这一项
7、选中类然后快捷键看其类结构
应用类型是 servlet ,对应的类AnnotationConfigServletWebServerApplicationContext ,全局搜索
应用上下文可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一 些高级功能
应用上下文对IoC容器是持有的关系。他的一个属性beanFactory就是IoC容器 (DefaultListableBeanFactory)。所以他们之间是持有,和扩展的关系
IoC容器作为一个属性存在上下文当中
8、它去具体的实例化操作
9、通过反射具体实例化操作
在createApplicationContext()方法中的, BeanUtils.instantiateClass(contextClass) 这个方法中,不但初始化了 AnnotationConfigServletWebServerApplicationContext类,也就是我们的上下文context,
同样 也触发了GenericApplicationContext类的构造函数,从而IoC容器也创建了。
10、调用构造函数
11、调用无参构造
12、一路进入
13、最后返回的结果
如上图所示,context就是我们熟悉的上下文(也有人称之为容器,都可以,看个人爱好和理 解),beanFactory就是我们所说的IoC容器的真实面孔了。细细感受下上下文和容器的联系和区 别,对于我们理解源码有很大的帮助。在我们学习过程中,我们也是将上下文和容器严格区分开来 的
4、第四步:刷新应用上下文前的准备阶段
1、在第三步得到的IOC 的对象集合,然后这里要完成对集合进行属性设置。
2、还要完成一个Bean对象的创建。
接下来刷新应用上下文前的准备阶段。也就是prepareContext()方法。
1、进入prepareContext()
2、prepareContext的源码如下
这个方法里面对很多都是对属性进行赋值的。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器环境
context.setEnvironment(environment);
//执行容器后置处理(里面的setConversionService设置转码格式)
postProcessApplicationContext(context);
//执行容器中的 ApplicationContextInitializer 包括spring.factories和通过三种方式自定义的
applyInitializers(context);
//向各个监听器发送容器已经准备好的事件(监听SpringBoot启动的各个阶段)
// 按照key找到对应的value,如果有要执行的就去执行
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// ------上面是进行属性设置,下面就开始创建Bean对象----------
// /将main函数中的args参数封装成单例Bean,注册进容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
//将 printedBanner 也封装成单例,注册进容器
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory) // 如果是这个类,就允许bean定义的信息被覆盖
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources 在getAllSources()中拿到了启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载我们的启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
//发布容器已加载事件
listeners.contextLoaded(context);
}
3、获取到启动类
4、看如何通过load加载,将Bean定义注册到IOC 容器当中的
5、先获取第一个参数
第一个参数是去获取bean定义注册器
5.1、强转为BeanDefinitionRegistry
创建的上下文强转为BeanDefinitionRegistry,他们之间是有继承关系的。
BeanDefinitionRegistry定义了很重要的方法registerBeanDefinition(),该方法将BeanDefinition 注册进DefaultListableBeanFactory容器的beanDefinitionMap中
因为:Spring容器在启动的时候,会将类解析为Spring内部的BeanDefinition结构。并将BeanDefinition结构存储到IOC 容器中 map中,这个容器就是DefaultListableBeanFactory
可以一路进去BeanDefinitionRegistry
所以上面的 才能进行强转。
5.2、创建createBeanDefinitionLoader对象
5.3、创建bean定义加载器对象
5.4、加入BeanDefinitionLoader构造方法
主要是进行赋值
6、继续调用load方法
回到第五步哪里,继续调用下面的load的方法
6.1、继续调用load方法
6.2、继续调用load方法
6.3、这个load方法isComponent判断是否有该注解
@SpringBootApplication注解是组合注解,一路进入,最后发现是存在Component对象的
6.4、将 启动类的 BeanDefinition注册进 beanDefinitionMap
6.5 调用注册Bean
6.6、继续进入
6.7、调用registerBeanDefinition进行注册
6.8、继续调用registerBeanDefinition
6.9、最终将其存到容器的Map集合中
7、得到的注解
8、在beanDefinitionMap中存入的是实例对象
context—-》beanFactory—-》beanDefinitionMap中可以看到到存入了很多Bean
其他他们在进行prepareContext这一步操作的时候,就会调用bean定义,存入到容器中
(整体属性了Bean的注册 )
bean从配置文件获取到工厂全限定类名,那么拿到这些类名,又是在哪里进行实例化,存在map中?
得细细回顾
上面整个第4步就是找到主类,然后将主类转为bean对象,存到容器中。
5、第五步: 刷新应用上下文(IOC容器的初始化过程)
(接下来很多都是Spring的逻辑,有空将SpringBoot源码和Spring 源码整合在一起)
上面已经对容器context进行了赋值
首先要知道到IoC容器的初始化过程,主要分下面三步:
1、 BeanDefinition的Resource定位
2、 BeanDefinition的载入
3、 向IoC容器注册BeanDefinition
接下来主要从refresh()方法中总结IoC容器的初始化过程。
从run方法的,refreshContext()方法一路跟下去,
最终来到AbstractApplicationContext类的 refresh()方法
注意:
什么是自动配置,是在Spring在启动的过程中,自动完成一些bean对象的自动配置,不用手动处理,
完整的解答应该是从启动类开始将,1、先如何找到该启动类(主类),又是如何进行实例化,然后又是如何对主类上面所标注的注解进行解析,又是如何对Import注解里面的组件进行调用,又是如何对componentscan注解进行解析及扫描获取到扫描的路径的?又是如何根据扫描路径下的main类生成实例存到容器的。把这些问题都解答出来了,就比较完整了。
1、进入refreshContext方法
2、进入refresh(context)方法
3、继续进入refresh方法
4、重点—捋一捋进到重要的去处理Bean工厂后置处理器前的思路
接下来就是交由Spring的源码来进行处理,(介绍一下完成自动配置)
接下来是重点介绍,invokeBeanFactoryPostProcessors方法,这样才能清楚main函数上面的注解是如何去解析的,以及组合函数里面的注解及注入的类又是如何去解析的!如何拿到ComponentScan注解级解析,然后拿到对应类如controller这样的类上面的RestController注解,及如何去解析的等等。
IoC容器的初始化过程包括三个步骤,(就是完成Bean对象的创建,并把bean对象存到容器中)
在invokeBeanFactoryPostProcessors()方法中完成了IoC容 器初始化过程就要完成下面三个步骤。
1、第一步:Resource定位
(Resource就是资源,SpringBoot的资源就是都有哪些类需要生成对象存入IOC容器中)
在SpringBoot的启动类(main函数)上面标注了SpringBootApplication(组合注解),点进去发现含有ComponentScan注解,这个注解的作用就是注解扫描的,它默认的扫描路径是该注解所在的包及其子包,那么main函数所在的包及其子包的类中,上面的那些注解如 @RestController 、@Component 等标注的类都需要生成Bean对象,存到IOC 容器中,所以我们都知道他的包扫描是从主类所在的包开始扫描的,
从main函数的run方法进去,在prepareContext() 方法中,会先将主类解析成BeanDefinition,然后在refresh()方法的 invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路径。这样就完成了定位的过程。
其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。(因为它们也要去生成实例注入到容器中)
还有 一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是 @Import注解,它要导入一个类,那么这个类也是要生成实例存到对象中的,所以在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的 定位加载。
常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现 的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了)
(上面主要是找到对应的类,下面开始把对应的类解析成BeanDefinition结构)
2、第二步:BeanDefinition的载入
所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成: classpath:com/slin/edu//.class这样的形式,然后一个叫做 xPathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后 遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。就将其解析成BeanDefinition结构,**大致过 程就是这样的了。
3、第三个过程:注册BeanDefinition
这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入 过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容 器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持 有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。
5、进入BeanFactory的后置处理器
6、继续调用重载方法
7、调用Bean定义的注册器
8、选择解析注解所标注的类
9、继续调用processConfigBeanDefinitions
10、生成解析器并对相应的类进行解析
11、继续调用parse方法
// 如果是SpringBoot项目进来的,bd其实就是前面主类封装成的 AnnotatedGenericBeanDefinition(AnnotatedBeanDefinition接口的实现类)
12、继续调用processConfigurationClass
13、调用doProcessConfigurationClass方法
14、进入重要解析的方法
14.1、ComponentScans方法的解析
这个截图是很重要,可以解析为什么主函数下的ComponentScans如何获取到,及获取到其主程序所在的包及其子包,及子包的类上面的注解。及被想@RestController这些注解标注后如何生成实例标到容器中。
14.2、为什么会扫描主类所在的包及子包的原因
到这里也就是拿到了第4点的第一步。拿到主类所在的包。
14.3、进入doScan方法
14.4、
14.4.1、
14.4.2、拼接路径获取资源
14.4.3、通过isCandidateComponent判断是否含有@Component注解
而且不是被排除的类,
14.4.4、然后封装成Bean定义
到此,也就完成了第4点的,第二步,根据包路径,拼接扫描路径,并将添加@Component注解的类都进行解析,分成成Bean定义。
然后一路往下走。又回到了14.4的那个方法里面。继续往下走
15、注册Bean定义
15.1、继续调用
15.2、继续调用
15.3、进入具体的实现类
15.4、将controller的bean定义都存入容器中
到此,也就是完成的第四点的三个步骤了。然后一路返回。
一路返回,还是回到 14.1 那个步骤的方法中。
16、继续往下走
所以到此,也就很清晰的了解到,如何SpringBoot启动的过程中,如何对自己所标注的那些注解进行扫描,解析,及封装成Bean定义存到容器中,(上面是针对的是主类所在的包的。)
17、如果通过@Import注解或SPI方式如何处理
一路往下,除了上面通过@ComponentScan注解扫描的方法,还会有对@Import引入的类,及SPI等形式进行解析自动配置类。那么这些都是如何注入到容器中的呢?
17.1、进入
17.2、第一次拿到主类上面标的注解
17.3、递归调用
17.4、第二次拿组合注解(三个)
遍历,取出每一个注解,然后再进行一层层的解析。
17.5、解析出标有@import注解的类
然后就是一路的返回。回到
18、还是回到11点的最后一行,继续进入
这个方法就是SpringBoot进行自动配置的入口
它主要是去调用组合注解里面的AutoXXX注解的调用。
18.1、进入processGroupImports方法
18.2、进入getImport方法
18.3、调用process方法
18.3.1、选择它的实现类
18.3.2、获取配置文件去解析自动配置类
18.4、回到18.3方法,继续走this.group.selectImports()
18.4.1、选择实现的类
18.4.2、选择SelectImport方法
18.4.3、最终得到的结果如下
然后一路返回。
但是到此,也只是获取到自动配置类的工厂的全路径,但还没有将他们注入到容器中的,接下来重点是将
自动配置类的工厂装成Bean定义,并存到容器中
19、一路返回回到第10点那个方法中。
20、走使自动配置类生效赶关键代码
21、看看效果
5、第六步: 刷新应用上下文后的扩展接口
扩展接口,设计模式中的模板方法,默认为空实现。
如果有自定义需求,可以重写该方法。
比如打 印一些启动结束log,或者一些其它后置处理
整个SpringBoot启动过程中,完成了对上下文的创建,创建的同时,对IOC 容器也进行了创建。
将核心的对象,放入到容器中
从上述源码可以看出,项目初始化启动过程大致包括以下部分:
第一步:获取并启动监听器
this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。
第二步:根据SpringApplicationRunListeners以及参数来准备环境
this.prepareEnvironment(listeners,applicationArguments)方法主要用于对项目运行环境进行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行
环境
第三步:创建Spring容器
根据webApplicationType进行判断,确定容器类型,如果该类型为SERVLET类型,会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组
装配置,并刷新配置
第四步:Spring容器前置处理
这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础
第五步:刷新容器
开启刷新spring容器,通过refresh方法对整个IOc容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭
第六步:Spring容器后置处理
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
第七步:发出结束执行的事件
获取EventpublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。
第八步:执行Runners
用于调用项目中自定义的执行器xxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,SpringBoot提供的执行器接口有ApplicationRunner和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后SpringBoot项目启动后会立即执行这些特定程序
下面,通过一个SpringBoot执行流程图,让大家更清晰的知道SpringBoot的整体执行流程和主要启动阶段: