Spring IOC高级应用和源码剖析

Spring IOC基础

spring主要有3中配置方式:

  1. 纯xml,bean信息定义全部配置在xml中。
  2. xml+注解部分bean使用xml定义,部分bean使用注解定义。
  3. 纯注解模式所以bean都使用注解来定义。

我们会使用beans.xml来定义需要实例化对象的类的全限定类名已经类之间依赖关系描述。BeanFactory是IOC容器通过反射技术来实例化对象并维护对象之间的依赖关系。

在采用了注解的方式创建spring时,javase 应用使用ApplicationContext appplicationContext = new ClassPathXmlApplication("beans.xml");或者new FileSystemXmlApplicationContext("c:/beans.xml");创建spring。javaweb 使用ContextLoaderListener(监听器去加载xml)

在采用纯注解的方式创建spring时,javase 应用使用ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); javaweb 使用ContextLoaderListener(监听器去加载注解配置类)

BeanFactory和ApplicationContext区别

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

BeanFactory为SpringIOC基础容器,ApplicationContext是容器的高级接口,比BeanFactory要拥有更多的功能,比如支持国际化和资源访问等等。

Java环境下启动IOC容器:

  1. ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)。
  2. FileSystemXmlApplicationContext:从磁盘路径上加载配置文件。
  3. 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> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </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> <context-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.lagou.edu.SpringConfig</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>

纯xml模式

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三种方式:

  1. 使用无参构造器,默认情况下,会通过反射调用无参构造器函数来创建对象。如果类中没有无参构造函数,将创建失败。

    <bean id="userService" class="com.guslegend.service.impl.TransferServiceImpl"> </bean>
  2. 使用静态方法创建,使用static修饰的方法。

    <bean id="userService" class="com.guslegend.factory.BeanFactory" factory-method="getTransferService"></bean>

  3. 使用实例化方式创建。

    <bean id="beanFactory" class="com.guslegend.factory.instancemethod.BeanFactory"></bean>
    <bean id="transferService" factory-bean="beanFactory" factormethod="getTransferService"></bean>

Bean的声明周期:在spring框架管理Bean对象的创建时,Bean对象默认是单例的,但是它支持配置的方式改变作用范围。日常开发中我们最多的作用范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。

复制代码
<!--配置service对象-->
<bean id="transferService"
class="com.guslegend.service.impl.TransferServiceImpl" scope="singleton">
</bean>

singleton:对象出生(容器创建时,对象就被创建);对象活着(只要容器存在,对象就一直活着);对象死亡(当容器销毁,对象就被销毁了)。

prototype:对象出生(当使用对象时,就一直活着);对象活着(只要对象在使用,就一直活着);对象死亡(当对象长时间不使用,将被java的垃圾回收器回收了)。

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时起作用。

DI依赖注入的xml配置:

  1. 按照注入的方式分类:构造器注入,set方法注入(使用最多)。
  2. 按照注入的数据类型分类:基本类型和String,复杂类型(集合类型)。

依赖注入的配置使用构造函数注入,类中提供的构造函数参数个数必须和配置的参数个数一致,且数据类型匹配。同时需要注意的是,当没有无参构造时,则必须提供构造函数参数的注入,否则Spring框架会报错。

在使用构造函数注入 时,涉及的标签constructor-arg,该标签有如下属性:

  • name:用于给构造函数中指定名称的参数赋值。
  • index:用于给够着函数中指定索引位置的参数赋值。
  • value:用于指定基本类型或者String类型的数据。
  • ref:用于指定其他Bean类型的数据,写的是其他bean的唯一标识。

使用set方法实现赋值的注入方式,此种方式在实际开发中使用最多的注入方式。使用此种方式注入property标签,该标签属性如下:

  • name:用于给构造函数中指定名称的参数赋值。
  • value:用于指定基本类型或者String类型的数据。
  • ref:用于指定其他Bean类型的数据,写的是其他bean的唯一标识。

注解和xml相结合模式

xml中标签与注解的对应

