第四部分 Spring IoC源码剖析

前叙:下图为spring创建加载对象的方式,值得注意的是会根据应用不同采用不同的加载方式。

image.png

学习注解的技巧:找xml中标签(属性)和注解的一一对应关系即可。

 

第四部分Spring IoC应用

第1节Spring loC基础

1.1 BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义一些基础功能,定义一些基础规范,而ApplicationContext是它的一个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能的。

通常,也成BeanFactory为SpringIoC的基础容器,ApplicationContext是容器的高级接口,比BeanFactory要拥有更多的功能,比如国际化支持和资源访问(xml,java配置类)等等。

  • BeanFactory:顶尖接口;
  • ApplicationContext:基础顶尖接口的接口,也是我们常用的;

image.png

1.2 纯xml模式

第一种:JavaSE应用

必须引入jar:Spring IoC容器功能的jar:spring-context

不推荐使用FileSystemXmlApplicationContext的方式是因为:文件绝对路径,代码迁移的话这块很麻烦;

image.png

第一种:JavaWeb应用:监听器方式

必须引入jar:Spring Web功能的jar:spring-web

image.png

使用:WebApplicationContext

image.png

1.3 xmI与注解相结合模式

注意:

1)实际企业开发中,纯xml模式使用已经很少了

2)引入注解功能,不需要引入额外的jar

3)xml+注解结合,xml文件依然存在,所以SpringIOC容器的启动仍然从加载xml开始

一般Jdbc这种公共资源利用xml方式,具体实现类用注解方式

image.png

1.4 纯注解模式

改造xml+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml文件,从java的配置类启动。

对应注解

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

第2节Spring IOC高级特性

2.1 lazy-Init延迟加载

Bean对象的延迟加载(延迟创建)

ApplicationContext容器的默认行为是在启动服务器时将所有singleton bean提前进行实例化,提前实例化意味着作为初始化过程的一部分,ApplicationContext实例会创建并配置所有的singleton bean。(通常情况下这是件好事,因为这样在配置中的任何错误就会即刻被发现(否则的话可能要花几个小时甚至几天)。)

比如:

<bean id="testBean" class="com.fhx.TestBean"> 
该bean默认的设置为:
<bean id="testBean" class="com.fhx.TestBean" lazy-init="false"> lazy-init="false" />

lazy-init="false",立退加载, 表示spring启动时,立刻进行实例化。(lazy-init 设置只对scop属性为singleton的bean起作用)

如果不想让singleton bean在ApplicationContext实例初始化时被提前实例化,可以将对象设置为延迟实例化。

<bean id="testBean" class="com.fhx.TestBean" lazy-init="false"> lazy-init="true" />

设置为lazy-init="true"的bean将不会ApplicationContext启动时提前被实例化,而是在第一次向容器通过getBean索取bean时实例化的。

如果一个设置了立即加载的bean1,引入了一个延迟加载的bean2,那么在bean1被实例化的时候,bean2也会被实例化不会进行延迟实例化。

延迟加载应用场景

(1)开启延迟加载一定程度提高容器启动和运转性能;

(2)对应不常使用的Bean设置延迟加载,这样偶尔使用的时候在加载,不必要从一开始该bean就占用资源;

2.2 FactoryBean和BeanFactory

BeanFactory接口是容器的顶级接口,定义了容器的一些基础行为,在BeanFactory中可以创建和管理Spring容器中的Bean,它对于Bean的创建有一个统一的流程。具体使用它下面的子接口类型,比如ApplicationContext;

FactoryBean是一个工厂Bean,可以生成某一类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。

 

Spring中的Bean有两种:一种是普通Bean;一种是工厂Bean(FactoryBean),FactoryBean可以生产某一个类型的Bean实例,也就是说我们可以借助于它自定义Bean的创建过程;

Bean创建的三种方式:静态方法和实例化方法,利用工长bean的方式创建;

 

FactoryBean 接口定义如下:

// 可以让我们自定义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;
    }
}

利用FactoryBean实现一个工长Bean:

/**
 * @description: 自定义FactoryBean实现
 * @author: mj
 **/
@Component
public class OrderFactoryBean implements FactoryBean {
 
    // 自定义bean的创建过程
    @Override
    public Object getObject() throws Exception {
        System.out.println("-----调用OrderFactoryBean.getObject----");
        // 1、直接new
        IOrderService orderService = new OrderServiceImpl();
        return orderService;
 
        // 2、通过动态代理,无需实现类,如 Mybatis的xxxMapper接口
//        OrderMapper bean = (OrderMapper) Proxy.newProxyInstance(OrderFactoryBean.class.getClassLoader(), new Class[]{OrderMapper.class}, new InvocationHandler() {
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                // todo
//                System.out.println(method.getName());
//                return null;
//            }
//        });
//        return bean;
    }
 
