孙哥Spring源码第六集

第6集 创建Spring对象的过程

【视频来源于:B站up主孙帅suns Spring源码视频】【微信号:suns45】

6.1、关于对象的那些事

image-20230508083745326

在这整个环节中,需要大家注意的是,在整个Spring做了一个人为的划分,这个人为的划分指的是,

创建对象:创建对象,

注入:属性填充,

初始化:容器注入Aware BeanPostProcessorBefore 初始化 initialbean【接口】 | init-method【自定义方法】 BeanPostProcessorAfter了解这些之后 后续会更加便于学习。

6.1.1 获取对象

image-20230508084540416
  • doGetBean为什么要先获取对象呢?从哪获取【怎么获取】对象?这可能是大家需要注意的第一个事情。

  • 作为Spring工厂来说,它不仅仅创建对象 同时 会把曾经 创建过的对象 进行存储,那么后续如果 再需要用到 曾经创建的对象,那么spring就会直接返回,这个指的主要是 scope=singleton。

    • 形式一:<bean id= class= scope>
    • 形式二:@Component @Scope
  • 那么怎么才能达到直接获取的效果呢?那spring一定要把曾经创建的对象进行存储,进行存储用什么结构来存呢?key是id,value是对象,正是因为spring有这个特性,在设计BeanFactory的时候,第一步工作是什么?看看这个对象 在你用之前是不是说我 曾经把它创建过了,那么直接获取这个对象就可以了。

  • 所以在整个设计的过程中 第一步就是获取对象。

6.1.2 获取对象的目的

  • 获取对象的核心目的是什么?只针对于scope=singleton 这种形式,Spring会把曾经创建过的对象进行存储。后续会先获取对象,获取不到再进行创建。 通过什么来存? 通过Map,那这个Map叫什么名字呢?singletonObjects【DefaultSingletonBeanRegistry】key是 beanName,Value是 bean Instance。

6.1.3 创建对象的细节

image-20230508084839397
  • 当获取了对象之后,下面才会涉及到我们所说的创建对象这个过程, 对象的创建又有那么需要大家关注的内容的呢?
  • 整个创建对象 大家要有一个基本的认知,这个基本认知是什么呢,如果创建的对象是简单对象 那就是通过反射调用构造方法来完成,如果是复杂对象就通过FactoryBean实例工厂 静态工厂,整个创建对象的环节就是 创建对象->属性填充->初始化。
  • getSingleton从单例池中获取看有没有,这块的细节还有很多的门道,有很多个池子。
  • 走到if(mbd.isSingleton)这块看注释// Create bean instance,分为两种情况,一种是单实例,一种是多实例,后续还会有一种判断是所谓的其他作用域,如果发现是单实例的话 还是调用了getSingleton第一个参数传入beanName值,第二个参数是一个典型的lamda表达式,它的整个参数是空的,作为lamda表达式的特点来讲,它最终是一定要回调里面的实现的,很类似于匿名内部类的效果。
  • 最核心的是createBean() 真正的对象创建,所以在整个我们所说的整个AbstactFactory.doGetBean谁代表了我们对象的最终创建的呢?
    其实就是crateBean【AbstractAutowiredCapableBeanFactory】这个方法是真正创建对象的方法,这个方法中 最核心的是doCreateBean()【AbstractAutowiredCapableBeanFactory】帮我们完成对象的创建。
  • 对象的创建是 createBeanInstance,
    • image-20230508085015477
  • 其他都不重要 看 populateBean 这个方法的调用 代表的就是属性的填充,这里面的属性填充 我们猜一下有哪些方式呢?包括set注入 包括autowire。

6.1.4 属性填充 autowire注入

image-20230508085146017 image-20230508085212546
  • 1、autowire可以在配置文件中写 <bean autowire=“”,里面的方式有 byName byType constructor这几种方式 总而言之都是在做注入,这是一种,当然现在的这种autowire 基本上已经没人用了。
  • 2、除此这外这种autowire 还可以在哪体现呢?可以在根标记有所体现,我们可以在beans中写 default-autowire=“byType”,
    bean标签和beans标签中的autowire的区别是什么呢?如果我们在某一个bean里面写autowire的话,那么它只针对于当前的这个bean来完成自动注入,如果在beans里面写的话会对beans里面所有的bean标签完成自动注入。
  • 3、@Autowired仅仅是形式上发生了改变,核心和上边两种完全一样。
    这些完成了属性的set注入和自动注入。
  • 有些同学会问那构造注入呢?构造注入一定是伴随 对象创建来完成的,因为在构造方法里面设置了参数,这是需要大家有所区别的。
    这就是我们所说的在创建对象当中 createBean做的第二件事情。

6.1.5 初始化

