【SpringBoot源码分析】粗略研究SpringBoot创建Bean的过程(上)

更新时间:2023-05-04 16:59:12 阅读: 评论:0

【SpringBoot源码分析】粗略研究SpringBoot创建Bean的过程(上)
写在最前
因为本次分析仅研究bean的创建过程,其他不重要的内容我将会忽略。本次内容⽐较长,有兴趣的朋友可以边阅读源码边看,如果有需要,我可以把涉及到的⽅法和类名都贴出来。
正⽂
开始
⾸先从SpringBoot的启动类开始,点进main⽅法中的SpringApplication.run()⽅法,可以看到是new了⼀个SpringApplication对象并运⾏了它的run⽅法。我们再点进去看看,是⼀段⾮常长的代码,我们准备开始分析:
public ConfigurableApplicationContext args){
long startTime = System.nanoTime();
// 1
DefaultBootstrapContext bootstrapContext =createBootstrapContext();
ConfigurableApplicationCo招聘总结 ntext context =null;
// 2
configureHeadlessProperty();
// 3
SpringApplicationRunListeners listeners =getRun母亲去世悼词 Listeners(args);
listeners.starting(bootstrapContext,this.mainApplicationClass);
try{
ApplicationArguments applicationArguments =new DefaultApplicationArguments(args);
// 4
ConfigurableEnvironment environment =prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 5
Banner printedBanner =printBanner(environment);
context =createApplicationContext();
context.tApplicationStartup(this.applicationStartup);
// 6
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 7
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime()- startTime);
if(this.logStartupInfo){
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
...
}
我们逐⾏分析
1. ⾸先看到createBootstrapContext();这个⽅法,根据字⾯意思应该是创建⼀个引导上下⽂对象,我们点进⽅法看⼀下,这⾥就不贴源码
了,内容⽐较简单,⾸先是直接new了⼀个DefaultBootstrapContext对象,然后遍历bootstrapRegistryInitializers属性进⾏初始化,并返回上⾯new的对象。
2. configureHeadlessProperty();翻译过来应该是配置⽆显⽰器属性?代码也⽐较简单,就是配置了⼏个常量。
3. 配置并初始化监听类,这不是今天的重点,所以先掠过。
4. 将传进来的args参数包装成A电影隐形人 pplicationArguments对象,这⾥可能有朋友不知道 java 的args参数是怎么来的,这⾥顺便提⼀句,在
java启动是后⾯跟着的参数(不是-跟着的)就会以args形式进⼊main⽅法。prepareEnvironment(listeners, bootstrapContext,
applicationArguments);初始化参数对象ConfigurableEnvironment,以下⽤environment来表⽰参数对象。这个参数对象⽐较重要,⾥⾯不光包括了java启动本⾝的参数,还包括了⼀些外部配置信息,包括我们写的配置⽂件、系统环境变量、jvm参数等,我们可以先眼熟⼀
下这个家伙。configureIgnoreBeanInfo(environment);也⽐较简单,就是读取⽂件中有没有设置忽略bean属性
(spring.beaninfo.ignore)。
推荐⼀下我的另⼀篇⽂章()
5. 打印Banner这⾏没什么好说的,我们看下⼀⾏context = createApplicationContext();获取到应⽤程序上下⽂对象,这个创建过程挺有意
思,⽤到了java8的特性函数编程,使⽤的是ApplicationContextFactory中的默认lambda表达式。随后设置⼀下应⽤程序启动,应该是起到监控类似的作⽤,暂时先忽略。
6. 这看起来是⽐较重要的⼀个⽅法,应该会有我们这次的⽬标,先点进去看看。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner){
context.tEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(con什么是制度 text);
bootstrapContext.clo(context);
if(this.logSt2000米 artupInfo){
Parent()==null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 以上基本是配置准备过程
ConfigurableListableBeanFactory beanFactory = BeanFactory();
if(printedBanner !=null){
}
if(beanFactory instanceof AbstractAutowireCapableBeanFactory){
((AbstractAutowireCapableBeanFactory) beanFactory).tAllowCircularReferences(this.allowCircularReferences);
if(beanFactory instanceof DefaultListableBeanFactory){
((DefaultListableBeanFactory) beanFactory)
.tAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if(this.lazyInitialization){
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources =getAllSources();
load(context, Array(new Object[0]));
}
代码⽐较长,我们来看⽐较重要的部分,逐⾏分析后我们不难发现,在ConfigurableListableBeanFactory beanFactory =
之后就先注册了两个单例bean到容器⾥,(搞不懂为什么springBootBanner也要注册⼀个)。然后就是对⼯⼚的⼀些配置参数,⽐如循环引⽤什么的。
后⾯也都是加载⼀些源配置,这⾥也先忽略。看样⼦这个⽅法也没有我们的⽬标,继续下⼀个⽅法。
7. refreshContext(context);⾥可以看到去执⾏了⽗类的refresh()⽅法,下⾯的catch⾥⾯则是出现了Webrver字样,估计这就是我们要
找的⽅法了,进去看⼀眼。代码⽅法很多,我们也是逐⾏开始分析。
synchronized(this.startupShutdow跳绳运动 nMonitor){
StartupStep contextRefresh =this.applicationStartup.start("fresh");
prepareRefresh();
ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try{
postProcessBeanFactory(beanFactory);
postProcessBeanFactory(beanFactory);
// 启动
StartupStep beanPostProcess =this.applicationStartup.start("t.beans.post-process");
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch(BeansException ex){
...
}
finally{
retCommonCaches();
}
}
1. prepareRefresh(); 这个主要是切换到活动状态,并且记录⼀下启动时间,springboot启动不是会显⽰时间吗,我估计就是这⾥
开始记录的。⼀顿⽇志输出然后就是初始化属性源,这个⽅法是空的。接着有⼀个getEnvironment().validateRequiredProperties();⽅法,似乎是⽤来验证⼀些必填项的,这⾥不太重要,我们先略过。在下⾯就是new了监听器集合和事件集合。
2. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();则是调⽤⼦类的refreshBeanFactory()⽅法来创建⼀个新的
bean⼯⼚并持有他的实例。
3. prepareBeanFactory(beanFactory);这个⽅法⽬标⽐较明确,就是设置⼀些bean⼯⼚的参数,⽐如添加类加载器,属性注册器什么
的,最后还注册了⼏个单例模式的上下⽂bean,这⾥就不说明了,感兴趣的读者可以⾃⼰进源码看⼀下。
4. postProcessBeanFactory(beanFactory);这⼀步应该是上下⽂⼦对象对bean⼯⼚进⾏⼆次处理,这⾥我没有写额外的配置,所以实
际上没有运⾏。(在debug⾥⾯发现了有scanner和page,推测可能是这⼀步扫出来的需要注册的bean)
5. invokeBeanFactoryPostProcessors(beanFactory);这⼀步是今天的重点 我们点进去看⼀下,可以看到⽅法签名 实例化并调
⽤所有已注册的BeanFactoryPostProcessor bean,如果给定,请遵守显式顺序。必须在单例实例化之前调⽤。 这很显然就是我们今天的⽬标,⽅法⾥⾯有两步,第⼀步是PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory,
getBeanFactoryPostProcessors());,第⼆步是检测织⼊并准备和检测@Bean注解标准的类并加⼊⼯⼚,这⾥涉及AOP的知识,先不管,重点是第⼀步的⽅法。这个⽅法很长,我们单独拎出来做⼀个⼩节。
invokeBeanFactoryPostProcessors⽅法解析:
⾸先我们可以看到⼀⼤段注释,⼤体意思就是说虽然可以很⽅便的重构这个⽅法以便修改⾥⾯很多重
复使⽤的集合,但最好不要这样做,他们使⽤集合和多次调⽤⽅法都是有意的,必须遵守处理器的规则,这⾥就不太关⼼了,感兴趣的读者可以
去PostProcessorRegistrationDelegate这个类阅读。
在官⽅源码中有⼀些注释,根据这些注释我们可以按顺序来阅读官⽅源码。
⾸先对beanFactory的类型进⾏判断,如果是BeanDefinitionRegistry类型:
1. ⾸先去遍历传参进来的beanFactoryPostProcessors取到postProcessor对象,对其进⾏分类,如果
是BeanDefinitionRegistryPostProcessor类型,就需要先进⾏初始化bean,这⾥涉及到了postProcessBeanDefinitionRegistry⽅法,我们下⼀⼩节在分析。如果不是,就分类到regularPostProcessors集合⾥⾯,待会在初始化。
2. 继续阅读我们发现spring将处理器分为了四类,分别是PriorityOrdered、Ordered、普通的和前⾯添加的regularPost集合⾥的。他
们的调⽤顺序依次进⾏进⾏并且全都保存在⼀个t集合中。在每次处理,总是先将处理器保存⾄currentRegistryProcessors集合中(如果之前的t集合没有的话),每次处理过程类似,总是先排序,在加⼊registryProcessors集合中,最后统⼀处理初始化。
如果不是BeanDefinitionRegistry类型,那就直接调⽤这些处理器。
接下来也是先给处理器分类(PriorityOrdered, Ordered, and the rest.)并且根据优先顺序来循环调⽤这些处理器。
上⽂中说指的调⽤处理器是指invokeBeanFactoryPostProcessors这个⽅法,⽅法内容⽐较少,主要是遍历传进来的集合,先启动beanFactory这个标签对应的处理器,并且直接从⼯⼚中取出对应的bean并处理,对应postProcessBeanFactory⽅法,这个⽅法就是解析bean的过程,也是我们的⽬标。
postProcessBeanFactory⽅法解析:
这个⽅法⽐较简单,先是判断了⼀下这个处理器有没有被调⽤过,然后就进⼊了下⼀个⽅法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry){
int registryId = System.identityHashCode(registry);
ains(registryId)){
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against "+ registry);
}
if(ains(registryId)){
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against "+ registry);
}
processConfigBeanDefinitions(registry);
}
processConfigBeanDefinitions⽅法解析:
先看看官⽅的注释 基于configuration类的注册表构建并验证配置模型。 我们快速的阅读⼀下这个⽅法。
⾸先是获取到定义bean的数组,然后对这个数组进⾏遍历,通过名称获取到对应的BeanDefinition对象,将其加⼊到List集合中。
然后对集合排序处理,主要针对优先度进⾏排序。
⽣成bean⽣成策略(BeanNameGenerator)并将其赋值给全局属性componentScanBeanNameGenerator和importBeanNameGenerator。
接下来就是⽐较重要的部分了,我们先看官⽅注释// Par each @Configuration class(解析每个配置类),⾸先是⽣成解析对象(ConfigurationClassParr),高育良结局 随后将传五行对应的五脏 进来的的BeanDefinitionHolder集合转换成t集合并遍历。遍历过程⽐较长,⾸先是⽣成应⽤程序启动对象标识应⽤程序的启动阶段,然后使⽤解析对象对BeanDefinitionHolder集合进⾏解析,这个过程是递归进⾏的,解析的⽅法我们我们放到下⼀个⼩节在分析。随后将解析出来的ConfigurationClass对象都放到⼀个t集合中,ConfigurationClass这个类就是表⽰了bean的各种信息。
接着实例化⼀个reader对象,属于ConfigurationClassBeanDefinitionReader类,它可以读取给定的定义ConfigurationClass实例集,根据其内容向BeanDefinitionRegistry中注册bean定义。恰好我们上⾯就有⼀个集合,调⽤读取⽅法,给之前打开的启动对象加⼀个标签并结束。
随后进⾏⼀个判断,如果定义注册表(BeanDefinitionRegistry)中的bean定义数量⼤于⽅法⼀开始保存的bean定义数量,说明在解析过程中加⼊了新的bean定义,那么就将新的数量的数组重新赋值并加⼊candidates集合,并且继续循环(因为在判断之前清空
了candidates集合,只有全部都解析完成了才能退出循环),这⾥的全部指的是⽗类之类的,可以看下⾯的解析⼩节。
最后就是清除缓存等了,这⾥先略过。
parr.par(candidates)解析⽅法解析:
⽅法主要是先判断了⼀下Bean定义的类型,这⾥我们直接看AnnotatedBeanDefinition类型就可以,最终调⽤的
是processConfigurationClass⽅法
processConfigurationClass这个⽅法的⽬的⽐较简单,⾸先获取⼀下当前加载的配置类(ConfigurationClass对象),判断⼀下是否为null,再判断是不是通过@Import注解导⼊的,如果是的话合并并且退出。如果不是注解导⼊的,就从加载的类中删除,因为下⾯要开始处理了⽤新加载的类更好。然后获取到源类信息,接着递归的调⽤doProcessConfigurationClass,这个⽅法每次返回⽗
类,⼀直循环处理到⽗类为null,最后放回加载的类。
doProcessConfigurationClass⽅法解析:
这个⽅法⽐较有意思,主要功能就是构建⼀个完整的ConfigurationClass。 先看下源码:

本文发布于:2023-05-04 16:59:12,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/856236.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:集合   对象   配置   启动   解析   过程   参数
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图