    // 返回bean的class类型
    @Override
    public Class<?> getObjectType() {
        return IOrderService.class;
    }
    // 是否单例,默认单例
    @Override
    public boolean isSingleton() {
        return true;
    }
}

2.3后置处理器

Spring提供了两种后处理bean的扩展接口,分别为BeanPostProcessorBeanFactoryPostProcessor,两者在使用上是有所区别的。一个是针对工长bean的后置处理器,一个是针对普通bean处理的;

工厂初始化(BeanFactory) --> Bean对象

在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做一下事情;

在Bean对象实例化(并不是Bean的整个生命周期完成)之后可以使用BeanPostProcessor进行后置处理做一些事情;

注意:对象不一定是springbean,而springbean一定是个对象

2.3.1 BeanPostProcessor

BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean。

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        reutrn bean;
    }
}

该接口提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是什么方法,类似我们再定义bean时,定义了init-method所指定的方法。

定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每个bean的实例,第二个参数是每个bean的name或者id属性的值。所有可以通过第二个参数,来判断将要处理的具体的bean。

注意:处理是发生在Spring容器的实例化和依赖注入之后。

2.3.2 BeanFactoryPostProcessor

BeanFactory级别的处理,是针对整个Bean的工厂进行处理,典型应用:ProperyPlaceholderConfigurer

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

此接口只提供一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法:

image.png

其中有个方法名为getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean的BeanDefinition对象。然后我们可以对定义个属性进行修改。

BeanDefinition对象:该对象很重要,我们在XML中定义的bean标签,Spring解析bean标签成为一个javaBean,这个javaBean就是BeanDefinition对象。

注意:调用BeanFactoryPostProcessor方法时,这时候bean还没有实例化,此时bean刚被解析成BeanDefinition对象。

 

BeanFactoryPostPostProcessor典型应用:

源码里面的PropertyPlaceholderConfigurer 替换属性分割符。 加载资源文件并且替换${}占位符的功能实现。

下面是源码:PropertyResourceConfigurer里面的核心方法:

image.png

第五部分Spring IoC源码深度剖析

读源码:

  • 好处:提高培养代码价格思维,深入理解框架
  • 原则:
    • 定焦原则:抓主线
    • 宏观原则:站在上帝视角,关注源码结构和业务流程(淡化具体某行代码的编写细节)
  • 读源码的方法和技巧:
    • 断点(观察调用栈)
    • 反调(Find Usages)
    • 经验(spring框架中doXXX,就是做具体处理的地方)
  • Spring源码构建
    • 下载源码(github)
    • 安装gradle 5.6.3(类似于maven) idea会自己安装

第1节Spring loC容器初始化主体流程

1.1 Spring loC的容器体系

IoC容器是Spring的核心模块,是抽象了对象关系,依赖关系管理的架构解决方案,Spring提供了很多的容器,其中BeanFactory是顶层容器(根容器),不能被实例化(顶级接口),它定义了所有Ioc容器必须遵从的一套原则,具体的容器实现可以增加额外的功能,比如我们常用的ApplicationContext,其下更具体的实现如ClassPathXmlApplicationContext包含了解析xml等一系列的内容,AnnotationConfigApplicationContext则是包含了注解解析等一系列的内容。Spring IoC容器继承体系非常聪明,需要使用哪个层次用哪个层次即可,不必使用功能大而全的。

// spring ioc 容器源码缝隙基础案例
@Test
public void testIoc() {
    // ApplicationContext是容器的高级接口,BeanFactory(顶级容器/根容器,规范了/定义了容器的基础行为)
    // Spring应用上下文,官方称之为IoC容器(错误的认识:容器就是map而已,准确来说,map是ioc容器的一个成员,
    // 叫做单例池,singletonObjects,容器是一组组件和过程的集合,包括BeanFactory,单例池,BeanPostProcessor等以及之间的协作流程)
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml);
    UserBean userBean = applicationContext.getBean(UserBean.class);
}

BeanFactory顶级接口方法如下:

image.png

  • FACTORY_BEAN_PREFIX 容器里面BeanFactory的对象前缀

BeanFactory容器继承体系:

image.png