|-------------------|-----------------------------------------------------------------------------------------------------------|
| XML形式 | 注解形式 |
| 标签 | @Component("accountDao",如果不写括号里面的,默认为类的类名首字母小写)。此外其有三种别名@Controller,@Service,@Repository分别用于控制层,服务层,dao层。 |
| 标签中的scope属性 | @Scope("prototype"),默认为单例 |
| 标签中的init-method属性 | @PostConstruct,初始化后调用的方法 |
| 标签中destory-method | @PreDestory,销毁前调用的方法 |

DI依赖注入的注解实现方式

@Autowired(推荐使用),需要导入org.springframework.beans.factory.annotation.Autowired;

复制代码
public class TransferServiceImpl {
     @Autowired
     private AccountDao accountDao;
}

当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况,这个时候我们需要配合使用@Qualifier使用。

复制代码
public class TransferServiceImpl {
     @Autowired
     @Qualifier("myBean")
     private AccountDao accountDao;
}

@Resouce,由j2EE提供,需要导入 javax.annotation.Resource,其默认按照ByName自动注入

复制代码
public class TransferService {
     @Resource 
     private AccountDao accountDao;
     @Resource(name="studentDao") 
     private StudentDao studentDao;
     @Resource(type="TeacherDao") 
     private TeacherDao teacherDao;
     @Resource(name="manDao",type="ManDao") 
     private ManDao manDao;
}
  • 如果同时指定了 name 和 type,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常。
  • 如果指定了 name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。
  • 如果指定了 type,则从上下文中找到类型匹配的唯一 bean 进行装配,找不到或是找到多个,都会抛出异常。
  • 如果既没有指定 name,又没有指定 type,则自动按照 byName 方式进行装配;

注意在jdk11中@Resource已经被移除如果需要使用,要单独引入jar包

复制代码
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>

纯注解方式

@Configuration注解,表名当前类是一个配置类。

@ ComponentScan注解,替代context:component-scan。

@PropertySource,引入外部属性配置文件

@Import引入其他配置文件

@Value对变量赋值,可以直接赋值,也可以使用${}读取资源配置文件中的信息

@Bean将方法返回对象加入SpringIOC容器

Spring IOC高级特性

lazy-Init延迟加载

ApplicationContext容器的默认行为是在启动服务器是将所有singleton bean提取进行实例化。会创建并配置所以的singletonbean。

复制代码
<bean id="testBean" class="cn.guslegend.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.guslegend.LazyBean" lazy-init="false" />

lazy-init = "false",标识立即加载,表示在spring启动时,立刻进行实例化。

将lazy-true设置为ture的bean不会再ApplicationContext启动时提取被实例化,那么可以将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就占用资源。

FactoryBean和BeanFactory

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

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

Bean创建的三种凡是中的静态方式和实例化方法和FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架一些组件中会使用,还有其他框架和Spring框架整合时使用。

复制代码
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {

	/**
	 * 返回FactoryBean创建的Bean实例,如果 isSingleton 返回true,则该实例会放到Spring容器
的单例对象缓存池中Map
	 */
	@Nullable
	T getObject() throws Exception;

	/**
	 * 返回FactoryBean创建的Bean类型
	 */
	@Nullable
	Class<?> getObjectType();

	/**
	 *  返回作⽤域是否单例
	 */
	default boolean isSingleton() {
		return true;
	}

}

测试获取FactoryBean产生的对象

复制代码
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='guslegend', address='中关村', scale=500}

测试获取FactoryBean,需要在id千添加"&"

复制代码
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09

后置处理器

Spring提供了两种后置处理Bean的扩展接口,分别BeanPostProcessorBeanFactoryPostProcessor,两者在使用上是有区别的。

工厂初始化BeanFactory ->Bean对象。

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

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

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

复制代码
public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

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

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

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

