Spring笔记:Spring IOC介绍与源码剖析

文章目录

  • 前言
  • 一、IOC基础
    • 1.BeanFactory与ApplicationContext区别
    • 2.关于xml模式的一些东西
      • 2.1 Bean不同作用范围的生命周期
      • 2.2 Bean标签属性
      • 2.3 DI依赖注入的xml配置
        • (1) 按照注入的方式分类
        • (2) 按照注入的数据类型分类
    • 3.注解
  • 二、IOC高级特性
    • 1. lazy-Init 延迟加载
    • 2. FactoryBean 和 BeanFactory
    • 3.后置处理器
      • 3.1 BeanPostProcessor
      • 3.2 BeanFactoryPostProcessor
    • 4.关于bean生命周期执行过程
  • 三、Spring IOC源码剖析
    • 1. Spring IOC容器初始化主体流程
    • 2. Bean生命周期关键时机点
      • 2.1 分析 Bean 的创建是在容器初始化时还是在 getBean 时
      • 2.2 分析构造函数调用情况
      • 2.3 分析 InitializingBean 之 afterPropertiesSet 初始化方法调用情况
      • 2.4 分析BeanFactoryPostProcessor 初始化和调用情况
      • 2.5 分析 BeanPostProcessor 初始化和调用情况
      • 2.6 总结
    • 3. Spring IOC容器初始化主流程
    • 4. BeanFactory创建流程
      • 4.1 获取 BeanFactory 子流程
      • 4.2 BeanDefinition加载解析及注册子流程
        • (1) 该子流程涉及到如下几个关键步骤
        • (2) 过程分析
        • (3) 时序图
    • 5. Bean创建流程
    • 6. lazy-init 延迟加载机制原理
      • 6.1 lazy-init 延迟加载机制分析
      • 6.2 总结
    • 7. Spring IOC循环依赖问题
      • 7.1 什么是循环依赖
      • 7.2 循环依赖处理机制


前言

好记性不如烂笔头,做记录亦是复习。加油
笔记:
Spring介绍与核心思想(IOC/AOP)
自定义注解实现IOC容器笔记
AOP介绍与源码剖析


一、IOC基础

Spring笔记:Spring IOC介绍与源码剖析_第1张图片

1.BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中IOC容器的顶层接口,只是用来定义⼀些基础功能,定义⼀些基础规范,而ApplicationContext是它的⼀个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能。
可以称BeanFactory为SpringIOC的基础容器,ApplicationContext为容器的高级接口,比BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等。
Spring笔记:Spring IOC介绍与源码剖析_第2张图片
Java环境下启动IoC容器
ClassPathXmlApplicationContext:从类的根路径下加载配置文件
FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
AnnotationConfigApplicationContext:纯注解模式下启动Spring容器

Web环境下启动IoC容器
1.从xml启动容器

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
 <display-name>Archetype Created Web Application</display-name>
 <!--配置Spring ioc容器的配置⽂件-->
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:applicationContext.xml</param-value>
 </context-param>
 <!--使⽤监听器启动Spring的IOC容器-->
 <listener>
 <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
 </listener>
</web-app>

2.从配置类启动容器

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
 <display-name>Archetype Created Web Application</display-name>
 <!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
 <context-param>
 <param-name>contextClass</param-name>
 <paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli
cationContext</param-value>
 </context-param>
  <!--配置启动类的全限定类名-->
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>com.lagou.edu.SpringConfig</param-value>
 </context-param>
 <!--使⽤监听器启动Spring的IOC容器-->
 <listener>
 <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
 </listener>
</web-app>

2.关于xml模式的一些东西

2.1 Bean不同作用范围的生命周期

在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它支持配置的方式改变作用范围。

单例模式:singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象生命周期与容器相同。
多例模式:prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就⼀直活着。
对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
一句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。

2.2 Bean标签属性

id: 用于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯一。
class: 用于指定创建Bean对象的全限定类名。
name: 用于给bean提供⼀个或多个名称。多个名称用空格分隔。
factory-bean: 用于指定创建当前bean对象的工厂bean的唯⼀标识。当指定了此属性之后,class属性失效。
factory-method: 用于指定创建当前bean对象的工厂方法,如配合factory-bean属性使用,则class属性失效。如配合class属性使用,则方法必须是static的。
scope: 用于指定bean对象的作用范围。通常情况下就是singleton。当要用到多例模式时,可以配置为prototype。
init-method: 用于指定bean对象的初始化方法,此方法会在bean对象装配后调用。必须是⼀个无参方法。
destory-method: 用于指定bean对象的销毁方法,此方法会在bean对象销毁前执行。它只能为scope是singleton时起作用。