通过其接⼝设计,我们可以看到我们⼀贯使⽤的 ApplicationContext 除了继承 BeanFactory 的⼦接⼝, 还继承了ResourceLoader 、 MessageSource 等接⼝,因此其提供的功能也就更丰富了。

1.2 Bean生命周期关键时机点

思路:创建⼀个类LagouBean,让其实现⼏个特殊的接⼝,并分别在接⼝实现的构造器、接⼝⽅法中断点,观察线程调⽤栈,分析出 Bean 对象创建和管理关键点的触发时机。

代码实现:

  • LagouBean类
  • BeanPostProcessor 接⼝实现类
  • BeanFactoryPostProcessor 接⼝实现类
  • applicationContext.xml
  • IoC 容器源码分析⽤例
LagouBean类 
package com.learn.science.ioc;

import org.springframework.beans.factory.InitializingBean;

/**
 * @author MJ
 * @date 2021/5/6
 */
public class LagouBean implements InitializingBean {
    /**
     * 构造函数
     */
    public LagouBean() {
        System.out.println("LagouBean 构造器...");
    }

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

BeanPostProcessor 接⼝实现类
package com.learn.science.ioc;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author MJ
 * @date 2021/5/6
 */
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 接⼝实现类

package com.learn.science.ioc;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

/**
 * @author MJ
 * @date 2021/5/6
 */
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

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

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

IoC 容器源码分析⽤例
package com.learn.science.ioc;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

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

}

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.learn.science.ioc.LagouBean"/>
    <bean id="myBeanFactoryPostProcessor"
          class="com.learn.science.ioc.MyBeanFactoryPostProcessor"/>
    <bean id="myBeanPostProcessor" class="com.learn.science.ioc.MyBeanPostProcessor"/>
</beans>

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

 image.png

根据断点调试,我们发现,在未设置延迟加载的前提下, Bean 的创建是在容器初始化过程中完成的。

( 2 )分析构造函数调⽤情况

image.png

image.png

通过如上观察,我们发现构造函数的调⽤时机在 AbstractApplicationContext 类 refresh ⽅法的fifinishBeanFactoryInitialization(beanFactory)处 ;

( 3 )分析 InitializingBean 之 afterPropertiesSet 初始化⽅法调⽤情况

image.png

观察调⽤栈

image.png

通过如上观察,我们发现 InitializingBean 中 afterPropertiesSet ⽅法的调⽤时机也是在 AbstractApplicationContext类 refresh ⽅法的 fifinishBeanFactoryInitialization(beanFactory);

( 4 )分析 BeanFactoryPostProcessor 初始化和调⽤情况

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

postProcessBeanFactory 调⽤ 在 AbstractApplicationContext 类 refresh ⽅法的

invokeBeanFactoryPostProcessors(beanFactory);

( 5 )分析 BeanPostProcessor 初始化和调⽤情况

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

postProcessBeforeInitialization 调⽤ 在 AbstractApplicationContext 类 refresh ⽅法的 fifinishBeanFactoryInitialization(beanFactory);

postProcessAfterInitialization 调⽤ 在 AbstractApplicationContext 类 refresh ⽅法的

fifinishBeanFactoryInitialization(beanFactory);

( 6 )总结

根据上⾯的调试分析,我们发现 Bean 对象创建的⼏个关键时机点代码层级的调⽤都在AbstractApplicationContext 类 的 refresh ⽅法中,可⻅这个⽅法对于 Spring IoC 容器初始化来说相当

关键,汇总如下:

image.png

断点bebug看调用栈得到:

  • Ioc容器创建管理Bean对象的,Spring Bean是有生命周期的
  • 构造器执行,初始化方法执行,Bean后置处理器before/after方法执行:AbstractApplicationContext#refresh#finishBeanFactoryInitialization
  • Bean工厂后置处理器初始化,方法执行:AbstractApplicationContext#refresh#invokeBeanFactoryProcessors
  • Bean后置处理器初始化,方法执行:AbstractApplicationContext#refresh#registerBeanFactoryProcessors

 

1.3 Spring loC容器初始化主流程

由上分析可知, Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() ⽅法中 ,我们查看 refresh ⽅法来俯瞰容器创建的主体流程,主体流程下的具体⼦流程我们后⾯再来讨论。

// class AbstractApplicationContext
@Override
public void refresh() throws BeansException, IllegalStateException {
    // 对象锁加锁
    synchronized (this.startupShutdownMonitor) {
         // 第⼀步:刷新前的预处理
         prepareRefresh();
         /*
         第⼆步:
         获取BeanFactory;默认实现是DefaultListableBeanFactory
         加载BeanDefition 并注册到 BeanDefitionRegistry
         */
        // BeanFactory对象的创建流程
         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();

        } catch (BeansException ex) {
                    ..
        }
    }
 }

