关于bean的populateBean中获取ApplicationContext的源码过程

前言

“风吹起如花般破碎的流年,而你的笑容摇晃摇晃,成为我命途中最美的点缀,看天,看雪,看季节深深的暗影。”- - 张爱玲

一、SSA

  • 我们都知道spring-framework的框架默认对bean的管理都是单例的,如果一个单例的类A里需要调用一个原型对象B,如果在A中@Autowired注入了B这个原型对象,那么从A中每次get到的B都是同一个(下面会证明这个现象),这里就发生了错误。
  • 此时我们可以使用在A中注入@Autowired ApplicationContext或者实现ApplicationContextAware接口拿到ApplicationContext,再从ApplicationContext中获取想要的bean。spring启动完成了,经过遍历spring的单例池并没有发现ApplicationContext这个对象。那ApplicationContext从哪里来又是怎样被@Autowired注解注入bean的呢,接下面从spring源代码看看。

二、GTTP

1.看下基本类,证明单例对象里@Autowired注入原型对象出现的问题

先看下笔者的基本类:
在这里插入图片描述
运行测试类看到如下结果:在这里插入图片描述

23:54:53.020 [main] INFO  dev - spring中的D--com.applicationContext.pojo.D@2adddc06
23:54:53.023 [main] INFO  dev - D中的A--com.applicationContext.pojo.A@6a84bc2a
23:54:53.023 [main] INFO  dev - spring中的A--com.applicationContext.pojo.A@5183d589
23:54:53.023 [main] INFO  dev - spring中的D--com.applicationContext.pojo.D@2adddc06
23:54:53.023 [main] INFO  dev - D中的A--com.applicationContext.pojo.A@6a84bc2a
23:54:53.024 [main] INFO  dev - spring中的A--com.applicationContext.pojo.A@511d5d04
  • 从 spring中获取的A对象第一个是A@5183d589,第二个是A@511d5d04。这符合我们在A的添加上注解@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE),表示为原型对象每次获取都是不同的对象。
  • 但是在同一个D@2adddc06对象中get得到的A却是同一个A@6a84bc2a,这里就出现了错误。

2.从ApplicationContext中获取原型对象

从上面的图中也可以得出结论:从AnnotationConfigApplicationContext ac获取的A和D都是同一对象,AnnotationConfigApplicationContext是ApplicationContext实现类的子类,下面展示下获取ApplicationContext的代码方式 ,但不仅限于这两种 @Autowired 手动注入 和 实现 ApplicationContextAware接口获得

在这里插入图片描述

3.从spring源码中查看属性注入populateBean环节如何注入ApplicationContext

在spring的refresh()方法里有几个于此处比较重要的方法,笔者此处重点提出来

  1. prepareBeanFactory()方法。这个方法顾名思义就是初始化spring容器的准备工作,给bean工厂注册一些后面会用到的重要组件。
  2. invokeBeanFactoryPostProcessors(),顾名思义是解析bean工厂的后置处理器。可以理解为spring会为每一个注入容器的对象新建一个建模类BeanDefinition,在这个方法内部会依次调用所有的bean工厂后置处理器对这些BeanDefinition进行属性填充。
  3. registerBeanPostProcessors(),注册bean的后置处理器,这个方法的作用是在初始化bean的时候程序员仍然可以通过实现BeanPostProcessor及其子类接口的重写方法来干扰bean的实例化过程,如根据beanName控制其是否被允许属性注入,在初始化这个bean之前之后能额外的定义一些操作等。
  4. finishBeanFactoryInitialization(beanFactory),初始化所有的非懒加载的单例bean。spring的循环依赖将在这里发生,本文的属性注入ApplicationContext也发生在这里,下面debug调试相关过程。
    在这里插入图片描述
  • 可以看到spring在初始化所有的bean之前先冻结了所有的beanDefinition,beanFactory.freezeConfiguration()表示要开始初始化了,将来要初始化的所有bean对应的建模类beanDefinition都标记为冻结。
  • 在经过单例池中查找Object sharedInstance = getSingleton(beanName);出来值为null后。
  • spring判断了是否是单例mbd.isSingleton(),便开始createBean(beanName, mbd, args);
  • 参数beanName是在new AnnotationConfigApplicationContext()容器时实例化出来的 BeanNameGenerator 名字解析器解析出来添加到beanDefinitionNames的list集合里的,这个集合包含程序员registerBeanDefinition注册的和扫描注解得到的bean
  • mbd就是合并后的beanDefinition,概念比较复杂此处不深入阐述。
  • args顾名思义就是实例化bean需要的相关参数。

在这里插入图片描述

可以看到初始化对象此时走到了populateBean属性填充的环节,笔者在此处打个断点并设置beanName为b的时候停止.

在这里插入图片描述
可以看到此时初始化的对象beanName为b,此处到了属性填充的环节。instanceWrapper为实例化出来的B对象的包装类,尚未进行属性填充。instanceWrapper里正真的对象是wrappedObject 它的值为{B@3713},这个对象有一个属性applicationContext=null,可以看到B中@Autowired手动注入的ApplicationContext applicationContext正等待被填充。

在这里插入图片描述

可以看到此时在遍历InstantiationAwareBeanPostProcessor 类型的bean的处理器来处理,此时查看下面的属性信息applicationContext依然为null

在这里插入图片描述
当遍历到AutowiredAnnotationBeanPostProcessor后置处理器时我们知道这个applicationContext会被它处理,因为它是被@Autowired注入的
在这里插入图片描述
AutowiredAnnotationBeanPostProcessor后置处理器在经过四次常规bean获取的方式之后都为null,最后去单例池之外的一个Map集合中去找
在这里插入图片描述
可以看到在这里会从DefaultListableBeanFactory的类成员变量resolvableDependencies集合中遍历是否有类型匹配的。调试查看发现里面有四个对象,正好有ApplicationContext的,于是放入Map<String, Object> result集合返回。

在这里插入图片描述
可以看到在经过 field.set(bean, value);方法后wrappedObject 这个对象的属性applicationContext有值了。说明@Autowired成功在B中注入了ApplicationContext(spring的上下文对象);那么这个上下文对象是何时被放入resolvableDependencies集合中的呢。答案是上面提到的四个比较重要的点的第一个方法中放入的,下面也可以看下放入的源码过程。

在这里插入图片描述

在beanFactory.registerResolvableDependency(ApplicationContext.class, this);过程中 向resolvableDependencies放入了ApplicationContext对象。


总结

总结:spring的体系比较庞大,需要把众多碎片知识点串联起来才会豁然开朗,否则难以理解spring这样做的前因后果,也足以窥见前辈们的智慧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值