浅谈Spring中的getBean

    getBean对Spring来说,是一个非常重要的方法,用于实例化对象。比如在获取BeanProcessor的时候,就用到了beanFacroty.getBean来获取BeanProcessor对象。从doGetBean出发。

getSingleton

    这边涉及到了几个类里面的参数先提及一下:

    singletonObjects:用Map类型的该参数来存放beanName和bean实例之间的关系,bean的生命周期已结束(一级缓存);

    singletonCurrentlyInCreation:正在实例化的单例;

    earlySingletonObjects:提前暴露的单例实例,bean的生命周期还未结束(二级缓存);

    singletonFactories:用Map类型的该参数来存放beanName和beanFactory之间的关系(三级缓存);

    先从一级缓存中根据beanName拿值,如果拿到了就返回,没有拿到的话,先去判断下beanName代表的实例是否正在创建,如果是在创建中,那就去二级缓存中拿值,如果没拿到,就去三级缓存中根据beanName获取到BeanFactory对象,通过BeanFactory的getObject拿到实例,然后重新将值赋给二级缓存,再从三级缓存中移除beanName和其BeanFactory,如图1。

图1

    为什么需要这么麻烦设计三次缓存来获取实例,为什么不像自定义Spring一样只要用一个singletonObjects就能解决获取实例的问题?是因为我们在使用Spring的过程中,很多时候涉及到了循环依赖,本次只讲getBean,循环依赖下一次再讲。

    如果getSingleton获取到了实例,还回去判断当前返回的是不是和工厂相关的(beanName有“&”前缀),如果是,但是并不是FactoryBean类型的就会报错。所以现在的获取到的实例要么是个正常的bean,要么是工厂bean。如果是正常的bean就直接返回,如图2:

图2

    如果是工厂bean,先去缓存中尝试获取bean,如果成功了直接返回,如果不成功,调用getObjectFromFactoryBean,如图3。

图3

    最终还是会通过调用doGetObjectFromFactoryBean方法里工厂的getObject操作获取bean实例,如图4:

图4

    不过一般第一次getSingleton是获取不到实例的,走的是else这条路线:

    接下来就是看是否有父类工厂、dependsOn依赖于其他实例的创建,有的话要它们先创建,这边就不看了。往下看代码发现又出现了一个getSingleton方法,里面有四个比较重要的方法:

1、beforeSingletonCreation

图5

    该方法中有两个类参数:inCreationCheckExeclusions(排除正在创建检查的beanName的集合,这个集合目前遇到的比较少,知道有这个属性即可),以及singletonCurrentlyIncreation(这个集合里的都是正在进行实例化的bean,就是实例化还为完成的beanName,防止bean对象在进行循环依赖引用的时候反复创建)。往singletonCurrentlyIncreation中添加正在实例化的beanName。

2、singletonFactory.getObject()

    通过工厂创建实例。

3、afterSingletonCreation

图6

    实例化完成后从singletonCurrentlyIncreation中删除对应的beanName。

4、addSingleton

图7

    二、三级缓存删除对应的beanName,一级缓存添加beanName和bean实例的对应关系,再往registeredSingletons添加beanName代表已经完成的bean实例化。

-----------------------------------------------------------------------------------------------------------------------------------

createBean

    关注doCreateBean,如果是单例,尝试用beanName直接从之前的保存在factoryBeanInstanceCache中获取BeanWrapper,如果不行则调用createBeanInstnce来创建BeanWrapper(BeanWrapper是对bean的包装,是bean到BeanDefinition的中间产物,用来为bean做属性填充)。

createBeanInstance

    用于生成BeanWrapper的方法,有三种实现:

图8

    1、如果当前BeanDefinition有instanceSupplier属性,就以此来生成BeanWrapper,在使用Spring的过程中,可以通过如图9所示来使用:

图9

    2、如果有factory-method属性,调用instantiateFactoryMethod生成BeanWrapper。

    3、对于没有上面两个属性的,也可以通过构造函数进行实例化,分为有参构造函数和无参构造函数。有参构造函数先通过determineConstructorsFromBeanPostProcessors获取到含有@Autowired注解的构造函数,然后调用autowireConstructor生成BeanWrapper。无参构造函数直接调用instantiateBean生成BeanWrapper。

applyMergedBeanDefinitionPostProcessors

    用来对类中的注解进行装配,是通过BeanPostProcessor的postProcessMergedBeanDefinition进行装配,列举下面两个比较重要的实现类:

CommonAnnotationBeanPostProcessor

    这个类中的解析分为两部分:一个是postProcessMergedBeanDefinition来扫描@PostConstruct和@PreDestory注解;一个是findResourceMetadata来扫描@Resource注解。

postProcessMergedBeanDefinition:先根据beanType去缓存lifecycleMetadataCache中寻找LifecycleMetadata,如果未找到则通过buildLifeCycleMetadata找到里面的initMethod和的storyMethods方法,以及类本身,会被包装成LifeCycleMetadata对象,如图10,并放入缓存lifeCycleMetadataCache,如图11:

图10
图11

然后通过checkConfigMembers对当前的BeanDefinition进行扫描,把其中的initMethod和的storyMethod拿出来,放入类参数checkedInitMethods和checkedDestoryMethods中,如图12:

图12

findResourceMetadata和checkedConfigMembers:和上面扫描初始化和销毁方法一样,也有一个缓存参数injectionMetadataCache,如果找不到也是要通过buildResourceMetadata构建,将里面有@Resource注解的元素放入injectedElements中,如图13,构建完后放入缓存,返回InjectionMetadata,如图14:

图13
图14

    通过checkConfigMembers对当前的BeanDefinition进行扫描,把含有@Resource注解的属性或者方法拿出来,遍历的就是上一步存到类中的元素集合,然后放到类参数checkedElements中,如图15:

图15

AutowiredAnnotationBeanPostProcessor

    功能就是用来扫描方法或者属性上的@Autowired注解,通过buildAutowiringMetadata方法构建元数据,将其放入injectionMetadataCache缓存中。然后对BeanDefinition进行checkConfigMembers扫描,把含有@Autowired注解的属性或者方法拿出来,放到类参数checkedElements中,和上述方式差不多,如图16:

图16

earlySingletonExposure

    用来判断是否支持进行提前暴露操作,会根据当前BeanDefinition是否单例、是否允许循环依赖(一般允许),以及在singletonCurrentlyInCreation中是否存在当前beanName,如图17:

图17

    如果都符合条件,调用getEarlyBeanReference获取到提前暴露的实例,并且在三级缓存中添加beanName和单例工厂,移除二级缓存中提前暴露的实例,如图18:

图18

populateBean

    这是IOC/DI依赖注入的核心方法,直接拉下来看关于BeanPostProcessor的应用,如图19:

图19

    关注postProcessProperties方法,该方法是之前通过postProcessMergedBeanDefinition扫描注解并收集后对其元数据执行inject方法。以@Autowired为例,如图20:

图20

    首先根据beanName寻找到InjectionMetadata,通过element.inject方法对已经收集到的注解进行反射调用,比如注解在字段上,则会使用field.set(bean, value);在方法上,则会使用method.invoke(bean, arguements)。

图21

    对于在xml中配置的属性,如<property>标签内部属性的依赖注入,则是通过下面applyPropertyValues方法实现的,如图22,因为现在基于XML配置的很少使用了,就不必去看是怎么添加属性的。

图22

initializeBean

    这个方法是在实例化和IOC/DI注入后的操作,首先是对某些Aware接口的调用,如图23:

图23

    然后调用了applyBeanPostProcessorsBeforeInitialization对类中某些特殊方法的调用,也是会先通过beanName拿到元数据,然后通过invoke方法进行反射调用,如图24:

图24

    再是通过invokeInitMethods方法,对实现了InitializingBean的类中的afterProperties方法的调用:

    最后调用applyBeanPostProcessorsAfterInitialization方法,和before一样,进行反射调用。

registerDisposableBeanIfNecessary

    在bean创建完成后都会调用这个方法,因为用到的都是单例模式的,所以直接看如下代码:

图25

    DisposableBeanAdapter对象是来负责bean销毁的类,在new的时候会去扫描BeanDefinition中的destory-method属性,以及其余属性来构建对象,然后以beanName为key,生成的DisposableBeanAdapter对象为value,放进disposableBeans中。

图26

----------------------------------------------------------------------------------------------------------------------------------

getObjectForBeanInstance

    里面有个BeanFactoryUtils.isFactoryDereference方法,判断传进去的name是否携带了“&”作为name开头,只有工厂beanName才会有这种结构,但如果该实例不是FactoryBean,还是会抛出异常。如果没有携带“&”作为name的开头,并且不是FactoryBean,表明这是个普通实例,直接返回;如果是工厂实例,则需要调用getObject从工厂中生产实例后返回,上面已经有类似的流程了,就不截图了。

-----------------------------------------------------------------------------------------------------------------------------------

Prototype作用域的BeanDefinition

    逻辑和单例的差不多,但是不像单例这么复杂,需要考虑重复创建的问题;多例作用域只要执行了createBean,就是一个新的实例,如图27:

图27

-----------------------------------------------------------------------------------------------------------------------------------

埋点

    在看源码的过程中我们经常看到如图28所示的代码,可以看出BeanPostProcessor接口类型是对具有某种特定功能的埋点,根据对当前的BeanPostProcessor进行instanceof判断,用来过滤掉功能不相关的BeanPostProcessor,然后执行特定的方法。

图28

下面来看一下有哪些地方用到了这类埋点:

1、MergedBeanDefinitionPostProcessor:图28调用的方法是postProcessorMergedBeanDefinition,收集@Resource、@PostConstruct、@PreDestory、@Autowired、@Value注解的方法和属性。

2、SmartInstantiationAwareBeanPostProcessor:调用的方法是determineCandidateConstructors,用来获取@Autowired注解的方法和属性;而在循环依赖中解决bean提前暴露的实例,则是通过getEarlyBeanReference,如图29:

图29

3、InstantiationAwareBeanPostProcessor:调用的方法是postProcessorAfterInstantiation,其中的continueWithPropertyPopulation为true代表可以进行依赖注入;postProcessProperties则是IOC/DI依赖注入的inject方法的埋点,如图30:

图30
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容