复制代码
@FunctionalInterface
public interface BeanFactoryPostProcessor {

	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

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

在这里面有个方法叫做getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean的BeanDefinition对象。然后我们可以对定义的属性进行修改,以下时BeanDefinition中的方法。

方法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所有当我们拿到BeanDefinition对象时,我们可以手动修改bean标签中定义的属性。

BeanDefinition对象:我们在xml中定义bean标签,Spring解析bean标签成为一个JavaBean,这个JavaBean就是BeanDefinition。

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

Spring IOC源码剖析

Spring IOC的容器初始化主题流程

SpringIOC的容器体系

springFacotry 顶级接口方法栈:

BeanFactory容器继承体系

Bean生命周期关键时机点

  1. Bean的创建是在容器初始化还是在getBean时?未设计延迟加载前提下,Bean创建是在容器初始化过程中完成的。
  2. 构造函数调用情况?构造函数的调用时机是在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory)处;
  3. 分析initialzingBean之afterPropertiesSet初始化方法调用情况? InitializingBeanafterPropertiesSet ⽅法的调⽤时机也是在 AbstractApplicationContextrefresh⽅法的finishBeanFactoryInitialization(beanFactory);
  4. 分析BeanFactoryPostProcessor初始化和调用情况?BeanFactoryPostProcessor 初始化在AbstractApplicationContextrefresh⽅法的 invokeBeanFactoryPostProcessors(beanFactory); postProcessBeanFactory 调⽤在AbstractApplicationContextrefresh⽅法invokeBeanFactoryPostProcessors(beanFactory);
  5. 分析BeanPostProcessor初始化和调⽤情况?BeanPostProcessor 初始化在AbstractApplicationContextrefresh⽅法的 registerBeanPostProcessors(beanFactory); postProcessBeforeInitialization 调⽤在AbstractApplicationContextrefresh⽅法的 finishBeanFactoryInitialization(beanFactory); postProcessAfterInitialization 调⽤在AbstractApplicationContextrefresh⽅法的 finishBeanFactoryInitialization(beanFactory);

总结:

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

Spring IOC 容器初始化主流程

SpringIOC容器初始化的关键环节就在 AbstractApplicationContext#refresh() 方法中,我们查看refresh方法来俯瞰容器创建的主体流程,主体流程下的具体子流程我们后面再来讨论。

复制代码
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 第一步:刷新前的预处理
        prepareRefresh();

        /*
         * 第二步:
         * 获取BeanFactory;默认实现是DefaultListableBeanFactory
         * 加载BeanDefinition 并注册到 BeanDefinitionRegistry
         */
        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) {
            // 标准实现中会捕获异常并处理(销毁已创建的bean、重置active状态)
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }
            // 销毁已创建的单例bean以避免资源占用
            destroyBeans();
            // 重置active标志
            cancelRefresh(ex);
            // 抛出异常
            throw ex;
        } finally {
            // 标准实现中的收尾工作(重置Spring核心的公共缓存)
            resetCommonCaches();
        }
    }
}

BeanFactory创建流程

获取BeanFactory子流程

BeanDefinition加载解析及注册子流程

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

Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂

件,并将其封装成Resource对象。

BeanDefinition载⼊ :把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数

据结构就是BeanDefinition。

注册BeanDefinition到 IoC 容器

(2)过程分析

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

Step 2 **:**依次调⽤多个类的 loadBeanDefinitions ⽅法 ---> AbstractXmlApplicationContext --->

AbstractBeanDefinitionReader ---> XmlBeanDefinitionReader ⼀直执⾏到

XmlBeanDefinitionReader 的 doLoadBeanDefinitions ⽅法

Step 3 **:**我们重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions ⽅法,期间产⽣了多

次重载调⽤,我们定位到最后⼀个

此处我们关注两个地⽅:⼀个createRederContext⽅法,⼀个是

DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions⽅法,先进⼊

createRederContext ⽅法看看

我们可以看到,此处 Spring ⾸先完成了 NamespaceHandlerResolver 的初始化。

我们再进⼊ registerBeanDefinitions ⽅法中追踪,调⽤了

DefaultBeanDefinitionDocumentReader#registerBeanDefinitions ⽅法

进⼊ parseBeanDefinitions ⽅法

