Spring 循环引用(三)AbstractFactoryBean 如何解决循环依赖

目录

  • Spring 循环引用(三)AbstractFactoryBean 如何解决循环依赖

    • 1. 循环依赖问题
    • 2. 准备实验
    • 3. 原因分析
    • 4. 解决方案
    • 5. 还有什么问题
    • 6. 总结

Spring 循环引用(三)AbstractFactoryBean 如何解决循环依赖

Spring 系列目录:https://www.cnblogs.com/binarylei/p/10198698.html

本章讨论的范围:AbstractFactoryBean 创建单例 Bean 过程中出现的循环依赖问题,多例 Bean 的创建根本不会出现循环依赖,因为会直接抛异常。

1. 循环依赖问题

在 AbstractFactoryBean 中有一个特殊的属性 earlySingletonInstance,用于提前将 bean 暴露。第一次看到这个属性很奇怪:

  1. Spring IoC 容器已经解决了单例 bean 非构造器注入时的循环依赖问题,这里为什么还需要解决循环依赖?
  2. 对于单例 bean 而言,只有在 FactoryBean 初始化完成后才能获取对象,也就是先执行 afterPropertiesSet 再执行 getObject 方法,也就是说 initialized 在 getObject 应该为 true 才对,有可能为 false 吗?

带着疑问,我们先看一下代码:

private T singletonInstance;        # FactoryBean创建的对象bean
private T earlySingletonInstance;   # 提前暴露bean,用于解决循环依赖

AbstractFactoryBean 初始化完成后调用 afterPropertiesSet 创建对象,然后通过 getObject 获取对象。

@Override
public void afterPropertiesSet() throws Exception {
    if (isSingleton()) {
        this.initialized = true;
        this.singletonInstance = createInstance();
        this.earlySingletonInstance = null;
    }
}

@Override
public final T getObject() throws Exception {
    if (isSingleton()) {
        // 单例,解决循环依赖,先生成代理对象,当getObject时才生成对象
        return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
    } else {
        // 多例,不会解决循环依赖,直接报错
        return createInstance();
    }
}

说明: 正常流程下,AbstractFactoryBean 初始化完成后,调用 afterPropertiesSet 创建对象,此时 initialized = true 且对象创建完成,当 getObject 时直接返回 singletonInstance。什么情况下 initialized 会为 false 呢?

我的第一想法是,这两个 FactoryBean 相互依赖,形成 AB - BA 的关系,但心里也有点惴惴不安,因为 Spring IoC 应该已经解决了这类循环依赖的问题,不管怎样,先进行一番实验再说。

2. 准备实验

class FactoryBeanA extends AbstractFactoryBean<BeanA> {
    @Override
    public Class<?> getObjectType() {
        return BeanA.class;
    }
    @Override
    protected BeanA createInstance() throws Exception {
        return new BeanAImpl();
    }
}

class FactoryBeanB extends AbstractFactoryBean<BeanB> {
    @Override
    public Class<?> getObjectType() {
        return BeanB.class;
    }
    @Override
    protected BeanB createInstance() throws Exception {
        return new BeanBImpl();
    }
}

class BeanAImpl implements BeanA {
}
class BeanBImpl implements BeanB {
}

interface BeanA {
}
interface BeanB {
}

说明: 因为 AbstractFactoryBean 通过 getEarlySingletonInstance 生成的代理对象是通过 JDK 的动态代理生成的,所以 BeanAImpl 和 BeanBImpl 必须有接口。

猜想1:两个 FactoryBean 直接互相依赖

在 FactoryBeanA 和 FactoryBeanB 中添加如下代码,FactoryBeanA 和 FactoryBeanB 创建时形成相互依赖关系:

class FactoryBeanA extends AbstractFactoryBean<BeanA> {
    @Autowired
    private FactoryBeanB factoryBeanB;
}

class FactoryBeanB extends AbstractFactoryBean<BeanA> {
    @Autowired
    private FactoryBeanA factoryBeanA;
}

说明: 果不其然,容器运行时根本没有调用到 getEarlySingletonInstance() 方法,也就是没有出现 FactoryBean 还未初始化完成就需要调用 getObject 创建对象的情况,当然 FactoryBeanA 和 FactoryBeanB 创建过程肯定是有循环依赖的。

猜想2:两个 FactoryBean#getObject 互相依赖

在 FactoryBeanA 和 FactoryBeanB 中添加如下代码:

class FactoryBeanA extends AbstractFactoryBean<BeanA> {
    @Autowired
    private BeanB beanB;
}

class FactoryBeanB extends AbstractFactoryBean<BeanA> {
    @Autowired
    private BeanA beanA;
}

说明: 果然,容器运行时调用到 getEarlySingletonInstance() 方法。也就是出现了 FactoryBean 还未初始化完,但要调用 getObject 创建对象的情况,这是肯定不行了,这就是循环依赖产生的根据原因,如何解决呢?

总结: 两个猜想说明

  1. 猜想 1 :Spring IoC 已经解决了 Bean 创建过程中的相互依赖,即 FactoryBeanA 和 FactoryBeanB 直接相互依赖问题。
  2. 猜想 2 :AbstractFactoryBean 的 earlySingletonInstance 属性解决的正是 FactoryBean#getObject() 时产生的相互依赖问题。