2.3 DI依赖注入的xml配置

(1) 按照注入的方式分类

构造函数注入:利用带参构造函数实现对类成员的数据赋值
依赖注入的配置实现之构造函数注入,就是利用构造函数实现对类成员的赋值。类中提供的构造函数参数个数必须和配置的参数个数一致,且数据类型匹配。同时要注意的是,当没有无参构造时,则必须提供构造函数参数的注入,否则Spring框架会报错。

使用构造函数注入的标签是 constructor-arg ,该标签有如下属性:
name:用于给构造函数中指定名称的参数赋值。
index:用于给构造函数中指定索引位置的参数赋值。
value:用于指定基本类型或者String类型的数据。
ref:用于指定其他Bean类型的数据。写的是其他bean的唯一标识。

set方法注入:通过类成员的set方法实现数据的注入。
依赖注入的配置实现之set方法注入,就是利用字段的set方法实现赋值的注入方式。此种方式在实际开发中是使用最多的注入方式
使用set方法注入使用 property 标签,该标签属性如下:
name:指定注入时调用的set方法名称。(注:不包含set这三个字母,druid连接池指定属性名称)
value:指定注入的数据。它支持基本类型和String类型。
ref:指定注入的数据。它支持其他bean类型。写的是其他bean的唯⼀标识

(2) 按照注入的数据类型分类

基本类型和String:注入的数据类型是基本类型或者是字符串类型的数据。
其他Bean类型:注入的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
复杂类型(集合类型):注入的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型


3.注解

@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引入外部属性配置文件
@Import 引入其他配置类
@Value 对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息
@Bean 将方法返回对象加入 SpringIOC 容器


二、IOC高级特性

1. lazy-Init 延迟加载

Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前实例化意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton bean。

<bean id="testBean" class="cn.njm.LazyBean" />
该bean默认的设置为: 
<bean id="testBean" calss="cn.njm.LazyBean" lazy-init="false" />
lazy-init为false则立即加载,表示在spring启动时,立刻进行实例化。
如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,
那么可以将bean设置为延迟实例化。
<bean id="testBean" calss="cn.njm.LazyBean" lazy-init="true" />
lazy-init为true的 bean 将不会在 ApplicationContext 启动时提前被实例化,
而是第⼀次向容器通过 getBean 索取 bean 时实例化的。

如果⼀个设置了立即加载的 bean1,引用了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,而 bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调用时才被实例化的规则。
也可以在容器层次中通过在元素上使用 “default-lazy-init” 属性来控制延时初始化。如下面配置:

<beans default-lazy-init="true">
 <!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,而是调用 getBean 方法实例化。

应用场景
1.开启延迟加载⼀定程度提高容器启动和运转性能。
2.对于不常使用的 Bean 设置延迟加载,这样偶尔使用的时候再加载,不必要从一开始该 Bean 就占用资源。


2. FactoryBean 和 BeanFactory

BeanFactory接口是容器的顶级接口,定义了容器的⼀些基础行为,负责生产和管理Bean的⼀个工厂,具体使用其实是使用它下面的一些子接口类型,比如ApplicationContext;

Spring中Bean有两种,⼀种是普通Bean,⼀种是工厂Bean(FactoryBean),FactoryBean可以生成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。FactoryBean使用较多,尤其在Spring框架⼀些组件中会使用,还有其他框架和Spring框架整合时使用。

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
 @Nullable
 // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器
的单例对象缓存池中Map
 T getObject() throws Exception;
 @Nullable
 // 返回FactoryBean创建的Bean类型
 Class<?> getObjectType();
 // 返回作⽤域是否单例
 default boolean isSingleton() {
 return true;
 } 
}

//用到的地方
public class CompanyFactoryBean implements FactoryBean<Company> {
...........
}
//获取FactoryBean产⽣的对象
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);

//获取FactoryBean,在获取FactoryBean的时候需要在id之前添加“&”
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);

3.后置处理器

Spring提供了两种后处理bean的扩展接口,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使用上是有所区别的。
BeanFactoryPostProcessor:在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做⼀些事情
BeanPostProcessor:在Bean对象实例化(并不是Bean的整个生命周期完成)之后可以使用BeanPostProcessor进行后置处理做一些事情
注意: 对象不一定是springbean,而springbean一定是个对象

