目录
- 一、演示代码
- 二、功能介绍
- 三、代码分析
- 1、从主启动类中调用run()方法出发
- 2、看一下SpringApplication的构造方法在干什么?
- 3、看下run()方法的主要流程代码
- 4、run():启动计时器计算springboot启动时间
- 5、run():创建DefaultBootstrapContext对象,并执行bootstrapRegistryInitializers集合中元素的初始化方法
- 6、run():设置无头服务
- 7、run():获取Spring启动运行的监听器,并发布开始启动监听事件
- 8、run():将main()方法中传递过来的参数封装成ApplicationArguments对象
- 9、run():准备environment对象,加载本地配置文件信息
- 10、run():打印Banner图
- 11、run():创建ApplicationContext对象
- 12、run():准备上下文
- 13、run():将bean交给Spring进行管理
- 13.1、先找到重要代码所在的位置
- 13.2、获取bean工厂
- 13.3、往bean工厂中填充 Bean后处理器、单例对象(环境类型)
- 13.4、找到processConfigBeanDefinitions()解析方法执行的地方
- 13.5、找到需要被Spring管理的类,即invokeBeanFactoryPostProcessors()方法(备菜过程)
- 13.5.1、分析添加@SpringBootApplication注解的主启动类(入口)
- 13.5.1.1、处理携带@Component注解的类的内部类(不执行)
- 13.5.1.2、处理@PropertySource注解(不执行)
- 13.5.1.3、处理@ComponentScan注解(一般情况下放在主启动类上,然后扫描到其他类,之后通过递归方式实现其他类的处理)
- 13.5.1.4、处理@Import注解
- 13.5.1.5、处理@ImportResource注解(不执行)
- 13.5.1.6、处理@Bean注解(不执行)
- 13.5.1.7、处理接口中默认方法上添加@Bean注解的情况(不执行)
- 13.5.1.8、处理父类(不执行)
- 13.5.2、处理添加@Bean注解的类
- 13.5.3、处理添加@Import注解的类
- 13.5.4、处理添加@ImportResource注解的类
- 13.5.5、处理添加@PropertySource注解的类
- 13.5.6、处理不加注解的父类中添加@Bean注解的情况
- 13.5.7、处理实现的接口中默认方法上添加@Bean注解的情况
- 13.5.8、其他几种情况
- 13.5.9、处理@Bean注解、@Import、@ImportResource注解而产生的bean信息,从而将它们加入到DefaultListableBeanFactory对象的beanDefinitionMap、beanDefinitionNames中
- 13.6、注册Bean后处理器
- 13.7、创建并启动Tomcat服务器,即onRefresh()方法
- 13.8、将bean交给Spring进行管理,即finishBeanFactoryInitialization方法(做菜过程)
- 13.8.1、找到getBean方法和doGetBean方法
- 13.8.2、简单分析一下`getSingleton()`方法和`@DependsOn`注解的作用
- 13.8.2.1、优先讲解一下`getSingleton()`方法在解决循环依赖和动态代理时起到的作用
- 13.8.2.2、进入`getSingleton()`方法内部探究一下
- 13.8.2.3、讲一下`@DependsOn`注解的作用
- 13.8.3、通过截图方式来分析不存在循环依赖的情况
- 13.8.3.1、背景
- 13.8.3.2、将对象A放入一级缓存中(不想写那么多了,写一点点吧~)
- 13.8.4、通过截图方式分析存在循环依赖的情况
- 13.8.4.1、背景
- 13.8.4.2、实在不想在写了,确实太多了
- 13.8.5、Bean实例化和初始化
- 13.8.5、Bean销毁
- 13.8.6、Bean的生命周期
一、演示代码
通过百度网盘分享的文件:SpringBoot启动流程代码.zip
链接:https://pan.baidu.com/s/1fnt5fWSBLY7UfFPoZFyZpQ?pwd=gyg1
提取码:gyg1
二、功能介绍
- config包:
- TestBeanLifeCycle类:测试Bean生命周期
- TestConfiguration类:测试@Configuration
- TestConfigurationProperties类:测试@ConfigurationProperties
- TestDependsOn类:测试@DependsOn
- TestImport类:测试@Import
- TestImportResource类:测试@ImportResource
- TestProfile类:测试@Profile
- TestPropertySource类:测试@PropertySource
- TestSuperClass类:测试普通父类中包含@Bean注解的方法
- controller包:
- TestController类:测试@Controller
- service包>impl包:
- TestServiceAImpl类:测试@Service、@Transactional
- CodeStudyApplication类:测试@SpringBootApplication
三、代码分析
1、从主启动类中调用run()方法出发
按着Ctrl
按键点击run()
方法:
继续按着Ctrl
按键点击run()
方法:
2、看一下SpringApplication的构造方法在干什么?
继续按着Ctrl
按键点击SpringApplication()
构造方法:
继续按着Ctrl
按键点击this()
构造方法:
我们逐步来分析SpringApplication构造方法中具体在什么事情:
-
this.resourceLoader = resourceLoader
:resourceLoader是null,所以赋值也是null -
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
:将主启动类放到primarySources
集合中,后续会使用到,知道有这么回事就行,看到时再提一下 -
this.webApplicationType = WebApplicationType.deduceFromClasspath();
:获取当前的web环境,即使用servlet
还是reactive
,我们使用servlet
环境
-
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
:往bootstrapRegistryInitializers
中放置了BootstrapRegistryInitializer
类型的初始化器 -
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
:往initializers
中放置了ApplicationContextInitializer
类型的初始化器 -
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
:往listeners
中放置了ApplicationListener
类型的监听器;大家肯定注意到了,在上面两个设置初始化器的方法,以及设置监听器的方法中,也都涉及到了getSpringFactoriesInstances(XXX)
,我们来解释一下该方法的作用,按着Ctrl
按键点进getSpringFactoriesInstances
方法
在按着Ctrl
按键点进getSpringFactoriesInstances
方法
我们来看SpringFactoriesLoader.loadFactoryNames()
方法,按着Ctrl
按键点进loadFactoryNames
方法
loadFactoryNames()
方法目的是从所有的META-INF/spring.factories
文件获取相关信息,然后组成一个Map集合,其中key是接口
或者抽象类
,而value是具体的实现类,大家来看下方法体中具体做了啥
我们往回看看loadFactoryNames()
方法,其中loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList())
作用就是从上面Map中根据传过来的接口/抽象类
全名称获取实现类列表
我们往回看看createSpringFactoriesInstances()
方法,当loadFactoryNames()
方法执行完毕后,我们可以获得对应接口/抽象类
的实现类全路径列表,那就该执行createSpringFactoriesInstances()
方法了,这个方法就是通过反射
方式依照实现类全路径列表来创建对象,这样我们就得到了所有的初始化器或者监听器
如果我们想添加一些相关类型的初始化器或者监听器,我们也可以在META-INF/spring.factories
文件中添加相关内容,然后就会被Spring容器自动识别
拓展: 众所周知,@SpringBootApplication
注解中的@ComponentScan
注解只能扫描当前包以及子包下的注解,如果我们想开发一个项目,然后别人通过maven依赖引入之后就可以通过AOP注入我们编写的类,那我们的类需要被spring扫描到。我们可以在自己项目的resources文件夹下创建META-INF
目录,然后在该目录下创建spring.factories
文件,然后等号前面写EnableAutoConfiguration
接口全路径,后面写需要被Spring管理的类的全路径,如下所示;至于这么做就可以让类被Spring管理的原因,那和主启动类上的@SpringBootApplication
注解》@EnableAutoConfiguration
注解》@Import(AutoConfigurationImportSelector.class)
注解有关,我们进入AutoConfigurationImportSelector
类,找到里面的selectImports
方法,在看方法体内的getAutoConfigurationEntry
方法,该方法可以找到spring.factories
文件中所有实现了EnableAutoConfiguration
接口的实现类,然后交给Spring进行管理;当然这个也涉及到使用主启动类上@ComponentScan
注解扫描加有@Import
注解的主启动类,从而将对象交给Spring进行管理
-
this.mainApplicationClass = deduceMainApplicationClass();
:将当前主启动类的全名称赋值给mainApplicationClass
属性
3、看下run()方法的主要流程代码
SpringApplication类:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
4、run():启动计时器计算springboot启动时间
5、run():创建DefaultBootstrapContext对象,并执行bootstrapRegistryInitializers集合中元素的初始化方法
6、run():设置无头服务
这里将java.awt.headless设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能。
做了这样的操作后,SpringBoot想干什么呢?其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置.
7、run():获取Spring启动运行的监听器,并发布开始启动监听事件
8、run():将main()方法中传递过来的参数封装成ApplicationArguments对象
9、run():准备environment对象,加载本地配置文件信息
我其实也没怎么看懂里面的代码,但是我看到了配置文件应该放置的位置是哪些,跟着我一步步往下走吧~
按着Ctrl
点击prepareEnvironment()
方法
按着Ctrl
点击environmentPrepared()
方法
其中doWithListeners()
方法中参数2
是一个消费型函数式接口,在该方法中会回调这个函数式接口,所以最终会执行environmentPrepared()
方法,然后按着Ctrl + Alt
点击environmentPrepared()
方法:
按着Ctrl
点击multicastEvent()
方法
按着Ctrl
点击multicastEvent()
方法
按着Ctrl
点击invokeListener()
方法
按着Ctrl
点击doInvokeListener()
方法
按着Ctrl + Alt
点击onApplicationEvent()
方法,点击EnvironmentPostProcessorApplicationListener
类:
按着Ctrl
点击onApplicationEnvironmentPreparedEvent()
方法
按着Ctrl + Alt
点击postProcessEnvironment()
方法,点击ConfigDataEnvironmentPostProcessor
类:
按着Ctrl
点击postProcessEnvironment()
方法
按着Ctrl
点击getConfigDataEnvironment()
方法
点击ConfigDataEnvironment
类:
在该类中,大家往上看下static静态代码块,可以看到配置文件可以放置的地方,分别是resources/config/
、resource/
、项目目录下/config/
、项目目录下/
、项目目录下/config目录下任意一个子目录/
我们可以把代码断点打到printBanner()
处
然后看下最终结果吧,打开environment
看一下:
-
activeProfiles
:对应配置文件中spring.profiles.active
的值,即:
-
defaultProfiles
:默认就是default
-
propertySources
:记录了配置文件的内容
最后一个记录了application.yml
中的配置信息
倒数第2个记录了激活的配置文件中的信息:
我们再看下environment
中的属性propertySources
的其他信息:
10、run():打印Banner图
按着Ctrl
点击printBanner()
方法
按着Ctrl
点击print()
方法
按着Ctrl
点击getBanner()
方法
可以看到下面有获取图片Banner图、文字Banner图、默认Banner图的入口:
我们先来看下图片Banner图的获取方式:
我们先来看下文字Banner图的获取方式
我们再来看下默认Banner图的获取方式:
通过上述方式,我们可以获取到Banner
对象了,按着Ctrl + Alt
点击printBanner()
方法,我们就看下默认Banner图的实现方式吧,我们点击SpringBootBanner
类:
可以看到对应代码和对应打印效果:
11、run():创建ApplicationContext对象
对于this.webApplicationType
,我们在SpringBootApplication构造器中解释过,将使用servlet
环境;而SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())
是从spring.factories
文件中获取ApplicationContextFactory
接口的实现类,我们将得到2个,分别是:
由于是for循环,所以都会处理到,按着Ctrl + Alt
点进AnnotationConfigReactiveWebServerApplicationContext
中:
由于当前是servlet环境,所以context返回值是null
;接着按着Ctrl + Alt
点进AnnotationConfigServletWebServerApplicationContext
中,所以此时AnnotationConfigServletWebServerApplicationContext
对象会被返回,然后赋值给context
上下文
因此context = createApplicationContext();
的最终结果是AnnotationConfigServletWebServerApplicationContext
对象赋值给了context
上下文对象
12、run():准备上下文
再看一下方法体中的细节:
比较有用的是最后一部分代码,也就是Load the sources
,我们之前在2、看一下SpringApplication的构造方法在干什么?
中提到过primarySources
的赋值结果是主类信息,即CodeStudyApplication
主类信息;此时按着Ctrl
点击getAllSources()
方法:
可以看到allSources中会被放入CodeStudyApplication
主类信息:
按着Ctrl
点击load()
方法:
按着Ctrl
点击load()
方法:
按着Ctrl
点击load()
方法:
按着Ctrl
点击load()
方法:
按着Ctrl
点击register()
方法:
按着Ctrl
点击registerBean()
方法:
按着Ctrl
点击doRegisterBean
方法:
按着Ctrl + Alt
点击registerBeanDefinition
方法,选择GenericApplicationContext
类:
按着Ctrl
点击registerBeanDefinition
方法:
将主启动类的信息放到beanDefinitionMap
、beanDefinitionName
中
我们下面也会用到beanDefinitionName
来从主启动类出发,进而扫描到所有的Bean类,到时候我们再提下这一章节
13、run():将bean交给Spring进行管理
13.1、先找到重要代码所在的位置
按着Ctrl
点击refreshContext()
方法:
按着Ctrl
点击refresh()
方法:
按着Ctrl + Alt
点击refresh()
方法,然后点击ServletWebServerApplicationContext
类:
按着Ctrl
点击refresh()
方法,先来看下方法的全貌:
详细代码如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
13.2、获取bean工厂
prepareRefresh()
方法也没做什么事情,不过给earlyApplicationEvents
赋值为new LinkedHashSet<>()
还是挺棒的,用来记录由于循环依赖触发后被提前处理的bean信息,其他就没啥东西了,所以这个方法不在讲解。
下面直接说下obtainFreshBeanFactory()
,目的是获得DefaultListableBeanFactory
对象,该对象主要是和bean打交道用的,即 bean工厂
13.3、往bean工厂中填充 Bean后处理器、单例对象(环境类型)
关于后处理器,我没那么擅长,我们来看下那几个单例对象吧,后续我们有需要的话可以直接注入相关单例对象(通过@Resource
注入后即可使用),然后直接取出配置文件或者环境信息中的内容使用
environment(即 ENVIRONMENT_BEAN_NAME 常量值):
systemProperties(即 SYSTEM_PROPERTIES_BEAN_NAME 常量值):
13.4、找到processConfigBeanDefinitions()解析方法执行的地方
首先看下postProcessBeanFactory(beanFactory)
方法,它啥都没做
两个if
判断都不符合要求,所以什么都没做
13.5、找到需要被Spring管理的类,即invokeBeanFactoryPostProcessors()方法(备菜过程)
注意: 当目录13.5
执行完成的时候,所有的Bean
信息(注意:是所有的)都会添加到DefaultListableBeanFactory
对象的beanDefinitionMap
和beanDefinitionName
属性中,等待往Spring
的Bean
缓存中添加
现在我们出发寻找processConfigBeanDefinitions()
方法。
我们回到run()方法的主要代码体,然后按着Ctrl
点击invokeBeanFactoryPostProcessors()
方法:
按着Ctrl
点击invokeBeanFactoryPostProcessors
方法:
先看第1个if
方法:
我认为最有用的是BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
这一句,也就是将DefaultlistableBeanFactory
赋值到registry
在看第2个if
方法:
按着Ctrl
点击invokeBeanDefinitionRegistryPostProcessors()
方法:
按着Ctrl + Alt
点击postProcessBeanDefinitionRegistry()
方法,选择ConfigurationClassPostProcessor
类(在上面第2
张图可以看到postProcessors
中只有1个对象,也就是ConfigurationClassPostProcessor
对象):
按着Ctrl
点击processConfigBeanDefinitions()
方法:
通过registry.getBeanDefinitionNames()
得到的包含主启动类信息,这个内容我们在12、run():准备上下文
中已经说明了主启动信息是如何放入beanDifinitionNames
中的;当处理之后configCandidates
中存储的是主启动类信息,我们下面会从主启动类出发,进而扫描后找到所有的Bean类
依然在processConfigBeanDefinitions()
方法中,我们往下滑动一下代码,找到parse()
方法,按着Ctrl
点击parse()
方法:
13.5.1、分析添加@SpringBootApplication注解的主启动类(入口)
按着Ctrl
点击parse()
方法,现在bd
对象就是主启动类的信息对象:
按着Ctrl
点击processConfigurationClass()
方法
重点:我们把话放在这里,这个方法将被多次递归调用到
,我们根据流程一步步分析
第一个if
判断,通过对类上添加的@Conditional
注解以及派生注解进行分析,决定该类是否被舍弃
按着Ctrl
点击doProcessConfigurationClass()
方法:
doProcessConfigurationClass()
方法中可以处理多个注解
13.5.1.1、处理携带@Component注解的类的内部类(不执行)
现在需要处理的是主启动类,其中主启动类上面添加@SpringBootApplication
注解,我们点进@SpringBootConfiguration
注解,在点击@Configuration
注解,然后就可以看到@Component
注解,那就符合doProcessConfigurationClass()
方法中的第一个if判断:
这个if
判断用来处理当前类的内部类中的相关注解,我们借用一下spring源码-Springboot解析配置类时,解析配置类的内部类中的代码,可以看到最终代码又回到了processConfigurationClass
方法(重点:这个方法将被多次递归调用到,现在又被调用到了
),既然回到了这个方法,那流程就是一样的了,我们不在重复分析了
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
// 获取当前配置类的所有内部类
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses) {
// 1、内部类不是一个接口
// 2、内部类含有@Component、@ComponentScan、@Import、@ImportResource注解
// 3、内部类含有@Bean注解的方法
// 满足以上三个条件中的2或3即将当前内部类解析
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
// ImportStack是双端队列ArrayDeque的实现类,防止配置类被重复解析
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 解析内部类,见系列文章1的解析过程
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
this.importStack.pop();
}
}
}
}
}
13.5.1.2、处理@PropertySource注解(不执行)
回到doProcessConfigurationClass()
主方法,现在需要处理的是主启动类,其中主启动类不包含@PropertySource
注解,所以第二个if方法不会执行
13.5.1.3、处理@ComponentScan注解(一般情况下放在主启动类上,然后扫描到其他类,之后通过递归方式实现其他类的处理)
现在需要处理的是主启动类,其中主启动类上面添加@SpringBootApplication
注解,点进去之后能看到@ComponentScan
注解
首先shouldSkip()
方法可以针对@Conditional以及派生注解进行是否处理逻辑判断,其中parse()
方法可以对主启动类所在包中的类以及主启动类所在包的子包中的类进行扫描,按着Ctrl
按键点击parse()
方法
parse()
方法体中基本都是获取注解信息的,一般情况下我们直接看最后一些代码片段接口,按着Ctrl
点击doScan()
方法:
先来看方法体中的findCandidateComponents()
方法,该方法可以找到所有可以把当做Bean处理的类,按着Ctrl
点击findCandidateComponents()
方法:
按着Ctrl
点击scanCandidateComponents()
方法:
从scanCandidateComponents()
方法中可以看到resources
中将存储相关包下所有的class
文件信息:
根据以上原则,可以找到很多的类,包括项目中other
包下的Test.class
类,但是该类上没有任何注解,本来就是一个干扰类,Spring通过isCandidateComponent()
方法来判断该类是否可以被Spring管理,按着Ctrl点击isCandidateComponent()
方法:
其中includeFilters
包含对@Component
和@ManagedBean
注解的判断,只要添加这两个注解或者派生注解(比如@Configuration),那该类就可以被Spring当做Bean进行管理
这样我们就得到了findCandidateComponents()
的返回值,即:主启动类所在包以及子包下被@Component
注解或者派生注解修饰的类(@ManagedBean
注解基本没用到)
我们继续回到doScan()
方法,来看for循环方法是如何处理这些类的:
先来说一下for循环中进行注解值解析的代码,按着Ctrl点击processCommonDefinitionAnnotations()
方法:
按着Ctrl
点击processCommonDefinitionAnnotations()
方法,我们可以看到代码是对@Lazy
、@Primary
、@DependsOn
、@Role
、@Description
注解进行解析处理,然后往BeanDefinition candidate
中填充对应属性,未来在真正处理该Bean的时候,这些都可以发挥作用,比如解析@DependsOn
注解,那就需要提前将其他Bean放到Bean工厂中,然后才能把当前Bean放到Bean工厂中~
然后回到for循环中,其中for循环中最后一些代码比较重要,按着Ctrl点击registerBeanDefinition()
方法:
按着Ctrl点击registerBeanDefinition
方法:
按着Ctrl + Alt
点击registerBeanDefinition
方法,选择DefaultListableBeanFactory
类:
其中hasBeanCreationStarted()
方法结果是true
,其中alreadyCreated
集合在第一次处理主启动类的时候将会执行到,具体执行路径是:
- org.springframework.context.support.AbstractApplicationContext#refresh
- org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
- org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)
- 看“First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered”下面的
beanFactory.getBean(XXX)
方法 - org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class)
- org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
- 之后找到“if (!typeCheckOnly)”的地方,然后观察“markBeanAsCreated(beanName)”
- org.springframework.beans.factory.support.AbstractBeanFactory#markBeanAsCreated
- 在该方法中就可以看到
alreadyCreated
被插入值的情况了
既然已经知道hasBeanCreationStarted()
结果是true,那当前类的信息就可以被放在DefaultListableBeanFactory
对象的beanDefinitionMap
和beanDefinitionName
属性中了
此时会将扫描出来的类信息组装成BeanDefinitionHolder
对象,然后往beanDefinitions
中填充,最后将beanDefinitions
向上返回,我们看一下细节图:
我们将细节折叠起来,再来一个全景图:
然后最终会将扫描到的所有类信息一层层返回到parse()
方法处,此时scannedBeanDefinitions
就是扫描出来的类信息列表
我们往下看for
循环,里面还有一个parse()
方法;按着Ctrl
点击parse()
方法,此时又看到了我们的老朋友processConfigurationClass()
方法(重点:这个方法将被多次递归调用到,现在又被调用到了
),之前多次提到过,所以现在它就会通过递归形式对扫描到的类中的注解等信息进行处理
13.5.1.4、处理@Import注解
主启动类中确实有这个注解,具体是:@SpringBootApplication 》 @EnableAutoConfiguration 》 @Import
、@SpringBootApplication 》 @EnableAutoConfiguration 》 @@AutoConfigurationPackage》@Import
继续回到doProcessConfigurationClass()
方法,然后看解析@Import
注解的地方,本着执行顺序的原则,我们先来看getImports()
方法,等会再回来看processImports()
方法;按着Ctrl
点击getImports()
方法:
按着Ctrl
点击collectImports()
方法
通过递归的方式,一点点找到当前类上所有的@Import
注解:
对于主启动类而言,获取到的imports
如下:
现在getImports(sourceClass)
执行完毕了,然后我们回到processImports()
方法处,按着Ctrl按键点击processImports()
方法:
进入processImports()
方法之后,由于@Import
里面可以放置3种对象,分别是实现ImportSelector接口、实现ImportBeanDefinitionRegistrar接口、普通类;
在启动类中,由于是@Import注解的value值是AutoConfigurationImportSelector
,我们来看下它的类图:
因此我们选择实现ImportSelector接口的方式,因此candidate.isAssignable(ImportSelector.class)
的结果是true
在@SpringApplication
中内部的注解是@Import(AutoConfigurationImportSelector.class)
,按着Ctrl点击AutoConfigurationImportSelector
类,然后看selectImports()
方法:
其中getAutoConfigurationEntry()
方法用来获取需要被Spring管理的类,按着Ctrl点击该方法:
我们需要先获取需要被Spring管理的类信息,按着Ctrl
点击getCandidateConfigurations()
方法:
我们又看到了熟悉的SpringFactoriesLoader.loadFactoryNames()
方法(解释:loadFactoryNames()
方法目的是从所有的META-INF/spring.factories
文件获取相关信息,然后从spring.factories
文件中获取EnableAutoConfiguration
接口的实现类)
这样getAutoConfigurationEntry()
方法返回值autoConfigurationEntry
中的configurations
属性列表就是需要被Spring管理的类列表
我们debug来看一下最终结果吧:
之后递归调用processImports()
方法,所以又回到了当前方法,但是需要被导入的类是一个普通类,所以将执行else
分支
又回到了processConfigurationClass
方法(重点:这个方法将被多次递归调用到,现在又被调用到了
),既然回到了这个方法,那流程就是一样的了,我们不在重复分析了
13.5.1.5、处理@ImportResource注解(不执行)
回到doProcessConfigurationClass()
主方法,现在需要处理的是主启动类,其中主启动类不包含@ImportResource
注解,所以第4个if方法不会执行
13.5.1.6、处理@Bean注解(不执行)
回到doProcessConfigurationClass()
主方法,现在需要处理的是主启动类,其中主启动类的方法上不包含@Bean
注解,所以beanMethods
是一个空集合,所以for循环不会执行
13.5.1.7、处理接口中默认方法上添加@Bean注解的情况(不执行)
回到doProcessConfigurationClass()
主方法,现在需要处理的是主启动类,其中主启动类没有出现任何接口,所以该方法不会执行任何实际事情了
13.5.1.8、处理父类(不执行)
回到doProcessConfigurationClass()
主方法,现在需要处理的是主启动类,由于主启动类没有父类,所以不会实际执行具体内容
13.5.2、处理添加@Bean注解的类
我们先来看下TestConfiguration.class
:
@Configuration
public class TestConfiguration {
@Bean
public A a() {
return new A();
}
@Bean
public B b() {
return new B();
}
}
我们回到doProcessConfigurationClass()
方法,其中上面
上述方法执行之后,只是将携带@Bean注解的方法信息放在configClass
对象的beanMethods
属性中,等下也会将这些Bean
放到beanDefinitionMap
中,稍等片刻~
13.5.3、处理添加@Import注解的类
我们先来看下TestImport.class
:
我们回到doProcessConfigurationClass()
方法,先来按着Ctrl
点击getImports()
方法
其中getImports()
方法就是获取@Import
注解中的value
属性值
针对@Import
注解中的几种情况,通过if
判断后分别进行处理:
我们先来看情况1
(实现ImportSelector
接口):
我们再来看情况2
(实现ImportBeanDefinitionRegistrar
接口),其中情况2没有直接处理,而是把方法调用信息放在importBeanDefinitionRegistrars
这个Map集合中,后续将会在this.reader.loadBeanDefinitions(configClasses);
中调用到
我们再来看情况3
(普通类),直接对普通类进行处理了,我们又看到了老朋友processConfigurationClass()
方法,如果该类中存在添加@Bean的方法,也会被doProcessConfigurationClass()
方法处理到,从而放到该类对应的ConfigurationClass
对象的beanMethods
属性中;目前只是将引入类的信息放在configurationClasses
中,等下也会将这些Bean
放到beanDefinitionMap
中,稍等片刻~
13.5.4、处理添加@ImportResource注解的类
我们先来看下TestImportResource.class
:
我们回到doProcessConfigurationClass()
方法,这种情况也没有直接处理,而是把方法调用信息放在importedResources
这个Map集合中,后续将会在this.reader.loadBeanDefinitions(configClasses);
中调用到
13.5.5、处理添加@PropertySource注解的类
我们先来看下TestPropertySource.class
:
我们回到doProcessConfigurationClass()
方法,先来按着Ctrl
点击processPropertySource()
方法:
按着Ctrl
点击processPropertySource()
方法:
按着Ctrl
点击addPropertySource()
方法:
最终将配置信息添加到environment
中
13.5.6、处理不加注解的父类中添加@Bean注解的情况
我们先来看下TestSuperClass.class
、F.class
:
我们回到doProcessConfigurationClass()
方法,由于类TestSuperClass
的父类是类F
,所以return
可以返回类F
,然后doProcessConfigurationClass()
方法就返回了,此时sourceClass
是类F,所以不为空,因此do...while
循环再次启动;现在执行doProcessConfigurationClass()
方法的是类F,然后对类F中添加@Bean
注解的方法进行处理,后续将会在this.reader.loadBeanDefinitions(configClasses);
中调用到,等下也会将这些类G
放到beanDefinitionMap
中,稍等片刻~
13.5.7、处理实现的接口中默认方法上添加@Bean注解的情况
我们先来看下TestServiceAImpl.class
、TestServiceA.class
:
我们回到doProcessConfigurationClass()
方法,可以获取实现的接口的默认方法上添加@Bean
注解的方法,我们将方法信息放在beanMethods
属性中,后续将会在this.reader.loadBeanDefinitions(configClasses);
中调用到,等下也会把相关信息放到beanDefinitionMap
中,稍等片刻~
13.5.8、其他几种情况
- 添加
@Component
注解的类中方法上添加@Bean
注解也有效,请看:TestComponent.class
@Component
注解有很多衍生注解,比如:@RestController
(TestController.class)、@Service
(TestServiceAImpl.class)、@Repository
(TestRepository.class)
13.5.9、处理@Bean注解、@Import、@ImportResource注解而产生的bean信息,从而将它们加入到DefaultListableBeanFactory对象的beanDefinitionMap、beanDefinitionNames中
我们回到org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
方法
按着Ctrl
点击loadBeanDefinitions()
方法,该方法可以处理@Bean注解、@Import注解、@ImportResource注解而产生的bean信息
按着Ctrl
点击loadBeanDefinitionsForConfigurationClass()
方法:
根据这几种情况,根据代码情况来逐个分析:
现在依托示例代码给大家分别看一下这几种情况的真实数据:
我们可以把断点打到org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
方法中的如下位置:
this.reader.loadBeanDefinitions(configClasses);
我们可以看configClasses
对象的内容,接下来请看结果:
情况1:
情况2:
情况3:
情况4:
接下来仔细分析一下代码执行过程吧~
情况1:使用@Import注解引入的普通类,以及实现ImportSelector接口的类引入的类
我们在上面分析过,使用@Import
注解引入的实现ImportSelector
接口的类引入的类最终也会走普通类的逻辑,所以我们就分析一个普通类放到DefaultListableBeanFactory
对象的beanDefinitionMap
和beanDefinitionNames
属性的例子即可
可以把debug断点
打到registerBeanDefinitionForImportedConfigurationClass
方法上,按着Ctrl
点击registerBeanDefinitionForImportedConfigurationClass()
方法:
按着Ctrl + Alt
点击registerBeanDefinition
方法,选择DefaultListableBeanFactory
类:
大家可以看到往DefaultListableBeanFactory
对象的beanDefinitionMap
和beanDefinitionNames
属性中塞值的情况
情况2:使用@Bean注解引入的类
按着Ctrl
点击loadBeanDefinitionsForBeanMethod
方法:
我们仔细分析一下loadBeanDefinitionsForBeanMethod
方法
第1
点是决定该方法是否会继续往下走,是否有成为被Spring管理的bean的可能性;也就是判断@Bean
注解所在方法上的@Conditional
注解或者衍生注解,比如@Profile
、@ConditionalOnProperty
注解
第2
点设置@Bean注解的属性上的初始化方法和销毁方法
第3
点调用registerBeanDefinition
方法实现往DefaultListableBeanFactory
对象的beanDefinitionMap
和beanDefinitionNames
属性中塞值,之前已经分析过了,这里不在分析
情况3:使用@ImportResource注解引入的配置文件
先来看一下我给的示例代码中使用@ImportResource
注解的位置
按着Ctrl
点击loadBeanDefinitionsFromImportedResources
方法:
在loadBeanDefinitionsFromImportedResources
方法中,主要分为3个步骤进行处理:
首先获取readerClass
对象:
其次就是获取reader对象
:
最后调用loadBeanDefinitions
方法:
按着Ctrl + Alt
点击上图的loadBeanDefinitions
方法,然后按着Ctrl
继续点击重载的loadBeanDefinitions
方法:
按着Ctrl
继续点击loadBeanDefinitions
方法:
按着Ctrl
继续点击loadBeanDefinitions
方法:
按着Ctrl + Alt
继续点击loadBeanDefinitions
方法,选择XmlBeanDefinitionReader
类:
按着Ctrl
继续点击loadBeanDefinitions
方法:
再往下的代码执行路径就太长了,大家感兴趣的往下追一下吧,也不难找~
情况4:执行@Import注解引入的视线ImportBeanDefinitionRegistrar接口的类中的方法
按着Ctrl
继续点击loadBeanDefinitionsFromRegistrars
方法:
按着Ctrl
继续点击registerBeanDefinitions
方法:
按着Ctrl + Alt
继续点击registerBeanDefinitions
方法,点击我们示例代码中编写的实现类MyImportRegistry
,然后就可以执行对应的registerBeanDefinitions
方法了
13.6、注册Bean后处理器
按着Ctrl
点击registerBeanPostProcessors()
方法:
按着Ctrl
点击registerBeanPostProcessors
方法:
首先在registerBeanPostProcessors
方法根据BeanPostProcessor
类型查找后续处理器名称列表,其实这个查询方式和之前DefaultListableBeanFactory
对象的beanDefinitionNames
属性有关
然后根据BeanPostProcessor
实现类是否实现PriorityOrdered
接口(Order接口的子接口)、是否实现Ordered
接口,以及实现PriorityOrdered
接口and属于MergedBeanDefinitionPostProcessor
接口这几种判断方式组成的if...else
逻辑,从而完成BeanPostProcessor
实现类的分组
然后针对识别出来的不同集合进行逐次处理
我们来看下priorityOrderedPostProcessors
集合,也就是实现PriorityOrdered
接口(Order接口的子接口)的集合,按着Ctrl
点击sortPostProcessors
方法:
虽然comparatorToUse
是AnnotationAwareOrderComparator
对象,但是该对象所属的类是类OrderComparator
的子类,真正的compare
比较方法其实在OrderComparator
类中,所以我们直接点击下图中OrderComparator
类即可
一起去看下OrderComparator
类中的compare
方法:
然后来看registerBeanPostProcessors
方法,然后将排序之后的priorityOrderedPostProcessors
往beanPostProcessors
集合中放置,由于是升序排序,所以越小在beanPostProcessors
集合中越靠前,到时候越先执行
后面依次将实现Order
接口的BeanPostProcessor
实现类升序排序后,然后往beanPostProcessors
集合中放置,由于是升序排序,所以越小在beanPostProcessors
集合中越靠前,到时候越先执行
然后依次执行剩余2种情况,最终实现将bean后处理器填充到beanPostProcessors
集合中
13.7、创建并启动Tomcat服务器,即onRefresh()方法
按着Ctrl + Alt
点击onRefresh()
方法,选择ServletWebServerApplicationContext
类:
按着Ctrl
点击createWebServer
方法:
获取TomcatServletWebServerFactory
对象:
按着Ctrl + Alt
点击getWebServer
方法,然后选中TomcatServletWebServerFactory
类:
首先创建Tomat
对象:
然后启动Tomcat
对象:
13.8、将bean交给Spring进行管理,即finishBeanFactoryInitialization方法(做菜过程)
13.8.1、找到getBean方法和doGetBean方法
按着Ctrl
点击finishBeanFactoryInitialization
方法:
按着Ctrl + Alt
点击finishBeanFactoryInitialization
方法:
按着Ctrl
点击getBean()
方法:
按着Ctrl
点击doGetBean()
方法:
13.8.2、简单分析一下getSingleton()
方法和@DependsOn
注解的作用
13.8.2.1、优先讲解一下getSingleton()
方法在解决循环依赖和动态代理时起到的作用
- 如果存在2个类,分别是A类、B类,B依赖A
- 当获取A对象的时候,由于A类不需要注入其他类,所以很方便的将生成对象放到一级缓存
singletonObjects
中 - 当获取B对象的时候,需要先注入A对象,B对象在获取A对象时需要执行到下面
getSingleton
方法,然后在getSingleton
方法中会从一级缓存中取出A类的最终对象
- 当获取A对象的时候,由于A类不需要注入其他类,所以很方便的将生成对象放到一级缓存
- 如果存在4个类,分别是A类、B类、C类、D类,A依赖B、C,B也依赖A,C也依赖A,D也依赖A
- 当获取A对象的时候,此时创建A对象的信息(匿名内部类)存在三级缓存
singletonFactories
中,然后注入B对象 - 而获取B对象的时候,需要先注入A对象
- B对象在获取A对象时需要执行到下面
getSingleton
方法,然后在getSingleton
方法中会执行三级缓存中对象A的匿名内部类的方法,从而获取A类的普通对象/代理对象(存在需要代理的情况时) - 之后将A类的普通对象/代理对象(存在需要代理的情况时)放到二级缓存
earlySingletonObjects
中 - 当获取A对象的时候,需要先注入C对象,而获取C对象的时候,需要先注入A对象
- C对象在获取A对象时需要执行到下面
getSingleton
方法,然后在getSingleton
方法中会从二级缓存中取出A类的普通对象/代理对象(存在需要代理的情况时) - 这时候A对象注入了B对象和C对象,之后将二级缓存中A类的普通对象/代理对象(存在需要代理的情况时)放到一级缓存
singletonObjects
中,也就是将A类的最终对象放到一级缓存中 - 当获取D对象的时候,需要先注入A对象,D对象在获取A对象时需要执行到下面
getSingleton
方法,然后在getSingleton
方法中会从一级缓存中取出A类的最终对象
- 当获取A对象的时候,此时创建A对象的信息(匿名内部类)存在三级缓存
13.8.2.2、进入getSingleton()
方法内部探究一下
按着Ctrl
点击getSingleton()
方法:
按着Ctrl
点击getSingleton()
方法:
下面是getSingleton()
方法的细节:
13.8.2.3、讲一下@DependsOn
注解的作用
先来看一下示例代码,这个注解的作用是让TestDependsOn
对象在testComponent
对象之后被Spring管理,这种一种类依赖的解耦方式,比如只有testComponent
对象的初始化方法执行完成之后,才能执行TestDependsOn
对象的初始化方法,那我们就需要这个注解来做事情
承接之前的代码,最开始时,代码Object sharedInstance = getSingleton(beanName);
的执行结果肯定是null
,所以一定是执行else
逻辑
在else
逻辑中,有这样一些代码,可以看到首先通过getDependsOn()
方法获取到依赖对象信息,然后最终调用getBean
方法先获取依赖对象,从而让依赖对象首先被Spring
当做Bean
管理,这就完成了上面所说的启动顺序要求,其中getBean()
方法再往下就不分析了,这又是一个同样的递归逻辑
13.8.3、通过截图方式来分析不存在循环依赖的情况
13.8.3.1、背景
如果存在2个类,分别是A类、B类,B依赖A,但是A不依赖B
13.8.3.2、将对象A放入一级缓存中(不想写那么多了,写一点点吧~)
大家感兴趣的请看我的另外一篇文章Sping源码:三级缓存,这篇文章的截图写的较为详细~
13.8.4、通过截图方式分析存在循环依赖的情况
13.8.4.1、背景
如果存在4个类,分别是A类、B类、C类、D类,A依赖B、C,B也依赖A,C也依赖A,D也依赖A
13.8.4.2、实在不想在写了,确实太多了
大家感兴趣的请看我的另外一篇文章Sping源码:三级缓存,这篇文章的截图写的较为详细~
13.8.5、Bean实例化和初始化
按着Ctrl + Alt
点击createBean
方法:
按着Ctrl + Alt
点击doCreateBean
方法:
按着Ctrl
点击initializeBean
方法:
分析一下initializeBean
方法:
我们来详细分析一下具体过程
先来看一下示例代码截图:
开始一步一步分析:
1、TestBeanLifeCycle第1步:无参构造~
2、TestBeanLifeCycle第2步:实现3个接口,等待重写方法被调用……
依旧从doCreateBean
方法出发:
按着Ctrl点击invokeAwareMethods
方法:
分析下invokeAwareMethods
方法细节,我们正好实现了这3个Aware
相关接口,所以这3个接口会被调用~
3、TestBeanLifeCycle第3步:@PostConstruct 注解
依旧从doCreateBean
方法出发:
按着Ctrl点击applyBeanPostProcessorsBeforeInitialization
方法:
按着Ctrl + Alt
点击postProcessBeforeInitialization
方法,选择InitDestroyAnnotationBeanPostProcessor
类:
按着Ctrl
点击invokeInitMethods
方法:
实现对添加@PostConstruct
注解的方法调用:
4、TestBeanLifeCycle第4步:InitializingBean 接口的 afterPropertiesSet 方法执行了
依旧从doCreateBean
方法出发:
按着Ctrl
点击invokeInitMethods
方法:
分析代码后可以看到调用实现InitializingBean
接口后重写的afterPropertiesSet
方法:
5、TestBeanLifeCycle第5步:@Bean注解的initMethod属性~
依旧从doCreateBean
方法出发:
按着Ctrl
点击invokeInitMethods
方法:
分析代码后可以看到调用实现InitializingBean
接口后重写的afterPropertiesSet
方法:
分析一下自定义的初始化方法,其中这种方式和在xml配置文件中配置的方式一致:
13.8.5、Bean销毁
13.8.6、Bean的生命周期
目录13.8.5、Bean实例化和初始化
与 13.8.5、Bean销毁
组合到一起就是Bean
的生命周期