3. 原因分析

说明: AbstractFactoryBean 循环依赖原因分析

  1. 执行第②步时,FactoryBeanA 在初始化时需要注入 BeanB,此时 FactoryBeanA 正在初始化。
  2. 注入 BeanB 时,又需要执行第⑦步来初始化 BeanA,但此时 FactoryBeanA 都还未初始化完成(FactoryBeanA 在第⑩步执行 afterPropertiesSet 才初始化完成),怎么能调用 FactoryBeanA#getObject 来创建 BeanA 对象呢?这样就陷入了循环依赖中。

4. 解决方案

当然,我们很容易想到,像 Spring IoC 一样,如果我们在 FactoryBeanA#getObject 时,创建一个代理对象 earlySingletonInstance 提前暴露到出去,这样 FactoryBeanA#getObject 不就可以正常初始化了吗。事实上 AbstractFactoryBean 也正是这么做的。

说明: 从时序图可以看到

  1. 第10 步:此时 FactoryBeanA 未初始化完成,因此 getObject 方法通过 getEarlySingletonInstance 返回了一个代理对象 earlySingletonInstance,真实的对象 beanA 实际上并没有创建。
  2. 第14 步:此时 FactoryBeanA 调用 afterPropertiesSet 完成初始化,调用 createInstance 创建真实的对象 beanA。

我们看一下 getEarlySingletonInstance 方法,会生成一个代理对象 earlySingletonInstance,当调用 getObject 时会直接将这个代理对象返回。

private T getEarlySingletonInstance() throws Exception {
    Class<?>[] ifcs = getEarlySingletonInterfaces();
    if (ifcs == null) {
        throw new FactoryBeanNotInitializedException(
            getClass().getName() + " does not support circular references");
    }
    if (this.earlySingletonInstance == null) {
        this.earlySingletonInstance = (T) Proxy.newProxyInstance(
            this.beanClassLoader, ifcs, new EarlySingletonInvocationHandler());
    }
    return this.earlySingletonInstance;
}

说明: earlySingletonInstance 通过 JDK 的动态代理生成代理对象,所以如果 FactoryBean 要支持循环依赖,必须有接口。

5. 还有什么问题

到目前为止,似乎一切顺利,FactoryBean 的循环依赖通过提前暴露动态代理对象,避免了死循环,但真的没有问题了吗?

  1. FactoryBeanB 中注入 BeaA 时,FactoryBeanA 还未初始化完成,注入的是一个代理对象 earlySingletonInstance。
  2. Spring IoC 中,FactoryBeanA 初始化完成时,调用 afterPropertiesSet 完成初始化,真正暴露到容器中的是实例对象 singletonInstance。
  3. earlySingletonInstance 和 singletonInstance 是一个东西吗?如果不是,是不是可能造成数据不一致的情况,那不违反了单例原则吗?

你可能对下面这段代码感到困惑,因为本章中我们讨论的都是单例 Bean 的问题,怎么可能不相等呢?

@Test
public void test() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
        CircularFactoryBeanDemo.class);

    BeanA beanA = context.getBean(BeanA.class);
    FactoryBeanB factoryBeanB = context.getBean(FactoryBeanB.class);

    Assert.assertNotEquals(beanA, factoryBeanB.getBeanA());
}

说明: 首先 earlySingletonInstance 是 JDK 代理对象,singletonInstance 是真正的实例,它们俩肯定不是一个东西。EarlySingletonInvocationHandler 正是代理了 singletonInstance 对象,即 earlySingletonInstance 底层其实也是使用 singletonInstance,这样就保证了数据的一致性,并不违反单例原则。

private class EarlySingletonInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // getSingletonInstance() 直接返回了 singletonInstance
        return method.invoke(getSingletonInstance(), args);
    }
}

说明: 既然 earlySingletonInstance 底层实际上也是使用 singletonInstance 对象,那至少数据肯定是一致的。

下图显示了这两个 beanA 之间的关系:

还有什么问题?现在我们已经知道 earlySingletonInstance 和 singletonInstance 是代理关系,我们如果想获取 singletonInstance 的注解等信息,可能通过正常的途径就无法获取了。特别是现在元编程大行其道的情况下,我们很多时候都需要获取类的注解、方法参数等信息,只能通过原始的对象类型获取。此时要特别小心。

6. 总结

  1. 和 Spring IoC 一样,AbstractFactoryBean 也是通过提前暴露代理对象 earlySingletonInstance 解决循环依赖问题。
  2. earlySingletonInstance 是通过 JDK 动态代理实现的,底层代理的正是真实对象 singletonInstance,这样保证了数据的一致性,也并不违反单例原则。正因为是 JDK 动态代理,所以想解决这类循环依赖的问题,Bean 必须有接口。
  3. earlySingletonInstance 和 singletonInstance 并不是同一个对象,导致 "注入的 beanA" 和 "容器中暴露的 beanA" 并不是同一个对象。实际上 Spring IoC 循环依赖也有类似的问题。
  4. 同样是因为上述问题,如果想获取类的注解、方法参数等信息,只能通过原始的对象类型获取。特别是现在元编程大行其道的情况下,此时要特别小心。
  5. 最后,虽然 Spring 对循环依赖问题提供了部分解决方案,但实际工作中,如果出现了循环依赖的问题,首先想到的应该是通过重构代码来解耦,而不是听之任之,放任不管。


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/12293764.html