image-20230508085620427
  • 那么第三件事是什么?
  • 整个的初始化,包括我们所说的三个初始化,第一个容器的初始化,也就是我们所说的各种aware,第二个BeanPostProcessor的调用,第三 initialbean | init-method的调用,这些都属于整个spring容器的初始化工作,
  • 那这个初始化在哪有所体现呢?
  • initialzeBean(),进入这个方法查看,校验,invokeAwareMethods 处理容器注入applyBeanPostProcessorsBeforeInitialization()进入这个方法中,把所有能获取的BeanPostProcessor调用一遍,他会调用postProcessBeforeInitialization(bean对象,beanName)完成完整的容器回调 。 java叫回调,前端会叫hook钩子函数。applyBeanPostProcessorsBeforeInitialization()这个方法 对应的就是BeanPostProcessorBefore。
    • image-20230508085553162
  • 接下来就是invokeInitMethods(beanName,wrappedBean,mbd)对应的就是我们所说的初始化方法,如果初始化在往里面跟的话发现调用了afterPropertiesSet(),后面调用的就是initMethod自定义方法 invokeCustomInitMethod(beanName, bean, mbd);
    这就是我们之前说的先调用接口再调用初始化方法。
    • image-20230508085650289
  • applyBeanPostProcessorsAfterInitialization和applyBeanPostProcessorsBeforeInitialization是一样的
    • image-20230508085843722

6.2 、创建对象 dogetBean的分析

image-20230508090142204
  • 整个获取对象和创建对象的核心就是doGetBean()。在spring的整个工厂当中一定是先获取对象然后再创建对象,这就是我们所说的一个整体性的预览,那这个整体性的预览想让大家达到目的是不是说了解到非常细的代码,主要是为了在开发过程中快速定位 以及后续面试 使用。
  • name:id值,requiredType就是我们最终创建的对象的预期,比如说我们在应用的过程中 beanFactory.getBean(“u”,User.class)进行类型校验,保证我们在获取的时候不需要再强制类型转化了,参数一定要明确。

6.2.1 transformedBeanName(name)分析

image-20230508090435965
  • 第一种情况

    • 1在大多数情况下 name=beanName,
      1如果工厂调用的话,beanFactory.getBean("&s")---->beanName=s name=&s需要大家注意的是这种场景是在使用FactoryBean的情况下 使用&id 最终获取的是FactoryBean类型的实例。
      
  • 第二种情况
    这个时候既出现了id值有出现了name,
    用户在调用的过程 beanFactory.getBean(“u”)最终获得的 beanName=user,name=u。这个方法保证我们永远获得的是我们最需要的id值。

  • 最终创建好的对象 Object bean;

6.2.2 getSingleton(beanName)分析

image-202305080905072441

6.2.2.1 为什么要用beanName
  • 后续再使用的过程中都用beanName 不再用这个name了
    • 为什么?因为beanName才是真正的id值。
    • 如果是做简单的开发 beanName和name都是一样的
    • 这段代码的最终效果是,作为spring来讲一个是获取对象,一个是创建对象,那么显然,那么这个方法的调用显然就是获取Spring曾经创建获得对象
    • 第一次从spring获取null
    • 后续从spring获取
6.2.2.2 从什么地方获取缓存对象呢?
  • 从singletonObjects单例池中获取对象,不仅仅从singletonObjects中获取,还从earlySingletonObjects中获取,还从singletonFactories。这是spring所谓的三级缓存,为什么要这么设计这么庞大的缓存体系呢?
  • 那这就要涉及到深入的话题了
    class A{
    private B b
    }
    class B{
    private A a
    }
    创建A实例 要创建B,创建B的时候要创建A实例,这时候就是一个死循环了。
  • 这时候的破解方式就是通过这三个池子,它的核心目的就是为了解决循环引用的问题,后续会详细的解决这个问题,而且不仅仅是这个循环引用的问题,还会和AOP相结合,有可能A也做代理了,B也做代理了,这时候你需要的是A的代理B的代理,这个时候他们之间的关系是及其复杂的。作为大家来讲,这块先弱化,就是singletonObjects,
    • 三级体系涉及如下的Map
      singletonObjects
      earlySingletonObjects
      singletonFactories
    • 设计这么庞大的目的就是为了解决循环引用的问题这样我们就简单的了解了getSingleton(beanName)从单例池中获取对象,第一次从spring获取null它是没有的,后续获取的时候是有的,乃至创建对象的过程当中,它会有一个循环引用的问题,它涉及到了三级缓存,可以解决这个循环引用的问题。这是大家必须了解到的问题。
