SpringBoot是怎么在实例化时候将bean加载进入容器中
之前写过的很多spring文章,都是基于应用方面的,这次的话,就带大家来一次对spring的源码追踪,看一看spring到底是怎么进行的初始化,如何创建的bean,相信很多刚刚接触spring的朋友,或者没什么时间的朋友都很想知道spring到底是如何工作的。
首先,按照博主一贯的作风,当然是使用最新的spring版本,这次就使用spring4.2.5...其次,也是为了方便,采用spring-boot-1.3.3进行追踪,和spring 4.2.5是相同的。
不用担心框架不同,大家如果是使用的xml方式进行配置的话,可以去你的ContextListener里面进行追踪,spring-boot只是对 spring所有框架进行了一个集成,如果实在进行不了前面几个步骤的话,可以从文章第6步的AbstractApplicationContext开始看起, 这里就是spring最最重要的部分。
1、默认的spring启动器,DemoApplication:
该方法是spring-boot的启动器,我们进入。
2、进入了SpringApplication.java:
这里创建了一个SpringApplication,执行run方法,返回的是一个ConfigurableApplicationContext,这只是一个接口而已,根据名称来看,称作可配置的应用程序上下文。
3、我们不看new SpringApplication(Sources)过程了,有兴趣可以自己研究一下,里面主要是判断了当前的运行环境是否为web,当然,博主这次的环境是web,然后看run:
try语句块内的内容最为重要,因为创建了我们的context对象,此时需要进入的方法为
context = createAndRefreshContext(listeners, applicationArguments)
4、 接着往下看,看到context = createApplicationContext这行,进入,因为我们刚刚在创建SpringApplication时并没有给 this.applicationContextClass赋值,所以此时this.applicationContextClass = null,那么便会创建指定的两个applicationContext中的一个,返回一个刚刚创建的context,这个context便是我们的基 础,因为门现在为web环境,所以创建的context为 AnnotationConfigEmbeddedWebApplicationContext。
5、第4步创建了一个context,需要指出的是,context里面默认带有一个beanFactory,而这个beanFactory的类型为DefaultListableBeanFactory。
然后继续看我们的createAndRefreshContext方法,忽略别的代码,最重要的地方为refresh(context):
6、进入refresh(context),不管你进入那个实现类,最终进入的都是AbstractApplicationContext.java:
该方法中,我们这次需要注意的地方有两个:
1、invokeBeanFactoryPostProcessors(beanFactory);
2、finishBeanFactoryInitialization(beanFactory);
两处传入的beanFactory为上面的context中的DefaultListableBeanFactory。
7、进入invokeBeanFactoryPostProcessors(beanFactory):
然后找到第98行的invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry),该方法看名字就是注册bean,进入。
8、 该方法内部有一个for循环,进入内部方法 postProcessor.postProcesBeanDefinitionRegistry(registry),此时传入的registry就是我们context中的beanfactory,因为其实现了BeanDefinitionRegistry接口。而此时的postProcessor实现类为ConfigurationClassPostProcessor.java。
9、进入之后直接看最后面的一个方法,名称为processConfigBeanDefinitions(registry),翻译过来就是配置beanDefinitions的流程。
10、在processConfigBeanDefinitions(registry)里,314行创建了一个parser解析器,用来解析bean。并在第321行进行了调用,那么我们进入parse方法。
11、进入parse方法之后,会发现内层还有parse方法,不要紧,继续进入内层的parse,然后会发现它们均调用了processConfigurationClass(ConfigurationClass configClass)方法:
12、 在processConfigurationClass(ConfigurationClass configClass)方法内,找到do循环,然后进入doProcessConfigurationClass方法,此时,便会出现许多我们常用的注 解了,spring会找到这些注解,并对它们进行解析。例如第268行的componentScanParser.parse方法,在这里会扫描我们的注 解类,并将带有@bean注解的类进行registry。
13、进入 componentScanParser.parse,直接进入结尾的scannner.doScan,然后便会扫描basepackages,并将扫描 到的bean生成一个一个BeanDefinitionHolder,BeanDefinitionHolder中包含有我们bean的一些相关信息、以 及spring赋予其的额外信息,例如别名:
14、 虽然已经创建了BeanDefinitionHolder,但并没有添加到我们的beanFactory中,所以需要执行263行的 registerBeanDefinition(definitionHolder, this.registry),进入后继续跳转:
然后看registry.registerBeanDefinition方法,因为我们的beanFactory为DefaultListableBeanFactory,所以进入对应的实现类。
15、在进入的registry.registerBeanDefinition方法中,关键点在851行或871行:
this.beanDefinitionMap.put(beanName, beanDefinition);
这个方法将扫描到的bean存放到了一个beanName为key、beanDefinition为value的map中,以便执行DI(dependency inject)。
16、现在我们回到第6步的第二条分支,此处是非懒加载的bean初始化位置,注意,我们之前只是对bean的信息进行了获取,然后创建的对象为BeanDefinition,却不是bean的实例,而现在则是创建bean的实例。
进入方法后找到829行的getBean(weaverAwareName):
17、getBean => getBeanFactory.getBean => doGetBean,然后找到306行的createBean,这里不讲语法,不要奇怪为什么这个createBean不能进入实现代码。
18、这之后的代码都比较容易追踪,直接给一条调用链:
doCreateBean(482) => createBeanInstance(510) => autowireConstructor(1034,1046) => autowireConstructor(1143) => instantiate(267) => instantiateClass(122) => newInstance(147)
括号内的数字代表行号,方便大家进行追踪,最后看到是反射newInstance取得的对象实例:
平时总说spring反射获取bean,其实也就是听别人这么说而已,还是自己见到才踏实,万一别人问你是不是通过Class.forName获取的呢?
19、属性注入,位于第18条的doCreateBean方法内,找到第543行,populateBean便译为填充Bean,进入后便能看到和我们平时代码对应的条件了,例如byType注入、byName注入:
这里还没有进行依赖注入,仅仅是准备一些必要的信息,找到1214行的ibp.postProcessPropertyValues方法
20、这里有很多实现类可以选择,因为博主平时是使用@Autowired注解,所以这里选择AutowiredAnnotationBeanPostProcessor,如果你使用@Resource的话,就选择CommonBeanPostProcessor:
21、进入该方法后,首先获取一些元信息metadata,通过findAutowiringMetadata获取,然后调用metadata.inject进行注入:
22、继续进入inject方法后,继续找到88行的element.inject方法并进入,实现类选择AutowiredFieldElement,该类是一个内部类:
在这个方法中,最重要的内容在第567~570行内,我们可以看到,这里其实也就是jdk的反射特性。
至此,spring的 bean初始化->注入 便完成了。
这次的博客内容很长(其实是自己追踪代码时间太久),感谢大家耐心看完,能有所收获的话便最好不过了。另外,若是有什么补充的话欢迎进行回复。
SpringBoot是怎么在实例化时候将bean加载进入容器中的更多相关文章
- 为什么Pojo类没有注解也没有spring中配置<bean>也能够被加载到容器中。
Spring的注入机制其实就是代替了new的这个过程(称为解耦). 写了一个Thread类,没有加注解@Component,但是可以正常运行,开始为了自圆其说,打通逻辑,猜测是StartThread中 ...
- SpringBoot中的bean加载顺序
https://www.dazhuanlan.com/2019/10/22/5daebc5d16429/ 最近在做传统Spring项目到SpringBoot项目迁移过程中,遇到了一些bean加载顺序的 ...
- SpringBoot系列教程之Bean加载顺序之错误使用姿势辟谣
在网上查询 Bean 的加载顺序时,看到了大量的文章中使用@Order注解的方式来控制 bean 的加载顺序,不知道写这些的博文的同学自己有没有实际的验证过,本文希望通过指出这些错误的使用姿势,让观文 ...
- SpringBoot下使用AspectJ(CTW)下不能注入SpringIOC容器中的Bean
SpringBoot下使用AspectJ(CTW)下不能注入SpringIOC容器中的Bean 在SpringBoot中开发AspectJ时,使用CTW的方式来织入代码,由于采用这种形式,切面Bean ...
- springboot由于bean加载顺序导致的问题
先记录现象: dubbo整合zipkin时,我的配置文件是这样的 @Bean("okHttpSender") public OkHttpSenderFactoryBean okHt ...
- XmlBeanFactory的Bean加载
如何使用这些bean,bean加载的探索: MyTestBean bean=(MyTestBean) bf.getBean("myTestBean"); AbstractBeanF ...
- Spring bean加载2--FactoryBean情况处理
Spring bean加载2--FactoryBean情况处理 在Spring bean加载过程中,每次bean实例在返回前都会调用getObjectForBeanInstance来处理Factory ...
- 【spring】bean加载顺序
问题来源 有一个bean为A,一个bean为B.想要A在容器实例化的时候的一个属性name赋值为B的一个方法funB的返回值. 如果只是在A里单纯的写着: private B b; private S ...
- spring源码阅读笔记06:bean加载之准备创建bean
上文中我们学习了bean加载的整个过程,我们知道从spring容器中获取单例bean时会先从缓存尝试获取,如果缓存中不存在已经加载的单例bean就需要从头开始bean的创建,而bean的创建过程是非常 ...
随机推荐
- java Socket多线程聊天程序
参考JAVA 通过 Socket 实现 TCP 编程 参考java Socket多线程聊天程序(适合初学者) 以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包 ...
- MAC下安装如何安装mysql?
最近有位搞开发的朋友,估计是买了一个新的MAC本,他说不是很熟悉这个系统(什么人哪,太懒了!),于是让我给他装一下后台开发过程中经常使用的数据库软件MYSQL,今天给大家分享一下我的操作步骤以及出现问 ...
- BZOJ_3365_[Usaco2004 Feb]Distance Statistics 路程统计&&POJ_1741_Tree_点分治
BZOJ_3365_[Usaco2004 Feb]Distance Statistics 路程统计&&POJ_1741_Tree_点分治 Description 在得知了自己农 ...
- php之array_column 的使用
听说只有大牛级的高工才知道的函数array_column () 讲真,我才知道. (PHP 5 >= 5.5.0, PHP 7) array_column - 返回数组中指定的一列 说明 arr ...
- jenkins+docker 持续构建非docker in docker
工欲善其事必先利其器,为了解脱程序员的,我们程序员本身发明了很多好用的工具,通过各种工具的组合来达到我们想要的结果 本文采用jenkins docker svn maven作为相关工具,项目sprin ...
- 分布式系统关注点(15)——「水平&垂直切分」详解
如果第二次看到我的文章,欢迎右侧扫码订阅我哟~
- Docker 快速开始
1. 概念 对于开发人员和系统管理员来说,Docker是一个使用容器开发.部署和运行应用程序的平台.使用Linux容器部署应用程序称为容器化.容器并不新鲜,但是将它们用于轻松部署应用程序却很新鲜. ...
- ConstraintLayoutDemo【约束性布局知识梳理】【基于1.1.3】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 在较新版本的Android Studio中新建项目默认使用 ConstraintLayout进行布局的. ConstraintLay ...
- Java8新特性之一:Lambda表达式
Java8是自java5之后最重大的一次更新,它给JAVA语言带来了很多新的特性(包括编译器.类库.工具类.JVM等),其中最重要的升级是它给我们带来了Lambda表达式和Stream API. 1. ...
- SpringBoot进阶教程(二十九)整合Redis 发布订阅
SUBSCRIBE, UNSUBSCRIBE 和 PUBLISH 实现了 发布/订阅消息范例,发送者 (publishers) 不用编程就可以向特定的接受者发送消息 (subscribers). Ra ...