时间: 2024-10-11 23:06:43

Spring 循环引用(三)AbstractFactoryBean 如何解决循环依赖的相关文章

使用gc、objgraph干掉python内存泄露与循环引用!

Python使用引用计数和垃圾回收来做内存管理,前面也写过一遍文章<Python内存优化>,介绍了在python中,如何profile内存使用情况,并做出相应的优化.本文介绍两个更致命的问题:内存泄露与循环引用.内存泄露是让所有程序员都闻风丧胆的问题,轻则导致程序运行速度减慢,重则导致程序崩溃:而循环引用是使用了引用计数的数据结构.编程语言都需要解决的问题.本文揭晓这两个问题在python语言中是如何存在的,然后试图利用gc模块和objgraph来解决这两个问题. 注意:本文的目标是Cpyth

Spring 循环引用(二)源码分析

Spring 循环引用(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) Spring 循环引用相关文章: <Spring 循环引用(一)一个循环依赖引发的 BUG>:https://www.cnblogs.com/binarylei/p/10325698.html <Spring 循环引用(二)源码分析>:https://www.cnblogs.com/binarylei/p/1032604

spring源码系列——spring循环引用

众所周知spring在默认单例的情况下是支持循环引用的 Appconfig.java类的代码 @Configurable@ComponentScan("com.shadow")public class Appconfig {}1234X.java类的代码 package com.shadow.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.

c++智能指针以及循环引用问题(转)

解决循环引用: 在知道存在循环引用的条件下,使用boost::weak_ptr,即弱引用来代替循环引用中的某个强引用,从而打破循环引用的环. 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete,比如流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见,并造成内存泄露.如此c++引入 智能指针 . c++ 智能指针主要包括:unique_ptr,shared_ptr, weak_ptr, 这三种,其中auto

循环引用,看我就对了

循环引用,看我就对了 我是一头来自北方的羊,咩-咩-咩-!谈到循环引用,不知道你能想到什么?可能是delegate为啥非得用weak修饰,可能是block为啥总是需要特殊对待,你也可能仅仅想到了一个weakSelf,因为它能帮你解决99%的关于循环引用的事情.本文中,我将谈一谈我对循环引用的看法. 一.循环引用的产生 1.基本知识 首先,得说下内存中和变量有关的分区:堆.栈.静态区.其中,栈和静态区是操作系统自己管理的,对程序员来说相对透明,所以,一般我们只需要关注堆的内存分配,而循环引用的产生

ios之block循环引用

在 iOS 4.2 时,苹果推出了 ARC 的内存管理机制.这是一种编译期的内存管理方式,在编译时,编译器会判断 Cocoa 对象的使用状况,并适当的加上 retain 和 release,使得对象的内存被合理的管理.所以,ARC 和 MRC 在本质上是一样的,都是通过引用计数的内存管理方式. 然而 ARC 并不是万能的,有时为了程序能够正常运行,会隐式的持有或复制对象,如果不加以注意,便会造成内存泄露!今天就列举几个在 ARC 下容易产生内存泄露的点,和各位童鞋一起分享下. block 系列

ios block常见的错误(二)——循环引用

这篇博文继续block的常见错误--循环引用. 循环引用是很多初学者不能察觉的,其产生的原因,是block中的代码会对对象进行强引用. 读者请阅读示例代码1,并思考示例代码1所创建的对象能否被正常销毁? 示例代码1: * mark 将代码改为调用self的方法 -(NSMutableArray * (_myBlocks ===-=(^sum)(, ) = ^( x,  mark 对象被释放时自动调用 - ( 结果是不能正常释放的.读者不妨在xcode中试试. 产生原因在block代码中出现了se

iOS 面试题(四):block 什么时候需要构造循环引用 --转自唐巧

问题 有没有这样一个需求场景,block 会产生循环引用,但是业务又需要你不能使用 weak self? 如果有,请举一个例子并且解释这种情况下如何解决循环引用问题. 答案 需要不使用 weak self 的场景是:你需要构造一个循环引用,以便保证引用双方都存在.比如你有一个后台的任务,希望任务执行完后,通知另外一个实例.在我们开源的 YTKNetwork 网络库的源码中,就有这样的场景. 在 YTKNetwork 库中,我们的每一个网络请求 API 会持有回调的 block,回调的 block

避免Block的循环引用

避免Block的循环引用 什么是循环引用,什么时候发生循环引用 1 循环引用就是当self 拥有一个block的时候,在block 又调用self的方法.形成你中有我,我中有你,谁都无法将谁释放的困局. self.myBlock = ^{ [self doSomething]; }; +-----------+ +-----------+ | self | | Block | ---> | | --------> | | | retain 2 | <-------- | retain 1