循环依赖对AOP代理对象创建流程和结果的影响

我们都知道Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器来自动完成的。也就是说Spring最终给我们放进容器里面的是一个代理对象,而非原始对象

本文结合循环依赖,回头再看AOP代理对象的创建过程,和最终放进容器内的动作,非常有意思。

  1. @Service
  2. public class HelloServiceImpl implements HelloService {
  3. @Autowired
  4. private HelloService helloService;
  5. @Transactional
  6. @Override
  7. public Object hello(Integer id) {
  8. return "service hello";
  9. }
  10. }

此Service类使用到了事务,所以最终会生成一个JDK动态代理对象Proxy。刚好它又存在自己引用自己的循环依赖。看看这个Bean的创建概要描述如下:

protected Object doCreateBean( ... ){
    ...

    // 这段告诉我们:如果允许循环依赖的话,此处会添加一个ObjectFactory到三级缓存里面,以备创建对象并且提前暴露引用~
    // 此处Tips:getEarlyBeanReference是后置处理器SmartInstantiationAwareBeanPostProcessor的一个方法,它的功效为:
    // 保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象~~~~  AOP自动代理创建器此方法里会创建的代理对象~~~
    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) { // 需要提前暴露(支持循环依赖),就注册一个ObjectFactory到三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 此处注意:如果此处自己被循环依赖了  那它会走上面的getEarlyBeanReference,从而创建一个代理对象从三级缓存转移到二级缓存里
    // 注意此时候对象还在二级缓存里,并没有在一级缓存。并且此时可以知道exposedObject仍旧是原始对象~~~
    populateBean(beanName, mbd, instanceWrapper);
    exposedObject = initializeBean(beanName, exposedObject, mbd);

    // 经过这两大步后,exposedObject还是原始对象(注意此处以事务的AOP为例子的,
    // 因为事务的AOP自动代理创建器在getEarlyBeanReference创建代理后,initializeBean就不会再重复创建了,二选一的,下面会有描述~~~)

    ...

    // 循环依赖校验(非常重要)~~~~
    if (earlySingletonExposure) {
        // 前面说了因为自己被循环依赖了,所以此时候代理对象还在二级缓存里~~~(备注:本利讲解的是自己被循环依赖了的情况)
        // so,此处getSingleton,就会把里面的对象拿出来,我们知道此时候它已经是个Proxy代理对象~~~
        // 最后赋值给exposedObject  然后return出去,进而最终被addSingleton()添加进一级缓存里面去  
        // 这样就保证了我们容器里**最终实际上是代理对象**,而非原始对象~~~~~
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) { // 这个判断不可少(因为如果initializeBean改变了exposedObject ,就不能这么玩了,否则就是两个对象了~~~)
                exposedObject = earlySingletonReference;
            }
        }
        ...
    }

}

上演示的是代理对象+自己存在循环依赖的case:Spring用三级缓存很巧妙的进行解决了。
若是这种case:代理对象,但是自己并不存在循环依赖,过程稍微有点不一样儿了,如下描述:

protected Object doCreateBean( ... ) {
        ...
        // 这些语句依旧会执行,三级缓存里是会加入的  表示它支持被循环引用嘛~~~
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    ...

    // 此处注意,因为它没有被其它Bean循环引用(注意是循环引用,而不是直接引用~),所以上面getEarlyBeanReference不会执行~
    // 也就是说此时二级缓存里并不会存在它~~~ 知晓这点特别的重要
    populateBean(beanName, mbd, instanceWrapper);
    // 重点在这:AnnotationAwareAspectJAutoProxyCreator自动代理创建器此处的postProcessAfterInitialization方法里,会给创建一个代理对象返回
    // 所以此部分执行完成后,exposedObject **已经是个代理对象**而不再是个原始对象了~~~~ 此时二级缓存里依旧无它,更别提一级缓存了
    exposedObject = initializeBean(beanName, exposedObject, mbd);

    ...

    // 循环依赖校验
    if (earlySingletonExposure) {
        // 前面说了一级、二级缓存里都木有它,然后这里传的又是false(表示不看三级缓存~~)
        // 所以毋庸置疑earlySingletonReference = null  so下面的逻辑就不用看了,直接return出去~~
        // 然后执行addSingleton()方法,由此可知  容器里最终存在的也还是代理对象~~~~~~
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) { // 这个判断不可少(因为如果initializeBean改变了exposedObject ,就不能这么玩了,否则就是两个对象了~~~)
                exposedObject = earlySingletonReference;
            }
        }...
        ...
        ...
    }
}

分析可知,即使自己只需要代理,并不被循环引用,最终存在Spring容器里的仍旧是代理对象。(so此时别人直接@Autowired进去的也是代理对象呀~~~)

