月泉的博客

浅谈使用Spring Annotation容器注册与注入

字数统计: 2.8k阅读时长: 9 min
2018/06/24 Share

首先,我先列个大纲目录,简明扼要的说明一下这一篇文章要写的内容是啥

  • 迷宫入口的宏观结构
  • 入口的背后
  • 容器中获取一个bean的背后

迷宫入口的宏观结构

嗯,迷宫,迷宫的入口是什么?标题说的很明确,是使用Annotation的方式来注册和注入,那么我分析的入口就是AnnotationConfigApplicationContext,那么入口有了,但是并不准备直接从这个迷宫走进去。

Spring结构还是清晰有序中透着复杂,直接闯像我这种闯迷宫已经好多次的“老司机”到是能来去自如,但是新司机怕不是要晕头转向,所以明人不说暗话,我要看图,没图说个XX

不要慌,虽然结构复杂,但只关注主要的,其它的也就自行不攻而破所以不慌!

已知ApplicationContext一个顶层的Context接口,然后它的顶层是继承的BeanFactoryBeanFactory是什么它主要负责什么?我这里就简而言之,它就是一个Bean工厂的接口规范,通过实现这个接口外部可以从工厂中获取一个bean

接下来我们再来说一下HierarchicalBeanFactoryListableBeanFactory

HierarchicalBeanFactory

简而言之就是一个用来层次化工厂的,其中就主要定义了2个接口方法getParentBeanFactorycontainsLocalBean,方法名就很顾名思义,如果还是不懂没关系,这个类对于本文来说不重要

ListableBeanFactory

也简单描述吧,首先这个接口定义的接口方法大体范围意义就是获取BeanDefinition一些根据Annotation和根据类型获得一些Bean类型属性,那所谓的BeanDefinition又是个什么鬼东西?那这里先来简单的介绍一下,无论你是从XML配置文件中使用<bean name="...." class="...">还是使用注解的方式注册一个Bean都会被当做一个BeanDefinition存储起来

不多哔哔,接下来再来看ApplicationContext作为一个顶层的Context接口总还是会有牌面的定义一些东西的,里面啊首先就是一堆context的属性获取当前context的id啦,什么ApplicationName啦,启动时间啦,获得父Context啦,但里面定义了getAutowireCapableBeanFactory(突然严肃),这个工厂也是个接口里面定义着配置Bean、创建Bean、销毁Bean和自动装配,简单点来说它就是一个定义着创建bean和注入bean的一个工厂接口,这样说OK嘛?里面还有一点细节我就不补充了

往下走,我们再来稍微关注一下ConfigurableApplicationContext接口,这个接口顾名思义就是一个定义了配置ApplicationContext的接口里面定义了些许接口方法,但暂时并不需要关注那么多,记住refreshgetBeanFactory方法即可,现在不同这2个方法是干啥用的都没关系,你先记住,待会你就知道了。

接下来越来越近了,终于看到了一个不是接口的抽象类AbstractApplicationContext,这个类细节很多,但不用慌,只需要关注我本文所提及即可,仅从本文的角度出发,该抽象类实现了BeanFactorygetBean方法,而这些getBean的实现内容都是通过getBeanFactory().getBean(...)来实现的,并且实现了一个refresh方法,请一定要记住这里实现了refresh,它里面实现了啥,都先不要管,记住就行,本文后面会回到这个方法来的。

接着是GenericApplicationContext我们仅关注主要的(与本文相关的),该类会在实例化的时候就会创建一个DefaultListableBeanFactorybeanFactory属性存起来,之前定义个getBeanFactory实际上返回的就是此处的beanFactory,其中还有一个关键的方法就是registerBeanDefinition

实际上也是调用的beanFactoryregistryBeanDefinition它的实现细节不管,马上就要介绍了,因为接下来就是入口的背后小节了

入口的背后

正式发车,首先来看下如何使用长什么样子?

1
2
3
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("base packages..");

applicationContext.getBean("....")

大致就是这样玩的吧,首先很关键在new AnnotationConfigApplicationContext时发生了什么



先简要说明各自的作用我们在细入从中,所谓从宏观到微观,可能微观中又透入着宏观,道法自然,顺气自然。
首先this(),调用了自身的无参构造函数

实例化了2个属性,只需要关注scanner属性即可它是扫描ClassPath下的BeanDefinition的
接着是scan方法

这个方法就是去调用了scanner.scan方法将外部传入的要扫面的basePackage给传了进去,接着来看下scanner.scan方法

其中也只要关注doScan方法即可,它才是幕后主使

首先老套路遍历传入的basePackages,挨个搜索,暂时只需要关注findCandidateComponents它会根据遍历的每个basePackage去定位出所有需要注册的BeanDefinition来简单看下

没什么好说的直接看scanCandidateComponents

直接聚焦到这个方法中最核心的2段来看下,就是由这里来判断是否有要注册的BeanDefinitioninlucdeFilters中在该类实例化时就会被默认的添加Component注解,所以默认在这里所有Component注解都会被注册

接着回来剩下的那一句判断就更加简单了

最后一个判断看方法名都能看出来,就不用再多解释了吧。。。还是简单解释一下吧(皮这一下很开心),首先类必须不是一个接口其次可以被子类重写,通过条件的就会被add进去最后返回,
再度回到doScan

