1、目的:
从源码的角度分析整个springboot的启动流程、了解springboot项目在整个启动过程都干了一些什么。
因为spring项目的启动主要是分为两步,一个是bean definition的获取,一个是根据bean definition 生成bean实例。
本篇主要涉及bean definition的获取部分,也就是第一点。
其中 bean definition的来源包括
(1)框架硬编码、(2)用户定义非mapper的bean、(3)通过自动配置加载xxAutoConfiguration、(4)mapper对象 。
本篇主要讲解的是2.3两个。
2、项目结构
基本的东西都是有的,包括dao,service、model、且包含mybatis框架(代码会在后面的链接)
3、加载bean definition的方法调用链
图片地址https://www.processon.com/view/link/5e847d98e4b07b16dcdb8a46
4、从启动类开始
首先从启动类开始分析
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class},scanBasePackages = {"com.defire.**"})
@ImportResource(locations = {
"classpath:spring-jdbc-zx-lsq.xml"
})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
这就是一个普通的main方法,里面调用了 SpringApplication.run 方法、传入启动类 DemoApplication作为参数。中间一些纯属转发的方法我们就不细看了。
找到第一个重点
/**
* 运行spring 应用,创造&刷新一个新的ApplicationContext
*/
public ConfigurableApplicationContext run(String... args) {
....初始化环境
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
....省略
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
Banner printedBanner = printBanner(environment);//打印我们常见的那边spring banner
context = createApplicationContext();//初始化AnnotationConfigServletWebServerApplicationContext 包括里面工厂对象。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);//最核心的,初始化整个spring环境,包括beandefinition的扫描,后置处理器的调用,bean的实例&初始化等几乎所有spring的核心都在这个方法里面
afterRefresh(context, applicationArguments);
...省略
listeners.started(context);
callRunners(context, applicationArguments);
return context;
}
接下来跟进的重要方法是 context对象的初始化方法(createApplicationContext)和prepareContext方法 和刷新容器的方法(refreshContext方法),特别是refreshContext 方法是特别复杂。
4、createApplicationContext()方法分析
这个是为了创建context对象。因为是通过反射的方式调用的构造器,所以我们直接找到目标类,直接在构造器里面打上断点。
实例化context的时候有哪些操作呢?
初始化AnnotatedBeanDefinitionReader的时候,里面有一个比较重要的就是初始化了一些负责处理注解的后置处理器。
包括@autowire注解、@required注解等
注意这里的factory 类型是DefaultListableBeanFactory。这个的beandefinition 只是bean的中间对象,没有实例化成为bean。
5、prepareContext()方法分析
这个方法不是很重要,但还是有几点需要提下,比如
- A、DemoApplication(启动类) 的beanDefinition 收集完成,这属于第一种beandefinition收集方式,硬编码的收集方式。
- B、bean容器(registeredSingletons )中首次加入4个实例化好bean对象 0 = "autoConfigurationReport" 1 = "org.springframework.boot.context.ContextIdApplicationContextInitializer$ContextId" 2 = "springApplicationArguments" 3 = "springBootBanner" ,
- C、context 加入了两个针对beanFactory 处理的后置处理器 0 = {ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor@4409} 1 = {SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor@4410}
这些都不是重点,这里就不展开了。对于prepareContext的方法的分析就告一段落。
6、refreshContext ()/refresh()
接下来要分析的就是refreshContext ,这个终极大boss,6.1,6.2 ....都是讨论这个方法里面的细节refreshContext的方法核心就是调用AbstractApplicationContext的refresh方法。中间会有一些跳转,我们直接讲核心方法。先来段代码。后面会针对这里面的方法一一展开。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//一些准备工作,列如配置属性源,记录启动时间,环境变量准备,部分map的清理工作
prepareRefresh();
//能对xml读取,转换为bean,AbstractApplicationContext因为引用了一个beanFactory
//所有她拥有了beanFactory的所有功能。还在此基础上进一步扩展。做了哪些扩展?
//真正的初始化bean Factory ,设置一些参数,并读取xml文件,转换为bean Definition
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备bean 工厂,对beanFactory进行各种功能填充
// Prepare the bean factory for use in this context.
// 此时,spring已经完成对配置文件的解析,也就是beanFactory对bean的解析已经开始
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//保留方法 允许在上下文子类中对bean工厂进行后处理。
postProcessBeanFactory(beanFactory);
// 激活各种beanFactory 处理器,按顺序执行了
invokeBeanFactoryPostProcessors(beanFactory);
// 执行完invokeBeanFactoryPostProcessors后,beanDefinition 基本都已经找出来了 ,并且新初始了如下 8个 bean 至此已经初始化16个bean
// 注册拦截bean 创建的 bean 后置处理器 ,执行是在getBean时候
// Register bean processors that intercept bean creation.
// 注册 BeanPostProcessor 处理 bean的
registerBeanPostProcessors(beanFactory);
// 执行完 registerBeanPostProcessors()方法后,新增了9个后置处理器 至此已经初始化25个bean
// 为此上下文初始化消息源,即不同语言的消息体。国际化处理
// Initialize message source for this context.
initMessageSource();
//执行完新增1个bean 至此已经初始化26个bean
//messageSource -> {DelegatingMessageSource@5542}
//初始化应用消息(事件)广播器,并放入 ApplicationEventMulticaster bean 中
// Initialize event multicaster for this context.
// 广播器,会保存相关的监听器,在有事件发生的时候,会调用相关的监听器的方法,
// 让监听器去执行内部的监听逻辑
initApplicationEventMulticaster();
//执行完新增一个bean 至此已经初始化27个bean
//applicationEventMulticaster -> {SimpleApplicationEventMulticaster@5595}
//留给子类来初始化其他bean
// Initialize other special beans in specific context subclasses.
onRefresh();
//执行完 onRefresh()新增 29个 bean 至此已经初始化65 个bean
// 将所有监听器bean,注册到前面刚刚注册的消息广播器。
// 并将没有发出的事件一并发出去。
// Check for listener beans and register them.
registerListeners();
// 初始化剩下的,非延迟加载的 bean的初始化工作。(也就是非延迟加载的bean,会在这一步实现初始化,其他bean不会))
// ConversionService 设置(这个是干啥的?)
// 配置冻结 、非延迟加载的 bean的初始化工作
// Instantiate all remaining (non-lazy-init) singletons.
//这也是context&bean Factory的区别所在
finishBeanFactoryInitialization(beanFactory);
// 发布相关事件 Last step: publish corresponding event
// 完成刷新过程,通知生命周期处理器,LifecycleProcesser 刷新过程,
// 并同时发出ContextRefreshEvent 通知别人(通知被人干啥.
finishRefresh();
}
}
6.1:postProcessBeanFactory()
AbstractApplicationContext 的 postProcessBeanFactory 方法在父类AbstractApplicationContext是空,所以默认会寻找子类中同名方法,为啥要提到这点呢?这中情况在spring中有挺多的,父类中的方法是空,实际调用子类中同名方法,这个也叫做委派设计模式。
我们看下AbstractApplicationContext的子类都有哪些操作?
至此,AbstractApplicationContext 的 postProcessBeanFactory ()方法分析完毕。增加了一个bean的后置处理器。
6.2 invokeBeanFactoryPostProcessors()方法,
从方法名字上看就知道是调用一遍beanFactory的后置处理器。从堆栈中看到我们context对象保存了三个beanFactory的后置处理器,看下面的截图
至于这些都是干什么的,下面的分析中会包含。
6.2.1、第一次postProcessBeanDefinitionRegistry() 执行,
从三个后置处理器中拿出类型是BeanDefinitionRegistryPostProcessor的处理器,调用他们的postProcessBeanDefinitionRegistry方法。其中 ConfigurationWarningsApplicationContextInitializer 主要是对配置做一点检查工作,比如检查配置的扫描包是否正确;SharedMetadataReaderFactoryContextInitializer 主要是初始化了一个元数据读取器工厂,并将其设置到容器的configurationProcessor后置处理器中,也是为后序整个容器完全初始化做一些前期准备工作;下图就是原码截图。
第三个 ConfigFileApplicationContextInitializer并不属于BeanDefinitionRegistryPostProcessor 也就没有postProcessBeanDefinitionRegistry方法。
重点第一波执行的factory 后置处理器主要来源于beanFactoryPostProcessors属性内。接下来介绍的第二波调用的后置处理器来源不同了。虽然调用了好几次后置处理器,但是都不会重复,因为随着前一次调用结束,我们系统中又多出了一些尚未调用的后置处理器。
6.2.2、第二次postProcessBeanDefinitionRegistry() 执行,
第二次执行完的成果主要是解析出用户自己的bean definition和可以动态导入的bean definition,用户自己的比较好理解,就是业务定义的,可以动态导入的就看当前classpath路径的情况,系统给你导出。
首先从beandefinitionMap中取出类型是BeanDefinitionRegistryPostProcessor的对象。尽管整个容器目前已经包含了8个bean定义,但能属于BeanDefinitionRegistryPostProcessor类型的仅有一个internalConfigurationAnnotationProcessor(class是ConfigurationClassPostProcessor)
下面进入ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法,想必是处理bean注册的吧。果然我们看到最核心的方法是 processConfigBeanDefinitions() ,这里最核心的就是下面两条调用链了。第一条是处理业务自己定义的bean definition,第二条、spring框架智能导入一些bean。
postProcessBeanDefinitionRegistry()-->processConfigBeanDefinitions()--->parse()---->parse()--->processConfigurationClass()--->doProcessConfigurationClass() ;
postProcessBeanDefinitionRegistry()-->processConfigBeanDefinitions()--->parse()---->processDeferredImportSelectors()--->loadBeanDefinitions()
第一条链最核心的逻辑还是doProcessConfigurationClass 方法,该方法的主要操作包括
- 处理注解 @ComponentScan
- 处理 @Import 注解
- 处理@ImportResource注解
- 处理 带@Bean注解的 方法
- .....
完成上面这个doProcessConfigurationClass方法后,用户定义的带有@component注解类的基本都转换为bean Definition 了。
下面重点分析分析第二步(处理注解 @ComponentScan)
第二步:处理注解 @ComponentScan
- 1在配置类上找到ComponentScan 注解的所有属性
- 2创建一个ClassPathBeanDefinitionScanner 调用其doScan方法对路径进行扫描
- 3执行findCandidateComponents方法,主要目标是找出所有带有@component注解,mapper等接口暂时是不会扫描进来
- 4然后执行 postProcessBeanDefinition ,对扫描到的bean Definition进一步补充资料(设置一些默认值,比如懒加载、init方法等)
- 5再执行 processCommonDefinitionAnnotations处理一些公共注解的填充工作。上一步是设置默认的,如果用户有指定,则会在这一步替换为用户指定值。
第2点(创建一个ClassPathBeanDefinitionScanner 调用其doScan方法对路径进行扫描)截图
第5点(再执行 processCommonDefinitionAnnotations 处理一些公共注解的填充工作。上一步是设置默认的,如果用户有指定,则会在这一步替换为用户指定值)源码如下
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));//设置lazy属性
}
else if (abd.getMetadata() != metadata) {
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));//设置lazy-init属性
}
}
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);//设置primary属性
}
.......
}
执行 registerBeanDefinition,到这个阶段bean Definition 基本初始化完成,可以注册到容器中,等待bean的实例化阶段来使用即可。注册bean的过程其实就是将beanDefinition 放入容器内。如下图
行 registerBeanDefinition,到这个阶段bean Definition 基本初始化完成,可以注册到容器中,等待bean的实例化阶段来使用即可。注册bean的过程其实就是将beanDefinition 放入容器内。如下图
分析完第一条调用链的逻辑之后(处理用户自己的bean definition)我们接下来分析第二条获取 bean definition的调用链。
先大体浏览下第二条调用链的方法调用栈。
第二条链主要是从几个配置文件中读取一些相关配置类,进行一些过滤之后加载到系统中。我们先找到加载配置文件的方法,selectImport方法。下面是源码
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);//拿到配置类的元数据
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);//去除重复的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);//去除用户指定的不包含的配置类
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);//按照条件进一步过滤
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
先从这个配置文件中读取全部配置信息。我们看下配置信息都是什么样的。
可以看到都是一堆的AutoConfiguration类,这个就是可以理解为一个功能的配置类,比如http消息转换的配置类,在他里面会引入一些其他类。 processDeferredImportSelectors方法执行完成之后,这些bean definition还没有实例化。真正实例化的时候是后面的 this.reader.loadBeanDefinitions(configClasses);
至此
本篇主要目标 对bean definition的获取部分的理解已经基本讲完,其中bean definition的来源包括
(1)框架硬编码、(2)用户定义非mapper的bean、(3)通过自动配置加载xxAutoConfiguration、(4)mapper对象 。
这里主要讲解的是2.3两个。