当前位置 博文首页 > 无Bug说的博客:spring ioc 容器启动,创建bean,getBean流程

    无Bug说的博客:spring ioc 容器启动,创建bean,getBean流程

    作者:[db:作者] 时间:2021-08-12 15:09

    本篇文章的目的 是  了解 spring ioc 启动 ,创建bean 到获取bean的时候,ioc 都做了些什么
    文章很长,并且我能力有限,慎看,
    之前  看过知乎上有 大佬说过, 带着目的看源码才是学习的正确方法,不要为了 看源码而看源码,
    这样的学习是无效的
    

    测试工具 idea

    测试准备:

    测试类 (javabean) : Person
    Person 实现

    
    public class Person {
        private String name;
        private Integer age;
    
        public Person(){}
     
        public Person(String name,int age){
            this.name = name;
            this.age = age;
        }
        
      /**
       *  	setter and getter
       */
       
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return this.name + "  " + this.age;
        }
    }
    

    配置 spring xml 文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <!--suppress ALL -->
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    	
    	    <!--第一个bean-->
        <bean id="person1" class="com.yg.springSource.Person">
            <property name="name" value="杨小光"></property>
            <property name="age" value="19"></property>
        </bean>
    
    	 <!--第二个bean-->
        <bean id="person2" class="com.yg.springSource.Person">
            <property name="name" value="张三"></property>
            <property name="age" value="20"></property>
        </bean>
    </beans>
    

    测试Test:

    public class SpringSource1 {
        public static void main(String[] args) {
    		//此处打断点 , 深入了解 ioc 容器启动 初始化 单例 bean 的过程
            ApplicationContext context = new ClassPathXmlApplicationContext("springSource.xml");
    
            Person person1 = (Person)context.getBean("person1");
    
            System.out.println(person1);
        }
    }
    

    一切就绪,在上面测试 指定的 地方 打上断点
    debug 开始
    先 F8 step over单步执行 ,可以看到如图
    在这里插入图片描述
    F7 步入 进入refresh 方法:
    在这里插入图片描述
    接着 F8执行 prepareRefresh 方法 , 可以在 控制台看到这个方法 是 初始化了 ioc 的日志部分
    在这里插入图片描述

    接着 F8 直到 执行prepareBeanFactory(beanFactory) 方法,这个方法解析xml 配置文件里的信息在这里插入图片描述
    更详细的信息可以 进入如图所示,很详细的展示每个 bean 的信息,其实看这里可以看到,这里已经大概知道 放bean 的容器到底是什么了
    在这里插入图片描述
    接着 F8 , 执行了 postProcessBeanFactory(beanFactory),这个就是 bean 的后置处理器,还不了解后置处理器的同学 建议赶紧去学习,这个方法大概就是 在 初始化 bean 之前和之后 做出的处理 。

    然后 两个方法,是注册 后置处理器 的,暂时不用 关心
    invokeBeanFactoryPostProcessors(beanFactory);
    registerBeanPostProcessors(beanFactory);、

    后面的initMessageSource() 方法 是支持 spring 国际化功能的

    接下来的 initApplicationEventMulticaster() 方法 是 初始化 事件转换器 的, 因为在 ioc 容器启动的时候,会有与之产生的诸多事件, 为了让这些 事件 被 其它 组件感知,做出相应的处理,就需要这个方法

    然后 onRefresh() 方法暂时无需关心,这个方法是 如果我们设计我们自己的 ioc 容器的时候需要重写的

    registerListeners()方法 注册ioc容器内部的监听器

    然后 非常重要的一个方法, 也可以说是最重要的方法,因为 前面的方法 都没有创建对象,而这个方法才创建了 对象
    finishBeanFactoryInitialization(beanFactory)

    ##执行这个方法之前
    在这里插入图片描述
    ##执行这个方法之后:
    在这里插入图片描述可以看到,ioc中已经存在 两个 单例对象 person1 和 person2 了,所以这个方法是非常关键的,
    到这里,就可以在这个方法打上断点, 重新调试到这个地方,F7看看这个方法的内部是如何实现的

    如图,进入这个方法的内部:
    在这里插入图片描述
    这个方法内部重点关注最后一个方法:
    beanFactory.preInstantiateSingletons() 这个方法是初始化 单实例bean的,之前根据上面所说的,执行finishBeanFactoryInitialization(beanFactory) 会创建单例对象,而方法内部就是这个方法做了这些事情,所以 我们又应该 进入这个方法内部,一探究竟:
    在这里插入图片描述
    如图所示,红圈 里 有一个 list 集合: beanNames , 顾名思义 , 这个 集合 是用来存储 ioc 容器里 bean的 id 名的,也就是 对象名,而 var2 就是这个id名集合的迭代器

    接下来有一个 很长一段 while 循环,这个循环 就是 获取 对象的,一步一步分析:
    在这里插入图片描述
    首先就是一个重要的 对象, RootBeanDefinition bd , bd 这个 对象 就是 一个 bean的定义 ,不理解不要紧,先往下看,继续 F8 执行 , 当执行完了如图这个方法之后:
    在这里插入图片描述
    可以看到 变量里多了一个 bd 的属性 ,而这些属性就是你ioc 容器里 bean 的那些属性,还有非常重要的一处:
    在这里插入图片描述
    这里的 do while 循环 判断 其实 是可以写成这样的:

    if(!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()){
    	........
    } 
    

    相信 已经 对spring 有很多了解的同学,一眼就看出这三个判断条件 是什么意思,刚才说过了 ,bd 是什么,bd 是 一个 bean 的属性 吧, 而这三个条件就是 判断 这个 bean 是否满足这三个条件:
    !bd.isAbstract() 判断当前 bean 否是 是抽象的
    bd.isSingleton 判断当前 bean 是否 是 单例的 (作用域范围为:singleton,如果是多例的就是 prototype)
    !bd.isLayInit() 判断这个bean 是否是 延迟加载的,延迟加载就说明这个 bean 是获取时才加载的
    综上所述,bean 必须满足三个条件, 这个 bean 必须 是单例的 ,非延迟加载的,非抽象的

    继续看,又有 2 个 判断条件 :在这里插入图片描述
    isFactoryBean(beanName) 判断这个 bean 是否是 实现了 FactoryBean 接口的,如果不了解FactoryBean 的的同学,请移步好好了解一下基础,我们在写 bean 的时候,并不是完全直接
    在 ioc 容器中 注入的,有时候是使用了 实现 FactoryBean 接口的bean

    如果没有 实现 FactoryBean 就调用第二个 红圈 里的 getBean方法,这个 getBean 方法就是源头了

    F8执行这个方法,结果如图所示:
    在这里插入图片描述
    就是我们之前见过的,两个 单例 bean 被创建了

    所以这个 getBean 就是关键之处,必须深入了,打断点,重新进入这个getBean方法内部,看看它又做了些什么,是如何创建 bean 的:在这里插入图片描述
    可以看到,所有getBean 都是调用的这个doGetBean方法,打断点接着进入:
    在这里插入图片描述
    来到doGetBean方法内部,,首先第一行 就是 拿到 当前bean 的名称,接着执行一个方法
    getSingleleton(beanName) ,这个方法是从 已经注册的 单实例 bean 缓存里面 拿出 bean , 但是 由于我们是第一次创建并获取, 所以就为 null , 下面 有 两个 判断 就是 判断 是否 成功拿到了bean,如果拿到了bean,就说明不是第一次获取,所以这里面的代码很少,可以直接赋值,而没有拿到bean,就执行else 里面的 代码,创建bean

    最终创建好的单实例bean是保存在 ConcurrentHashMap中的,也就是SingletonObjects,不止 单实例bean,还有其它的非单例bean 保存在其它的集合中。

    在这里插入图片描述
    总结:
    其实可以看到,整个的bean创建的过程,BeanFactory (工厂接口)是负责了底层大部分责任的,最终创建好的bean 被放在 很多集合中,这些集合所在的类 是DefaultSingletonBeanRegistry。 而我们通常使用的ApplicationContext 是BeanFactory的接口,也就是容器接口,我们所使用的一系列操作
    ioc 的功能都是使用ApplicationContext 提供的,所以基于这点来看,BeanFactory (工厂接口) 更关注于底层的实现,容器接口ApplicationContext 更关注于用户(我们) 的使用。

    附: spring 中 使用思想 最广 的模式 是 工厂模式 : BeanFactory 帮用户创建bean

    由于能力有限,写这篇文章是想巩固自己的基础,其中错误,请同学原谅.

    cs