终极case:如果我关闭Spring容器的循环依赖能力,也就是把allowCircularReferences设值为false,那么会不会造成什么问题呢?

// 它用于关闭循环引用(关闭后只要有循环引用现象就直接报错~~)
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false);
    }
}

若关闭了循环依赖后,还存在上面A、B的循环依赖现象,启动便会报错如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)

注意此处异常类型也是BeanCurrentlyInCreationException异常,但是文案内容和上面强调的有所区别~~
它报错位置在:DefaultSingletonBeanRegistry.beforeSingletonCreation这个位置~

报错浅析:在实例化A后给其属性赋值时,会去实例化B。B实例化完成后会继续给B属性赋值,这时由于此时我们关闭了循环依赖,所以不存在提前暴露引用这么一说来给实用。因此B无法直接拿到A的引用地址,因此只能又去创建A的实例。而此时我们知道A其实已经正在创建中了,不能再创建了。so,就报错了~

@Service
public class HelloServiceImpl implements HelloService {

    // 因为管理了循环依赖,所以此处不能再依赖自己的
    // 但是:我们的此bean还是需要AOP代理的~~~
    //@Autowired
    //private HelloService helloService;

    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

这样它的大致运行如下:

protected Object doCreateBean( ... ) {
    // 毫无疑问此时候earlySingletonExposure = false  也就是Bean都不会提前暴露引用了  显然就不能被循环依赖了~
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    ...
    populateBean(beanName, mbd, instanceWrapper);
    // 若是事务的AOP  在这里会为源生Bean创建代理对象(因为上面没有提前暴露这个代理)
    exposedObject = initializeBean(beanName, exposedObject, mbd);

    if (earlySingletonExposure) {
        ... 这里更不用说,因为earlySingletonExposure=false  所以上面的代理对象exposedObject 直接return了~
    }
}

可以看到即使把这个开关给关了,最终放进容器了的仍旧是代理对象,显然@Autowired给属性赋值的也一定是代理对象。

最后,以AbstractAutoProxyCreator为例看看自动代理创建器是怎么配合实现:循环依赖+创建代理

AbstractAutoProxyCreator是抽象类,它的三大实现子类InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator小伙伴们应该会更加的熟悉些

该抽象类实现了创建代理的动作:

// @since 13.10.2003  它实现代理创建的方法有如下两个
// 实现了SmartInstantiationAwareBeanPostProcessor 所以有方法getEarlyBeanReference来只能的解决循环引用问题:提前把代理对象暴露出去~
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    ...
    // 下面两个方法是自动代理创建器创建代理对象的唯二的两个节点~

    // 提前暴露代理对象的引用  它肯定在postProcessAfterInitialization之前执行
    // 所以它并不需要判断啥的~~~~  创建好后放进缓存earlyProxyReferences里  注意此处value是原始Bean
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

    // 因为它会在getEarlyBeanReference之后执行,所以此处的重要逻辑是下面的判断
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // remove方法返回被移除的value,上面说了它记录的是原始bean
            // 若被循环引用了,那就是执行了上面的`getEarlyBeanReference`方法,所以此时remove返回值肯定是==bean的(注意此时方法入参的bean还是原始对象)
            // 若没有被循环引用,getEarlyBeanReference()不执行 所以remove方法返回null,所以就进入if执行此处的创建代理对象方法~~~
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    ...
}

由上可知,自动代理创建器它保证了代理对象只会被创建一次,而且支持循环依赖的自动注入的依旧是代理对象。

上面分析了三种case,现给出结论如下:
不管是自己被循环依赖了还是没有,甚至是把Spring容器的循环依赖给关了,它对AOP代理的创建流程有影响,但对结果是无影响的。
也就是说Spring很好的对调用者屏蔽了这些实现细节,使得使用者使用起来完全的无感知~

总结

解决此类问题的关键是要对SpringIOC和DI的整个流程做到心中有数,要理解好本文章,建议有【相关阅读】里文章的大量知识的铺垫,同时呢本文又能进一步的帮助小伙伴理解到Spring Bean的实例化、初始化流程。

本文还是花了我一番心思的,个人觉得对Spring这部分的处理流程描述得还是比较详细的,希望我的总结能够给大家带来帮助。
另外为了避免循环依赖导致启动问题而又不会解决,有如下建议:

  1. 业务代码中尽量不要使用构造器注入,即使它有很多优点。
  2. 业务代码中为了简洁,尽量使用field注入而非setter方法注入
  3. 若你注入的同时,立马需要处理一些逻辑(一般见于框架设计中,业务代码中不太可能出现),可以使用setter方法注入辅助完成