3.1 BeanPostProcessor

路径:package org.springframework.beans.factory.config;
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean
Spring笔记:Spring IOC介绍与源码剖析_第3张图片
该接口提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是什么方法,类似在定义bean时,定义了init-method所指定的方法。
定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每个bean的实例,第二个参数是每个bean的name或者id属性的值。所以可以通过第二个参数,来判断将要处理的具体的bean。
注意: 处理是发生在Spring容器的实例化和依赖注入之后。

3.2 BeanFactoryPostProcessor

路径:package org.springframework.beans.factory.config;
BeanFactory级别的处理,是针对整个Bean的工厂进行处理
Spring笔记:Spring IOC介绍与源码剖析_第4张图片
此接口只提供了一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法
Spring笔记:Spring IOC介绍与源码剖析_第5张图片
其中有个方法名为getBeanDefinition的方法,可以根据此方法,找到我们定义bean 的BeanDefinition对象。然后可以对定义的属性进行修改,以下是BeanDefinition中的方法
Spring笔记:Spring IOC介绍与源码剖析_第6张图片
方法名字类似bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当拿到BeanDefifinition对象时,可以手动修改bean标签中所定义的属性值。
BeanDefifinition对象:在 XML 中定义的 bean标签,Spring 解析 bean 标签成为一个 JavaBean,这个JavaBean 就是 BeanDefifinition
注意: 调用 BeanFactoryPostProcessor 方法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefifinition对象

4.关于bean生命周期执行过程

  1. 根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
  2. 利用依赖注入完成 Bean 中所有属性值的配置注入。
  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  6. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法postProcessAfterInitialization()。此时 Bean 已经可以被应用系统使用了。
  10. 如果在bean标签中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IOC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在bean标签中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

注意: Spring 为 Bean 提供了细致全面的生命周期过程,通过实现特定的接口或 bean>标签的属性设置,都可以对 Bean 的生命周期过程产生影响。虽然可以随意配置 的属性,但是建议不要过多地使用 Bean 实现接口,因为这样会导致代码和 Spring 的聚合过于紧密。


三、Spring IOC源码剖析

好处: 提高培养代码架构思维、深入理解框架
原则:
定焦原则:抓主线
宏观原则:站在上帝视角,关注源码结构和业务流程(淡化具体某行代码的编写细节)读源码的方法和技巧
断点(观察调用栈)
反调(Find Usages)
经验(spring框架中doXXX,做具体处理的地方)

Spring源码构建步骤:
1.从github下载源码
2.安装gradle 5.6.3(类似于maven) Idea 2019.1 Jdk 11.0.5
3.导入(耗费⼀定时间)
4.编译工程(顺序:core-oxm-context-beans-aspects-aop)工程—>Tasks—>other—>compileTestJava
Spring笔记:Spring IOC介绍与源码剖析_第7张图片

1. Spring IOC容器初始化主体流程

IOC容器是Spring的核心模块,抽象了对象管理、依赖关系管理的框架解决方案。Spring 提供了很多的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IOC 容器 必须遵从的⼀套原则,具体的容器实现可以增加额外的功能,比如常用到的ApplicationContext,更具体的实现比如 ClassPathXmlApplicationContext 包含了解析 xml 等⼀系列的内容,AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。IOC容器继承体系非常聪明,需要使用哪个层次用哪个层次即可,不必使用功能大而全的。
BeanFactory 顶级接口方法栈如下:
Spring笔记:Spring IOC介绍与源码剖析_第8张图片
BeanFactory 容器继承体系:
Spring笔记:Spring IOC介绍与源码剖析_第9张图片

«interface» BeanFactory «interface» AutowireCapableBeanFactory «interface» ListableBeanFactory «interface» HierarchicaIBeanFactory «interface» ConfigurableBeanFactory «interface» ApplicationContext «interface» MessageSource «interface» ResourceLoader «interface» ApplicationEventPublisher «interface» ConfigurableApplicationContexe WebApplicationContext «interface» ThemeSource

通过其接口语设计,可以看到使用的 ApplicationContext 除了继承BeanFactory的子接口,还继承了ResourceLoader、MessageSource等接口,因此其提供的功能也就更丰富了。


2. Bean生命周期关键时机点

以 ClasspathXmlApplicationContext 为例,深入源码查看 IOC 容器的初始化流程。
思路:创建⼀个类 LagouBean ,让其实现几个特殊的接口,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。