进⼊ parseDefaultElement ⽅法

进⼊ processBeanDefinition ⽅法

⾄此,注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为

BeanDefinition 对象之后放⼊⼀个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition

的。

可以在DefaultListableBeanFactory中看到此Map的定义

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

Bean创建流程

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

进⼊finishBeanFactoryInitialization

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

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

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

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 = getMergedBeanDefinition(beanName);
        // 判断是否是抽象类,不是抽象的并且是单例的并且不是懒加载的则在容器创建时初始化
        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 {
                    isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit());
                }
                if (isEagerInit) {
                    // 如果是普通bean则进行初始化并依赖注入,此 getBean(beanName) 接下来触发的逻辑
                    // 和 懒加载时 context.getBean("beanName") 所触发的逻辑是一样的
                    getBean(beanName);
                }
            } else {
                getBean(beanName);
            }
        }
    }
}

总结

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

Spring IOC 循环依赖问题

Spring中循环依赖场景有:

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

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

  • 单例 bean 构造器参数循环依赖(⽆法解决)
  • prototype 原型 bean循环依赖(⽆法解决)

对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依 赖,Spring都 会直接报错处理。

复制代码
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 的循环依赖。

  1. 循环依赖场景:单例 bean 通过 @Autowired/@Resource 形成循环依赖(如 ClassA 依赖 ClassB,ClassB 依赖 ClassA)。
  2. Spring 循环依赖处理原理:基于 Java 引用传递 + 提前暴露对象引用实现:
    • 获取对象引用时,对象的属性可延后设置,但构造器必须在获取引用前完成;
    • 核心方案:通过提前暴露一个 ObjectFactory 对象,将未完全初始化的对象引用提前注册到容器中。
  1. 具体流程(以 ClassA 依赖 ClassB 为例):
    • Spring 容器初始化 ClassA:对 ClassA 完成对象初始化(调用构造器);在调用 ClassA.setClassB () 前,将 ClassA 实例通过 ObjectFactory 提前暴露到 Spring 容器中。
    • ClassA 调用 setClassB ():Spring 尝试从容器中获取 ClassB(此时 ClassB 未初始化)。
    • Spring 容器初始化 ClassB:对 ClassB 完成对象初始化;同时将 ClassB 提前暴露到 Spring 容器中。
    • ClassB 调用 setClassA ():Spring 从容器中获取 ClassA(因前期已提前暴露,可获取到 ClassA 实例)。
    • 完成初始化:ClassB 完成属性注入,成为完整对象;ClassA 拿到 ClassB 实例,完成属性注入,解决循环依赖。
  1. 核心代码片段(提前暴露对象引用):
vb 复制代码
`// 判断是否允许提前暴露单例对象(单例 + 允许循环依赖 + 正在创建中)
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);
        }
    });
}`
相关推荐
无敌最俊朗@3 小时前
STL-vector面试剖析(面试复习4)
java·面试·职场和发展
PPPPickup3 小时前
easychat项目复盘---获取联系人列表,联系人详细,删除拉黑联系人
java·前端·javascript
LiamTuc3 小时前
Java构造函数
java·开发语言
长安er3 小时前
LeetCode 206/92/25 链表翻转问题-“盒子-标签-纸条模型”
java·数据结构·算法·leetcode·链表·链表翻转
菜鸟plus+4 小时前
N+1查询
java·服务器·数据库
我要添砖java4 小时前
《JAVAEE》网络编程-什么是网络?
java·网络·java-ee
CoderYanger4 小时前
动态规划算法-01背包问题:50.分割等和子集
java·算法·leetcode·动态规划·1024程序员节
菜鸟233号5 小时前
力扣513 找树左下角的值 java实现
java·数据结构·算法·leetcode
Neoest6 小时前
【EasyExcel 填坑日记】“Syntax error on token )“: 一次编译错误在逃 Runtime 的灵异事件
java·eclipse·编辑器
自在极意功。6 小时前
Web开发中的分层解耦
java·microsoft·web开发·解耦