Spring 循环引用AbstractFactoryBean 如何解决循环依赖
Posted binarylei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 循环引用AbstractFactoryBean 如何解决循环依赖相关的知识,希望对你有一定的参考价值。
Spring 循环引用(三)AbstractFactoryBean 如何解决循环依赖
Spring 系列目录:https://www.cnblogs.com/binarylei/p/10198698.html
本章讨论的范围:AbstractFactoryBean 创建单例 Bean 过程中出现的循环依赖问题,多例 Bean 的创建根本不会出现循环依赖,因为会直接抛异常。
1. 循环依赖问题
在 AbstractFactoryBean 中有一个特殊的属性 earlySingletonInstance,用于提前将 bean 暴露。第一次看到这个属性很奇怪:
- Spring IoC 容器已经解决了单例 bean 非构造器注入时的循环依赖问题,这里为什么还需要解决循环依赖?
- 对于单例 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 :Spring IoC 已经解决了 Bean 创建过程中的相互依赖,即 FactoryBeanA 和 FactoryBeanB 直接相互依赖问题。
- 猜想 2 :AbstractFactoryBean 的 earlySingletonInstance 属性解决的正是 FactoryBean#getObject() 时产生的相互依赖问题。
3. 原因分析
说明: AbstractFactoryBean 循环依赖原因分析
- 执行第②步时,FactoryBeanA 在初始化时需要注入 BeanB,此时 FactoryBeanA 正在初始化。
- 注入 BeanB 时,又需要执行第⑦步来初始化 BeanA,但此时 FactoryBeanA 都还未初始化完成(FactoryBeanA 在第⑩步执行 afterPropertiesSet 才初始化完成),怎么能调用 FactoryBeanA#getObject 来创建 BeanA 对象呢?这样就陷入了循环依赖中。
4. 解决方案
当然,我们很容易想到,像 Spring IoC 一样,如果我们在 FactoryBeanA#getObject 时,创建一个代理对象 earlySingletonInstance 提前暴露到出去,这样 FactoryBeanA#getObject 不就可以正常初始化了吗。事实上 AbstractFactoryBean 也正是这么做的。
说明: 从时序图可以看到
- 第10 步:此时 FactoryBeanA 未初始化完成,因此 getObject 方法通过 getEarlySingletonInstance 返回了一个代理对象 earlySingletonInstance,真实的对象 beanA 实际上并没有创建。
- 第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 的循环依赖通过提前暴露动态代理对象,避免了死循环,但真的没有问题了吗?
- FactoryBeanB 中注入 BeaA 时,FactoryBeanA 还未初始化完成,注入的是一个代理对象 earlySingletonInstance。
- Spring IoC 中,FactoryBeanA 初始化完成时,调用 afterPropertiesSet 完成初始化,真正暴露到容器中的是实例对象 singletonInstance。
- 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. 总结
- 和 Spring IoC 一样,AbstractFactoryBean 也是通过提前暴露代理对象 earlySingletonInstance 解决循环依赖问题。
- earlySingletonInstance 是通过 JDK 动态代理实现的,底层代理的正是真实对象 singletonInstance,这样保证了数据的一致性,也并不违反单例原则。正因为是 JDK 动态代理,所以想解决这类循环依赖的问题,Bean 必须有接口。
- earlySingletonInstance 和 singletonInstance 并不是同一个对象,导致 "注入的 beanA" 和 "容器中暴露的 beanA" 并不是同一个对象。实际上 Spring IoC 循环依赖也有类似的问题。
- 同样是因为上述问题,如果想获取类的注解、方法参数等信息,只能通过原始的对象类型获取。特别是现在元编程大行其道的情况下,此时要特别小心。
- 最后,虽然 Spring 对循环依赖问题提供了部分解决方案,但实际工作中,如果出现了循环依赖的问题,首先想到的应该是通过重构代码来解耦,而不是听之任之,放任不管。
7. 补充说明:循环依赖+类型推断引发的 Bug
到现在为止,AbstractFactoryBean 循环依赖的问题已经解决,再将上述案例稍稍改动一下,将 FactoryBeanA 和 FactoryBeanB 上的泛型倒掉,重新测试一下:
private static class FactoryBeanA extends AbstractFactoryBean {
@Override
public Class<?> getObjectType() {
return BeanA.class;
}
}
private static class FactoryBeanB extends AbstractFactoryBean {
@Override
protected Object createInstance() throws Exception {
return new BeanBImpl();
}
}
再次测试,这里你会发现初始化 BeanA 时居然抛出 NoSuchBeanDefinitionException 异常。
@Test
public void test2() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.addBeanPostProcessor(beanFactory.createBean(
AutowiredAnnotationBeanPostProcessor.class));
beanFactory.registerBeanDefinition("userA",BeanDefinitionBuilder
.rootBeanDefinition(FactoryBeanA.class).getBeanDefinition());
beanFactory.registerBeanDefinition("userB", BeanDefinitionBuilder
.rootBeanDefinition(FactoryBeanB.class).etBeanDefinition());
BeanA beanA = beanFactory.getBean("userA");
}
说明: 在猜想 2 中还可以正常运行的案例,此时居然抛出 NoSuchBeanDefinitionException 异常,明明容器中已经注册了 BeanA 和 BeanB,简直让人匪夷所思。
最简单的解决方案是:不要有循环依赖,因为你不知道这雷什么时候会爆。
当然这两个案例的最大的区别是 FactoryBean 类上一个有泛型,一个没有泛型。当 FactoryBeanB 注入 BeanA 时,需要根据类型 BeanA 查找对应的 bean,即 beanFactory.getBean(BeanA.class)。此时 FactoryBeanA 正在初始化,不可以根据 FactoryBeanA#getObjectType 获取对象类型,但可以根据其泛型获取对象类型。在本例中,FactoryBeanA 没有对应的泛型,也就在容器中找不到 BeanA,无法注入 FactoryBeanB,进而抛出 NoSuchBeanDefinitionException。
说明: 在第⑥步,FactoryBeanB 注入 BeanA 时,需要在 Spring IoC 容器中根据 BeanA 类型查找依赖。因为 FactoryBeanA 和 FactoryBeanB 都外于正在创建的状态中,此时如果执行每⑨步 getSingletonFactoryBeanForTypeCheck 获取对象类型时会返回 null,也就是说不能通过 FactoryBean#getObjectType() 获取对象类型,但可以通过 FactoryBean 泛型获取对象类型。
// 部分初始化FactoryBean,调用getObjectType获取对象类型。
// 所谓部分初始化指的是只实例化FactoryBean对象,不进行属性注入。
// 部分初始化的FactoryBean对象,会缓存到factoryBeanInstanceCache集合中,当真正创建时从该集合中移除
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
...
if (isSingletonCurrentlyInCreation(beanName) ||
(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
return null;
}
...
}
每天用心记录一点点。内容也许不重要,但习惯很重要!
以上是关于Spring 循环引用AbstractFactoryBean 如何解决循环依赖的主要内容,如果未能解决你的问题,请参考以下文章