LagouBean类

public class LagouBean implements InitializingBean {
    /**
     * 构造函数
     */
    public LagouBean() {
        System.out.println("LagouBean 构造器...");
    }

    /**
     * InitializingBean 接⼝实现
     */
    public void afterPropertiesSet() throws Exception {
        System.out.println("LagouBean afterPropertiesSet...");
    }
}

BeanPostProcessor 接口实现类

public class MyBeanPostProcessor implements BeanPostProcessor {
    public MyBeanPostProcessor() {
        System.out.println("BeanPostProcessor 实现类构造函数...");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if ("lagouBean".equals(beanName)) {
            System.out.println("BeanPostProcessor 实现类
                    postProcessBeforeInitialization ⽅法被调⽤中......");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if ("lagouBean".equals(beanName)) {
            System.out.println("BeanPostProcessor 实现类
                    postProcessAfterInitialization ⽅法被调⽤中......");
        }
        return bean;
    }
}

BeanFactoryPostProcessor 接口实现类

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public MyBeanFactoryPostProcessor() {
        System.out.println("BeanFactoryPostProcessor的实现类构造函数...");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory
                                               beanFactory) throws BeansException {
        System.out.println("BeanFactoryPostProcessor的实现⽅法调⽤中......");
    }
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<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 https://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="lagouBean" class="com.lagou.LagouBean"/>
 <bean id="myBeanFactoryPostProcessor" class="com.lagou.MyBeanFactoryPostProcessor"/>
 <bean id="myBeanPostProcessor" class="com.lagou.MyBeanPostProcessor"/>
 
</beans>

IOC 容器源码分析用例

/**
 * Ioc 容器源码分析基础案例
 */
@Test
public void testIoC(){
        ApplicationContext applicationContext=new
        ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        LagouBean lagouBean=applicationContext.getBean(LagouBean.class);
        System.out.println(lagouBean);
}

2.1 分析 Bean 的创建是在容器初始化时还是在 getBean 时

Spring笔记:Spring IOC介绍与源码剖析_第10张图片
根据断点调试,可以发现,在未设置延迟加载的前提下,Bean 的创建是在容器初始化过程中完成的。

2.2 分析构造函数调用情况

Spring笔记:Spring IOC介绍与源码剖析_第11张图片
观察调用栈:Spring笔记:Spring IOC介绍与源码剖析_第12张图片
通过如上观察,可以发现构造函数的调用时机在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory)处;

2.3 分析 InitializingBean 之 afterPropertiesSet 初始化方法调用情况

Spring笔记:Spring IOC介绍与源码剖析_第13张图片
观察调用栈:
Spring笔记:Spring IOC介绍与源码剖析_第14张图片
通过如上观察,可以发现 InitializingBean中afterPropertiesSet 方法的调用时机也是在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);

2.4 分析BeanFactoryPostProcessor 初始化和调用情况

分别在构造函数、postProcessBeanFactory 方法处打断点,观察调用栈发现BeanFactoryPostProcessor 初始化在AbstractApplicationContext类refresh方法的invokeBeanFactoryPostProcessors(beanFactory);
postProcessBeanFactory 调用在AbstractApplicationContext类refresh方法的invokeBeanFactoryPostProcessors(beanFactory);

2.5 分析 BeanPostProcessor 初始化和调用情况

分别在构造函数、postProcessBeanFactory 方法处打断点,观察调用栈发现BeanPostProcessor 初始化在AbstractApplicationContext类refresh方法的registerBeanPostProcessors(beanFactory);
postProcessBeforeInitialization 调用在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);
postProcessAfterInitialization 调用在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);

2.6 总结

根据上面的调试分析,可以发现 Bean对象创建的几个关键时机点代码层级的调用都在AbstractApplicationContext 类 的 refresh 方法中,可见这个方法对于Spring IOC 容器初始化来说相当关键。
汇总如下:

关键点 触发代码
构造器 refresh#finishBeanFactoryInitialization(beanFactory)(beanFactory)
BeanFactoryPostProcessor 初始化 refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanFactoryPostProcessor 方法调用 refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanPostProcessor 初始化 registerBeanPostProcessors(beanFactory)
BeanPostProcessor 方法调用 refresh#finishBeanFactoryInitialization(beanFactory)

3. Spring IOC容器初始化主流程