6.2.2.3 正在创建中和完整状态
  • 至此还会有一个问题是 Spring创建对象 对象是有2种状态的 一个是完全状态 一个是正在创建中的状态
  • 假定我有一个A类
    A{
    private Stirng id;
    private B b;
    }
    又有一个B类
    B{
    private String id;
    private A a;
    }
  • 我们所说的正在创建中这个状态是什么状态,就是这个类的对象已经有了,new出来了这个A的对象,但是还没有进行属性的填充,以及初始化,这个就是我们说的正在创建中的这个状态,这个类的对象它是有的,但它不具备使用条件,因为它的属性是没有填充的,没有做依赖注入,同时也没有初始化,BeanPostProcessor的加工也没有做,这就叫做正在创建中,
  • 而所谓的完整的状态,就是这一条链路全部打通,从创建对象-属性填充-初始化,这三个全部搞定,这个叫做完整状态,关于这两种状态,spring是严格做了区分,
  • 为什么?如果它不做严格区分后面的程序可能会产生意想不到的效果,什么意思,我要是以这个对象的创建作为我是否来作为使用它的依据的话,很有可能我们拿到的是个空对象,属性是没有的,它是没有经过任何加工的,显然来说这样的对象是不能使用的,我们只能是完整的对象才可以使用。能听得懂吗?
6.2.2.4 抛出循环引用
  • 其实这里面也会涉及到循环引用,在循环引用当中不一定非得让对象成为完全体对象。
    • 这个完全的对象:创建对象 属性填充 初始化。刚才我们讲的 初始化里面有个很重要的环节是什么?是beanPostProcessor,那也就是说做了初始化也就是换句话说 工厂有可能对这个对象做了深加工,那做深加工,意味着什么?这个对象有可能被代理 是AOP对象,这才是完全体状态。仅仅有一个空对象
    • 正常创建中的对象:仅仅有一个简单对象。
  • 这就是目前对于getSingleton的分析,后续我们还会再深讲,等后续讲循环引用的时候会进行更细致的分析。暂时对这个方法建立了认知,还抛出了三个Map。

6.2.3 getObjectForBeanInstance 分析

image-20230508091520538
  • 简述:这块的内容不繁琐,这块表达的是 sharedInstance不为null,这个对象已经创建好了,

  • 代码的作用:这个if分支所表达的内容就是 从缓存池中获得对象 进行普通对象和FactoryBean对象接口的区分 如果是普通对象,则直接返回给调用者,如果是FactoryBean接口的实现了则调用getObject方法返回。

    • if (sharedInstance != null && args == null) {
      	if (logger.isTraceEnabled()) {
      解释日志:是不是正在被创建中?就是评估从缓存池中拿到的对象是否是完全体对象,通过日志来做输出的。其实这块的内容是为了帮助 spring的开发人员来做代码追踪的,这是日志的分支,这块的代码和我们没有关系,但是我们可以通过蛛丝马迹分析到这个对象当前是被创建还是完全体对象,这块代码的整个核心,就是一个简单日志的分析。  		
      
  • 上面的其实不用看,getObjectForBeanInstance这块儿才是核心,把单例对象给你了,把输入的名字给你了,把id给你了,如果是简单情况,这个两个名字是一样的,把bean返回给你。

  • 为什么创建出来的ShadedInstance不是最终返回给用户的,就是我们从单例池中拿到Spring 创建好的,但是返回的不是
    ShadedInstance而是bean,bean和ShadedInstance有什么区别呢?

  • 其实就是getObjectForBeanInstance的核心作用,如果这个方法过了,那么这个分支就过了。为什么ShadedInstance和bean不是一个东西?根据前面学的大家有没有想得到呢?

  • 这是spring的配置文件 applicationContext.xml
    如果是单例就会存到singletonMaps中,sharedInstance=bean,
    这个类的对象会被存到singletonMaps中–>最终从singletonMaps所获取的sharedInstance是FactoryBean,但是我们想获得的是 beanFactory.getObeject()返回的对象核心作用就呼之欲出了,核心作用是如果是简单对象就直接返回,如果是FactoryBean对象就会返回 FactoryBean#getObject()的返回值。

6.2.4 父子容器

image-20230508092405078

代码出现了解决父子容器问题的代码,这里验证一下父子容器怎么用

  • 注意点

    • 父子容器问题,获取父子容器,如果有父容器且这个配置在子容器中没有,那么实力话父容器对应的bean(递归)。

      一旦使用了父子容器的配置信息融合,如果遇到同名的配置内容 使用子容器

  • SpringMVC在注解扫描的时候,不能让子容器扫描所有,只能让子容器扫描Controller层父容器解决的Dao层,子容器解决Controller层。子容器没有去找父容器的东西,接下来就是一个递归的操作。

  • 测试

    • image-20230508092647470
    • image-20230508092855726
    • image-20230508092811565
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值