第2节BeanFactory创建流程

2.1获取BeanFactory子流程

// BeanFactory对象的创建流程

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

时序图如下:

image.png

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

( 1 )该⼦流程涉及到如下⼏个关键:

  1. 步骤Resource 定位: 指对 BeanDefifinition 的资源定位过程。通俗讲就是找到定义 Javabean 信息的 XML ⽂件,并将其封装成Resource 对象。
  2. BeanDefifinition 载⼊ :把⽤户定义好的 Javabean 表示为 IoC 容器内部的数据结构,这个容器内部的数据结构就是BeanDefifinition 。
  3. 注册 BeanDefifinition 到 IoC 容器

( 2 )过程的类调用过程分析

Step 1 : ⼦流程⼊⼝在 AbstractRefreshableApplicationContext#refreshBeanFactory ⽅法

Step 2 : 依次调⽤多个类的 loadBeanDefifinitions ⽅法

image.png

image.png

( 3 )时序图

image.png

第3节Bean创建流程

  • 通过最开始的关键时机点分析,我们知道Bean创建⼦流程⼊⼝在AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory)处。

image.png

  • 进⼊finishBeanFactoryInitialization

 image.png

  • 继续进⼊ DefaultListableBeanFactory 类的 preInstantiateSingletons ⽅法,我们找到下⾯部分的代码,看到⼯⼚Bean 或者普通 Bean ,最终都是通过 getBean 的⽅法获取实例

 image.png

  • 继续跟踪下去,我们进⼊到了AbstractBeanFactory类的doGetBean⽅法,这个⽅法中的代码很多,我们直接找到核⼼部分

image.png

  • 接着进⼊到AbstractAutowireCapableBeanFactory类的⽅法,找到以下代码部分

image.png

  • 进⼊doCreateBean⽅法看看,该⽅法我们关注两块重点区域

创建 Bean 实例,此时尚未设置属性

 image.png

第4节lazy-init延迟加载机制原理

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

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

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);
             }
         }
     }
}

总结

  • 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次 进⾏getBean时候才进⾏初始化并依赖注⼊
  • 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经 初始化完成并缓存了起来

第5节Spring loC循环依赖问题

5.1什么是循环依赖

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

image.png

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

Spring 中循环依赖场景有:

  • 构造器的循环依赖(构造器注⼊)
  • Field 属性的循环依赖(set注⼊)

其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring 采⽤的是提前暴露对象的⽅法。

5.2循环依赖处理机制

  • 单例bean构造器参数循环依赖(⽆法解决)
  • 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的循环依赖。

  • 单例bean通过setXxx或者@Autowired进⾏循环依赖

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

 

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);
         }
     });
 }

ClassA 调⽤ setClassB ⽅法, Spring ⾸先尝试从容器中获取 ClassB ,此时 ClassB 不存在 Spring 容器中。Spring 容器初始化 ClassB ,同时也会将 ClassB 提前暴露到 Spring 容器中ClassB调⽤ setClassA ⽅法, Spring 从容器中获取 ClassA ,因为第⼀步中已经提前暴露了ClassA,因此可以获取到 ClassA 实例ClassA通过 spring 容器获取到 ClassB ,完成了对象初始化操作。

这样 ClassA 和 ClassB 都完成了对象初始化操作,解决了循环依赖问题。

 

 

 

来源:拉钩高薪视频

 

Spring Bean的生命周期

Spring 容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。

而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。

了解 Spring 生命周期的意义就在于,可以利用 Bean 在其存活期间的指定时刻完成一些相关操作。这种时刻可能有很多,但一般情况下,会在 Bean 被初始化后和被销毁前执行一些相关操作。

在 Spring 中,Bean 的生命周期是一个很复杂的执行过程,我们可以利用 Spring 提供的方法定制 Bean 的创建过程。

当一个 Bean 被加载到 Spring 容器时,它就具有了生命,而 Spring 容器在保证一个 Bean 能够使用之前,会进行很多工作。Spring 容器中 Bean 的生命周期流程如图 1 所示。

image.png

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 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。

11)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

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

 

 

 

image.png

1)根据配置情况调用Bean构造方法或工厂方法实例化Bean。

2)利用依赖注入完成Bean中所有属性值的配置注入。

image.png

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

松鼠喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值