由上分析可知,Spring IOC 容器初始化的关键环节就在AbstractApplicationContext#refresh() 方法中,可以查看 refresh 方法来俯瞰容器创建的主体流程。

	@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 第⼀步:刷新前的预处理
            prepareRefresh();
             /*
             第⼆步:
             获取BeanFactory;默认实现是DefaultListableBeanFactory
             加载BeanDefition 并注册到 BeanDefitionRegistry
             */
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
            prepareBeanFactory(beanFactory);
            try {
                // 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
                postProcessBeanFactory(beanFactory);
                // 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
                invokeBeanFactoryPostProcessors(beanFactory);
                // 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏
                registerBeanPostProcessors(beanFactory);
                // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
                initMessageSource();
                // 第⼋步:初始化事件派发器
                initApplicationEventMulticaster();
                // 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
                onRefresh();
                // 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
                registerListeners();
                 /*
                 第⼗⼀步:
                 初始化所有剩下的⾮懒加载的单例bean
                 初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
                 填充属性
                 初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
                 调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
                 */
                finishBeanFactoryInitialization(beanFactory);
                 /*
                 第⼗⼆步:
                 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
                件 (ContextRefreshedEvent)
                 */
                finishRefresh();
            }
            
            ......
       
        }
    }

4. BeanFactory创建流程

4.1 获取 BeanFactory 子流程

Spring笔记:Spring IOC介绍与源码剖析_第15张图片

4.2 BeanDefinition加载解析及注册子流程

(1) 该子流程涉及到如下几个关键步骤

Resource定位: 指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML文件,并将其封装成Resource对象。
BeanDefinition载入 : 把用户定义好的Javabean表示为IOC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
注册BeanDefinition到 IOC 容器

(2) 过程分析

1.子流程入口在 AbstractRefreshableApplicationContext#refreshBeanFactory 方法中
Spring笔记:Spring IOC介绍与源码剖析_第16张图片
2.依次调用多个类的 loadBeanDefinitions 方法 —> AbstractXmlApplicationContext —>AbstractBeanDefinitionReader —> XmlBeanDefinitionReader ⼀直执行到XmlBeanDefinitionReader 的 doLoadBeanDefinitions 方法。
Spring笔记:Spring IOC介绍与源码剖析_第17张图片
这里重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions 方法,期间产生了多次重载调用,定位到最后⼀个
Spring笔记:Spring IOC介绍与源码剖析_第18张图片
此处关注两个地方:⼀个createRederContext方法,⼀个是DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,先进入createRederContext 方法看看
Spring笔记:Spring IOC介绍与源码剖析_第19张图片
可以看到,此处 Spring 首先完成了 NamespaceHandlerResolver 的初始化。再进入 registerBeanDefinitions 方法中追踪,调用了DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 方法
在这里插入图片描述
进入 doRegisterBeanDefinitions 方法
Spring笔记:Spring IOC介绍与源码剖析_第20张图片
进入 parseBeanDefinitions 方法
Spring笔记:Spring IOC介绍与源码剖析_第21张图片
进入 parseDefaultElement 方法
Spring笔记:Spring IOC介绍与源码剖析_第22张图片
进入 processBeanDefinition 方法
Spring笔记:Spring IOC介绍与源码剖析_第23张图片
至此,注册流程结束,发现所谓的注册就是把封装的 XML 中定义的 Bean信息封装为BeanDefinition 对象之后放⼊⼀个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition的。
Spring笔记:Spring IOC介绍与源码剖析_第24张图片
可以在DefaultListableBeanFactory中看到此Map的定义

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new
ConcurrentHashMap<>(256);

(3) 时序图

Spring笔记:Spring IOC介绍与源码剖析_第25张图片


5. Bean创建流程

1.通过上面之前的关键时机点分析,得知Bean创建子流程入口在AbstractApplicationContext#refresh()方法的finishBeanFactoryInitialization(beanFactory) 处
Spring笔记:Spring IOC介绍与源码剖析_第26张图片
2.进入finishBeanFactoryInitialization
Spring笔记:Spring IOC介绍与源码剖析_第27张图片
3.继续进入DefaultListableBeanFactory类的preInstantiateSingletons方法,找到下面部分的代码,看到工厂Bean或者普通Bean,最终都是通过getBean的方法获取实例
Spring笔记:Spring IOC介绍与源码剖析_第28张图片
4.继续跟踪下去,进入到了AbstractBeanFactory类的doGetBean方法,这个方法中的代码很多,直接找到核心部分
Spring笔记:Spring IOC介绍与源码剖析_第29张图片
5.接着进入到AbstractAutowireCapableBeanFactory类的方法,找到以下代码部分
Spring笔记:Spring IOC介绍与源码剖析_第30张图片
6. 进入doCreateBean方法看看,该方法关注两块重点区域
(1) 创建Bean实例,此时尚未设置属性
Spring笔记:Spring IOC介绍与源码剖析_第31张图片
(2) 给Bean填充属性,调用初始化方法,应用BeanPostProcessor后置处理器
Spring笔记:Spring IOC介绍与源码剖析_第32张图片