然后在此处获得beanName然后将拿到的所有BeanDefinition封装成BeanDefinitionHolder,其实这里也是封装了一下,将beanNamealias还有beanDefinition封装了进去最后再调用applyScopedProxyMode

这里我们这样使用默认是使用的NO所以直接返回没什么好说的(PS: 其实我是想说一下的,这里还是有点道道可以说的,日后有机会再说吧对于这里暂时不重要~~)

最后在doScan中调用registerBeanDefinition完成注册

我们接着来看这个registerBeanDefinition干了什么

实际上还是调用了AnnotationConfigApplicationContextregisterBeanDefinition方法再把BeanDefinition传递了过去,我们回到AnnotationConfigApplicationContext来看
registerBeanDefinition,你会发现并木有,还记得它是继承什么类来着吗?没错GenericApplicationContext它里面定义了而它又是调用beanFactoryregisterBeanDefinition方法所以直接看beanFactoryregisterBeanDefinition方法就可以了

别的不管专注于这三句即可,实际上就是定义了一个Map一个List将它存储起来,scan分析完了,有没有发现那里不对现在还只是个BeanDefinition不是一个实例啊不能注入啊有木有,当然了因为还没有分析完,我们在回到AnnotationConfigApplicationContext这里来

因为还有一个refresh方法没有讲啊,它这里也是直接调用AbstractApplicationContext中的refresh方法,从本文的前文中就一直提醒读者记住这个方法,说之前先已知一件事情,就是在默认情况下所有的bean实例化都是单例的和非懒加载,OK现在就是时候来说一下它了,它可是一个至关重要的方法

里面调用了很多个方法来完成这件事,实际上refresh方法就是初始化容器用的它为IOC容器准备了Bean生命周期的管理条件听不懂没关系,我们只关注它其中很小的部分,还记得我说的已知嘛?时候贴代码了

只关注这一句即可,实例化所有单例的非懒加载的bean,迷宫快走到头了,来我们来看看它究竟是何方神圣

不听不听🙉,我们只关注beanFactory.preInstantiateSingletons()即可,继续来看这个beanFactory.preInstantiateSingletons做了什么

实际上这里调用的就是scan注册的beanDefinitionNames有木有啊,然后将它挨个循环出来然后调用getBean

他这里调用了它的父类AbstractBeanFactorygetBean

然后在继续来看doGetBean里干了啥,代码很长只用关注必要的即可

还记得我们已知的条件吗?bean默认的作用域是单例的,所以看这里,这里实际上是先去调用了AbstractAutowireCapableBeanFactorycreateBean方法,同理我们也只需要关注这其中需要关注的点即可

注意了,这个方法里面的细节还是比较多的,我仅简要说明让大家理解即可

接着先看它的createBeanInstance方法,直接看到它的最后一行,一般来说也是最常用的,使用无参构造函数来创建一个实例

接着来看instantiateBean做了啥

此处就是获取一个实例化策略,然后在调用instantiate来进行实例化,这里我们还是已最常用最简单的SimpleInstantiationStrategyinstantiate来说

这里实际上会去调用BeanUtils.instantiateClass另一种CGLIB的方式在简单的实例化策略中是不支持的

接着来看BeanUtils.instantiateClass

没什么好说的,就是直接通过反射创建实例,接着继续回来

createBean之后会调用的一个getSingleton方法,并将创建的bean实例传递进去,这个方法比较长,直接看关键点

对于新的实例都会调用一个addSingleton方法来看下

实际上很简单有木有,就是还是定义了一个Map将实例化的对象存储了起来,再继续回到doGetBean来最后将bean返回回去,简单的来说doGetBean如果实例是单例的,如果没有被实例化过就会帮你实例化一遍,否则就直接获取返回

OK容器的里bean的初始化都已经ok了,那我Autowire的对象是如何注入的呢?实际上在调用doCreateBean中有一个关键的点就是这一段

if (earlySingletonExposure)会将所依赖的还未初始化的bean先给拿到并进行优先初始化,然后再通过populateBean去填充我们所要注入的value,OK,很简单的几句话就把注入的思路给说明白了,如果要具体到里面的细节又是好多道道,这里我已经把源码中注入的思路写明白了,具体细节自个去看吧

接下来最后一个小节

容器中获取一个bean的背后

这个小节还用写么,不就是调用getBean?

1
applicationContext.getBean("xxxx")

还不懂的自个去复看第二个小节~

为了更好的理解,我还写了一个炒鸡简单的实现了一个Spring的demo,有兴趣的可以点我了解一下,顺手可以给个star

本文完

如果你觉得作者写的这篇文章对你有帮助或者收获很大的话,你可以尝试着请我喝一瓶《东方树叶》或者吃一块《鸡胸》来激励我!!!

微信请客

支付宝请客

原文作者:yuequan

原文链接:http://www.lunaspring.com/2018/06/24/spring_init_ioc/

发表日期:June 24th 2018, 10:38:13 pm

更新日期:June 20th 2019, 4:11:28 pm

版权声明:© 月泉 - 邓亮泉 版权所有

CATALOG
  1. 1. 迷宫入口的宏观结构
  2. 2. 入口的背后
  3. 3. 容器中获取一个bean的背后