6. lazy-init 延迟加载机制原理

6.1 lazy-init 延迟加载机制分析

普通 Bean 的初始化是在容器启动初始化阶段执行的,而被lazy-init=true修饰的 bean 则是在从容器里第一次进行context.getBean() 时进行触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始化时用,然后对每个BeanDefinition 进行处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进行初始化并依赖注入。

public void preInstantiateSingletons() throws BeansException {
        // 所有beanDefinition集合
        List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
        // 触发所有⾮懒加载单例bean的初始化
        for (String beanName : beanNames) {
            // 获取bean 定义
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            // 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                // 判断是否是 FactoryBean
                if (isFactoryBean(beanName)) {
                    final FactoryBean<?> factory = (FactoryBean<?>)
                            getBean(FACTORY_BEAN_PREFIX + beanName);
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                            @Override
                            public Boolean run() {
                                return ((SmartFactoryBean<?>) factory).isEagerInit();
                            }
                        }, getAccessControlContext());
                    }
                } else {
                     /*
                     如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑和
                     懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
                     */
                    getBean(beanName);
                }
            }
        }
    }

6.2 总结

对于被修饰为lazy-init的bean Spring容器初始化阶段不会进行 init 并且依赖注入,而是当第一次进行getBean时候才进行初始化并依赖注入。
对于非懒加载的bean,getBean的时候会从缓存里头获取,因为容器初始化阶段 Bean 已经初始化完成并缓存了起来了。


7. Spring IOC循环依赖问题

7.1 什么是循环依赖

循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。

依赖
依赖
依赖
A
B
C

注意: 这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是⼀个死循环,除非有终结条件。

Spring中循环依赖场景有:
构造器的循环依赖(构造器注入)
Field 属性的循环依赖(set注入)
其中,构造器的循环依赖问题无法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法。

7.2 循环依赖处理机制

1.单例 bean 构造器参数循环依赖(无法解决)
2.prototype 原型 bean循环依赖(无法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx方法产生循环依赖,Spring都会直接报错处理。
AbstractBeanFactory.doGetBean()方法:

if (isPrototypeCurrentlyInCreation(beanName)) {
 throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
                        curVal).contains(beanName))));
    }

在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进行标记这个beanName正在被创建,等创建结束之后会删除标记。

try {
 //创建原型bean之前添加标记
 beforePrototypeCreation(beanName);
 //创建原型bean
 prototypeInstance = createBean(beanName, mbd, args);
}
finally {
 //创建原型bean之后删除标记
 afterPrototypeCreation(beanName);
}

总结:Spring 不支持原型 bean 的循环依赖。

3.单例bean通过setXxx或者@Autowired进行循环依赖
Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延后设置的,但是构造器必须是在获取引用之前Spring通过setXxx或者@Autowired方法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调用构造器完成对象初始化之后,在调用ClassA的setClassB方法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中
(1) Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。

boolean earlySingletonExposure = (mbd.isSingleton() && 
this.allowCircularReferences &&
 isSingletonCurrentlyInCreation(beanName));
 if (earlySingletonExposure) {
 if (logger.isDebugEnabled()) {
 logger.debug("Eagerly caching bean '" + beanName +
 "' to allow for resolving potential circular references");
 }
 //将初始化后的对象提前已ObjectFactory对象注⼊到容器中
 addSingletonFactory(beanName, new ObjectFactory<Object>() {
	 @Override
	 public Object getObject() throws BeansException {
	 return getEarlyBeanReference(beanName, mbd, bean);
 }
 });
 }

(2) ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
(3)Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中。
(4)ClassB调用setClassA方法,Spring从容器中获取ClassA ,因为第一步中已经提前暴露了ClassA,因此可以获取到ClassA实例(ClassA通过spring容器获取到ClassB,完成了对象初始化操作)。
这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。

你可能感兴趣的:(课程笔记,Spring,java,spring)