Spring三级缓存与循环依赖

一、前言

循环依赖:就是N个类循环(嵌套)引用。通俗的讲就是多个Bean互相引用对方,最终形成闭环

用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):

20190618225329264.png

  • 其实可以N=1,也就是极限情况的循环依赖:自己依赖自己
  • 这里指的循环引用不是方法之间的循环调用,而是对象的相互依赖关系。(方法之间循环调用若有出口也是能够正常work的)

二、Bean的循环依赖

假设现在有AServiceBService两个类需要加入到spring容器中,这两个类相互持有对方的引用

@Service
public class AServiceImpl implements AService { 
    
    @Autowired
    private BService bService;
    
    //...
}

@Service
public class BServiceImpl implements BService {

    @Autowired
    private AService aService;
    
    //...
}

这其实就是Spring环境下典型的循环依赖场景。但是很显然,这种循环依赖场景,Spring已经完美的帮我们解决和规避了问题。所以即使平时我们这样循环引用,不用特别注意。

三、循环依赖场景演示

3.1 Spring循环依赖

Spring环境中,因为我们的Bean的实例化、初始化都是交给了容器,因此它的循环依赖主要表现为下面三种场景。

3.1.1 构造器注入循环依赖

@Service
public class A {

    private B b;

    public A(B b) {    
        this.b = b;
    }
}

@Service
public class B {

    private A a;

    public B(A a) {
        this.a = a;
    }
}

结果:项目启动失败抛出异常BeanCurrentlyInCreationException,如下:

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) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

构造器注入构成的循环依赖,此种循环依赖方式是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。这也是构造器注入的最大劣势。

根本原因Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决。

构造器注入优势

Spring在文档

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.

上文大意是说构造器注入的方式,能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态

  • 依赖不可变:其实说的就是final关键字,这里不再多解释了。
  • 依赖不为空(省去了我们对其检查):当要实例化FooController的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK。2:无该类型的参数->报错。所以保证不会为空,Spring总不至于传一个null进去吧。
  • 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。)。所以返回来的都是初始化之后的状态。

3.1.2 singleton模式field属性注入循环依赖

这种方式是我们为常用的依赖注入方式(所以猜都能猜到它肯定不会有问题啦):

@Service
public class A {

    @Autowired
    private B b;
}

@Service
public class B {

    @Autowired
    private A a;
}

结果:项目启动成功,能够正常work

备注:setter方法注入方式因为原理和字段注入方式类似,此处不多加演示

3.1.3 prototype模式field属性注入循环依赖

prototype 在平时使用情况较少,但是也并不是不会使用到,因此此种方式也需要引起重视。

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
    
    @Autowired
    private B b;
}

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {

    @Autowired
    private A a;
}

结果:需要注意的是 本例中启动时是不会报错的 (因为非单例Bean默认 不会初始化,而是使用时才会初始化),所以很简单咱们只需要手动getBean() 或者在一个单例Bean内@Autowired 一下它即可

// 在单例Bean内注入
@Autowired
private A a;

这样子启动就报错:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is 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.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)

3.1.4 @Async注解产生循环引用

@Async注解也会产生循环依赖问题

public class AService {
    @Autowired
    BService bService;

    @Async
    public void methodA(){
    }
}

public class BService {
    
    @Autowired
    AService aService;
}

Bean with name ‘aService’ has been injected into other beans [bService] in its raw version as part of a circular reference, but has eventually been wrapped.
This means that said other beans do not use the final version of the bean. This is often the result of over-eager
type matching - consider using ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.

从上面的代码不难发现,AServiceBService发生了循环引用,不过在此之前的代码中其实是已经存在循环依赖的关系,不过Spring使用三级缓存已经解决了单例的循环依赖问题,所以之前的程序在启动时是没有报错的。
这次发生报错是因为AService中的方法使用了@Async注解。

@Async注解产生循环引用

3.2 解决

如何解决?可能有的小伙伴看到网上有说使用@Lazy注解解决:

@Lazy
@Autowired
private A a;

此处负责任的告诉你这样是解决不了问题的(可能会掩盖问题),@Lazy只是延迟初始化而已,当你真正使用到它(初始化)的时候,依旧会报如上异常。

对于Spring循环依赖的情况总结如下:

不能解决的情况:

  • 构造器注入循环依赖
  • prototype模式field属性注入循环依赖

能解决的情况:

  • singleton模式field属性注入(setter方法注入)循环依赖
  • @Async注解产生循环引用

四、Spring解决循环依赖的原理分析

Spring的循环依赖的理论依据基于Java的引用传递 ,当获得对象的引用时,对象的属性是可以延后设置的 。(但是构造器必须是在获取引用之前,毕竟你的引用是靠构造器给你生成的)

4.1 Spring创建Bean的流程

首先需要了解是Spring它创建Bean的流程,我把它的大致调用栈绘图如下:

20190619142513115.png

对Bean的创建最为核心三个方法解释如下:

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  • populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
  • initializeBean:回到一些形如initMethod、InitializingBean等方法

从对单例Bean的初始化可以看出,循环依赖主要发生在第二步(populateBean) ,也就是field属性注入的处理。

4.2 Spring的三级缓存(重要)

public class DefaultSingletonBeanRegistry
        extends SimpleAliasRegistry
        implements SingletonBeanRegistry {

    // 从上至下 分表代表这“三级缓存”

    //单例对象的缓存:bean名称 -> bean实例
    //一级缓存,存放实例化好的单例对象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //早期单例对象的缓存:bean名称 -> bean实例
    //二级缓存,存放提前曝光的实例对象
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    //单例工厂的缓存:bean名称 -> ObjectFactory
    //三级缓存,存放要被实例化的对象的对象工厂
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    //存放正在创建的bean
    private final Set<String> singletonsCurrentlyInCreation = 
                           Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    //一组已注册的单例实例,bean名称按注册顺序排列
    private final Set<String> registeredSingletons = new LinkedHashSet(256);

    //...
}

注:AbstractBeanFactory继承自DefaultSingletonBeanRegistry

  • singletonObjects:用于存放完全初始化好的bean,从该缓存中取出的bean可以直接使用
  • earlySingletonObjects:提前曝光的单例对象的cache,存放原始的bean对象(尚未填充属性),用于解决循环依赖
  • singletonFactories:单例对象工厂的cache,存放bean工厂对象,用于解决循环依赖

获取单例Bean的源码如下:

public class DefaultSingletonBeanRegistry 
        extends SimpleAliasRegistry 
        implements SingletonBeanRegistry {
    
    //...

    @Override
    @Nullable
    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
	
    //...

    public boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.singletonsCurrentlyInCreation.contains(beanName);
    }

    protected boolean isActuallyInCreation(String beanName) {
        return isSingletonCurrentlyInCreation(beanName);
    }

    //...
}
  1. 先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
  2. 如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
  3. 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动到了二级缓存)

加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

getSingleton() 从缓存里获取单例对象步骤分析可知,Spring解决循环依赖的诀窍:就在于singletonFactories这个三级缓存 。这个Cache里面都是ObjectFactory ,它是解决问题的关键。

// 它可以将创建对象的步骤封装到ObjectFactory中 交给自定义的Scope来选择是否需要创建对象来灵活的实现scope。
// 具体参见Scope接口
@FunctionalInterface
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

经过ObjectFactory.getObject()后,此时放进了二级缓存earlySingletonObjects内。这个时候对象已经实例化了,虽然还不完美,但是对象的引用已经可以被其它引用了。

此处说一下二级缓存earlySingletonObjects它里面的数据什么时候添加什么移除???

添加: 向里面添加数据只有一个地方,就是上面说的getSingleton()里从三级缓存里挪过来。
移除: addSingleton、addSingletonFactory、removeSingleton从语义中可以看出添加单例、添加单例工厂ObjectFactory的时候都会删除二级缓存里面对应的缓存值,是互斥的。

4.3 源码解析

Spring容器会将每一个正在创建的Bean标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,而对于创建完毕的Bean将从当前创建Bean池中清除掉。

这个“当前创建Bean池”指的是上面提到的singletonsCurrentlyInCreation那个集合。

public abstract class AbstractBeanFactory
        extends FactoryBeanRegistrySupport
        implements ConfigurableBeanFactory {

    //...

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                              @Nullable final Object[] args, boolean typeCheckOnly) 
            throws BeansException {
        //...

        // 先去获取一次,如果不为null,此处就会走缓存了
        Object sharedInstance = getSingleton(beanName);

        //...

        // 如果不是只检查类型,那就标记这个Bean被创建了 添加到缓存里 也就是所谓的  
        // 当前创建Bean池
        if (!typeCheckOnly) {
            markBeanAsCreated(beanName);
        }

        //...

        // Create bean instance.
        if (mbd.isSingleton()) {
            // 这个getSingleton方法不是SingletonBeanRegistry的接口方法  
            // 属于实现类DefaultSingletonBeanRegistry的一个public重载方法

            // 它的特点是在执行singletonFactory.getObject();
            // 前后会执行beforeSingletonCreation(beanName)和afterSingletonCreation(beanName)

            // 也就是保证这个Bean在创建过程中,放入正在创建的缓存池里
            // 可以看到它实际创建bean调用的是我们的createBean方法
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                } catch (BeansException ex) {
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }

    //...

}

// 抽象方法createBean所在地 这个接口方法是属于抽象父类AbstractBeanFactory的实现在这个抽象类里
public abstract class AbstractAutowireCapableBeanFactory
        extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    //...

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd,
                                  final @Nullable Object[] args) 
            throws BeanCreationException {
        //...

        // 创建Bean对象,并且将对象包裹在BeanWrapper中
        instanceWrapper = createBeanInstance(beanName, mbd, args);

        // 再从Wrapper中把Bean原始对象(非代理)这个时候这个Bean就有地址值了,就能被引用了
        // 注意:此处是原始对象,这点非常的重要
        final Object bean = instanceWrapper.getWrappedInstance();

        //...

        // earlySingletonExposure用于表示是否”提前暴露“原始对象的引用,用于解决循环依赖。
        // 对于单例Bean,该变量一般为true,可以通过属性allowCircularReferences = false来关闭循环引用
        // isSingletonCurrentlyInCreation(beanName)表示当前bean必须在创建中才行
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
                && isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName
                        + "' to allow for resolving potential circular references");
            }
            // 上面讲过调用此方法放进一个ObjectFactory,二级缓存会对应删除的
            // getEarlyBeanReference的作用:
            // 调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()方法,否则啥都不做

            // 也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑

            // 比如在getEarlyBeanReference()里可以实现AOP的逻辑
            // 参考自动代理创建器AbstractAutoProxyCreator实现了这个方法来创建代理对象

            // 若不需要执行AOP的逻辑,直接返回Bean
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        Object exposedObject = bean; //exposedObject是最终返回的对象
        //...
        // 填充属于,解决@Autowired依赖
        populateBean(beanName, mbd, instanceWrapper);
        // 执行初始化回调方法们
        exposedObject = initializeBean(beanName, exposedObject, mbd);

        // earlySingletonExposure:如果你的bean允许被早期暴露出去也就是说可以被循环引用 那这里就会进行检查
        // 此段代码非常重要,但大多数人都忽略了它
        if (earlySingletonExposure) {
            // 此时一级缓存肯定还没数据,但是呢此时候二级缓存earlySingletonObjects也没数据
            //注意,注意:第二参数为false表示不会再去三级缓存里查了

            //此处非常巧妙的一点:因为上面各式各样的实例化、初始化的后置处理器都执行了,如果你在上面执行了这一句
            //  ((ConfigurableListableBeanFactory)this.beanFactory).registerSingleton(beanName, bean);
            // 那么此处得到的earlySingletonReference 的引用最终会是你手动放进去的Bean最终返回,
            //            完美的实现了"偷天换日" 特别适合中间件的设计
            // 我们知道,执行完此doCreateBean后执行addSingleton() 其实就是把自己再添加一次
            //              **再一次强调,完美实现偷天换日**
            Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                // 这个意思是如果经过了initializeBean()后,exposedObject还是木有变,那就可以大胆放心的返回了
                // initializeBean会调用后置处理器,这个时候可以生成一个代理对象,
                //          那这个时候它哥俩就不会相等了 走else去判断吧
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                }

                // allowRawInjectionDespiteWrapping这个值默认是false
                // hasDependentBean:若它有依赖的bean 那就需要继续校验了(若没有依赖的 就放过它)
                else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    // 拿到它所依赖的Bean们 下面会遍历一个一个的去看
                    String[] dependentBeans = getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);

                    // 一个个检查它所以Bean
                    // removeSingletonIfCreatedForTypeCheckOnly这个放见下面  在AbstractBeanFactory里面
                    // 简单的说,它如果判断到该dependentBean并没有在创建中的了的情况下,
                    //        那就把它从所有缓存中移除 并且返回true
                    // 否则(比如确实在创建中) 那就返回false 进入我们的if里面  表示所谓的真正依赖
                    //(解释:就是真的需要依赖它先实例化,才能实例化自己的依赖)
                    for (String dependentBean : dependentBeans) {
                        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            actualDependentBeans.add(dependentBean);
                        }
                    }

                    // 若存在真正依赖,那就报错(不要等到内存移除你才报错,那是非常不友好的)
                    // 这个异常是BeanCurrentlyInCreationException,报错日志也稍微留意一下,方便定位错误
                    if (!actualDependentBeans.isEmpty()) {
                        throw new BeanCurrentlyInCreationException(beanName,
                            "Bean with name '" + beanName + "' has been injected into other beans [" + 
                            StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                            "] in its raw version as part of a circular reference, but has" +
                            " eventually been wrapped. This means that said other beans  do not use " +
                            "the final version of the bean. This is often the result  of over-eager " +
                            "type matching - consider using 'getBeanNamesOfType' " +
                            "with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }
        }

        return exposedObject;
    }

    // 虽然是remove方法 但是它的返回值也非常重要
    // 该方法唯一调用的地方就是循环依赖的最后检查处
    protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
        // 如果这个bean不在创建中  比如是ForTypeCheckOnly的  那就移除掉
        if (!this.alreadyCreated.contains(beanName)) {
            removeSingleton(beanName);
            return true;
        } else {
            return false;
        }
    }

}

这里举例:例如是field属性依赖注入,在populateBean时它就会先去完成它所依赖注入的那个bean的实例化、初始化过程,最终返回到本流程继续处理,因此Spring这样处理是不存在任何问题的。

这里有个小细节:

if (exposedObject == bean) {
    exposedObject = earlySingletonReference;
}

这一句如果exposedObject == bean表示最终返回的对象就是原始对象,说明在populateBean和initializeBean没对他代理过,那就啥话都不说了exposedObject = earlySingletonReference,最终把二级缓存里的引用返回即可

4.4 流程总结

此处以如上的A、B类的互相依赖注入为例,在这里表达出关键代码的走势:

  1. 入口处即是实例化、初始化A这个单例BeanAbstractBeanFactory.doGetBean("a")
protected <T> T doGetBean(...) {
    //...
    
    // 标记beanName a是已经创建过至少一次的 它会一直存留在缓存里不会被移除(除非抛出了异常)
    // 参见缓存Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256))
    if (!typeCheckOnly) {
        markBeanAsCreated(beanName);
    }
 
    // 此时a不存在任何一级缓存中,且不是在创建中  所以此处返回null
    // 此处若不为null,然后从缓存里拿就可以了(主要处理FactoryBean和BeanFactory情况吧)
    Object beanInstance = getSingleton(beanName, false);
    
    //...
    
    // 这个getSingleton方法非常关键。
    //1、标注a正在创建中
    //2、调用singletonObject = singletonFactory.getObject();(实际上调用的是createBean()方法)
    //    因此这一步最为关键
    //3、此时实例已经创建完成  会把a移除整整创建的缓存中
    //4、执行addSingleton()添加进去。(备注:注册bean的接口方法为registerSingleton,它依赖于addSingleton方法)
    sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); });
}
  1. 下面进入到最为复杂的AbstractAutowireCapableBeanFactory.createBean/doCreateBean()环节,创建A的实例
protected Object doCreateBean() {
    //...
    
    // 使用构造器/工厂方法 instanceWrapper是一个BeanWrapper
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    // 此处bean为"原始Bean" 也就是这里的A实例对象:A@1234
    final Object bean = instanceWrapper.getWrappedInstance();

    //...

    // 是否要提前暴露(允许循环依赖) 现在此处A是被允许的
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
               && isSingletonCurrentlyInCreation(beanName));

    // 允许暴露,就把A绑定在ObjectFactory上,注册到三级缓存singletonFactories里面去保存着
    // Tips:这里后置处理器的getEarlyBeanReference方法会被促发,
    //  自动代理创建器在此处创建代理对象(注意执行时机 为执行三级缓存的时候)
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    //...

    // exposedObject为最终返回的对象,此处为原始对象bean也就是A@1234,下面会有用处
    Object exposedObject = bean;
    
    // 给A@1234属性完成赋值,@Autowired在此处起作用
    // 因此此处会调用getBean("b"),so 会重复上面步骤创建B类的实例
    // 此处我们假设B已经创建好了 为B@5678

    // 需要注意的是在populateBean("b")的时候依赖有beanA,所以此时候调用getBean("a")最终会调用getSingleton("a"),
    // 此时候上面说到的getEarlyBeanReference方法就会被执行。
    // 这也解释为何我们@Autowired是个代理对象,而不是普通对象的根本原因

    populateBean(beanName, mbd, instanceWrapper);
    // 实例化。这里会执行后置处理器BeanPostProcessor的两个方法
    // 此处注意:postProcessAfterInitialization()是有可能返回一个代理对象的,
    //         这样exposedObject 就不再是原始对象了 特备注意哦
    // 比如处理@Aysnc的AsyncAnnotationBeanPostProcessor它就是在这个时间里生成代理对象的
    //   (有坑,请小心使用@Aysnc)
    exposedObject = initializeBean(beanName, exposedObject, mbd);

    //...
    
    // 至此,相当于A@1234已经实例化完成、初始化完成(属性也全部赋值了)
    // 这一步我把它理解为校验:校验:校验是否有循环引用问题

    if (earlySingletonExposure) {
        // 注意此处第二个参数传的false,表示不去三级缓存里singletonFactories再去调用一次getObject()方法了
        // 上面建讲到了由于B在初始化的时候,会触发A的ObjectFactory.getObject()
        //    所以a此处已经在二级缓存earlySingletonObjects里了
        // 因此此处返回A的实例:A@1234
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {

            // 这个等式表示,exposedObject若没有再被代理过,这里就是相等的
            // 显然此处我们的a对象的exposedObject它是没有被代理过的 所以if会进去
            // 这种情况至此,就全部结束了
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }

            // 继续以A为例,比如方法标注了@Aysnc注解,exposedObject此时候就是一个代理对象,因此就会进到这里来
            // hasDependentBean(beanName)肯定为true,因为getDependentBeans(beanName)得到的是["b"]这个依赖
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);

                // A@1234依赖的是["b"],所以此处去检查b
                // 如果最终存在实际依赖的bean:actualDependentBeans不为空 那就抛出异常 证明循环引用了
                for (String dependentBean : dependentBeans) {
                    // 这个判断原则是:如果此时候b并还没有创建好,
                     // this.alreadyCreated.contains(beanName)=true表示此bean已经被创建过,就返回false
                    // 若该bean没有在alreadyCreated缓存里,
                    //      就是说没被创建过(其实只有CreatedForTypeCheckOnly才会是此仓库)
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName,
                            "Bean with name '" + beanName + "' has been injected into other beans [" +
                            StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                            "] in its raw version as part of a circular reference, but has" +
                            " eventually been wrapped. This means that said other beans  do not use " +
                            "the final version of the bean. This is often the result  of over-eager " +
                            "type matching - consider using 'getBeanNamesOfType' " +
                            "with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }
}

由于关键代码部分的步骤不太好拆分,为了更具象表达,那么使用下面一副图示帮助小伙伴们理解:

2019061918335746.png

五、总结

依旧以上面A、B类使用属性field注入循环依赖的例子为例,对整个流程做文字步骤总结如下:

  1. 使用context.getBean(A.class),旨在获取容器内的单例A(若A不存在,就会走A这个Bean的创建流程),显然初次获取A是不存在的,因此走A的创建之路。
  2. 实例化A(注意此处仅仅是实例化),并将它放进缓存(此时A已经实例化完成,已经可以被引用了)
  3. 初始化A:@Autowired依赖注入B(此时需要去容器内获取B)
  4. 为了完成依赖注入B,会通过getBean(B)去容器内找B。但此时B在容器内不存在,就走向B的创建之路。
  5. 实例化B,并将其放入缓存。(此时B也能够被引用了)
  6. 初始化B,@Autowired依赖注入A(此时需要去容器内获取A)
  7. 此处重要:初始化B时会调用getBean(A)去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)能够正常返回
  8. B初始化成功(此时已经注入A成功了,已成功持有A的引用了),return(注意此处return相当于是返回最上面的getBean(B)这句代码,回到了初始化A的流程中)。
  9. 因为B实例已经成功返回了,因此最终A也初始化成功
  10. 到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B。

六、拓展

6.1 多例的循环依赖可以解决吗

单例bean的循环引用是因为每个对象都是固定的,只是提前暴露对象的引用,最终这个引用对应的对象是创建完成的。但是多例的情况下,每次getBean都会创建一个新的对象,那么应该引用哪一个对象呢,这本身就已经是矛盾的了。多实例Bean是每次创建都会调用doGetBean方法,根本没有使用一二三级缓存,肯定不能解决循环依赖。因而spring中对于多例之间相互引用是会提示错误的。

可见spring会认为多例之间的循环引用是无法解决的。

6.2 构造器、setter注入方式的循环依赖可以解决吗

这里还是拿A和B两个Bean举例说明:

注入方式 是否解决循环依赖
均采用setter方法注入
均采用构造器注入
A中注入B的方式为setter方法,B中注入A的方式为构造器
B中注入A的方式为setter方法,A中注入B的方式为构造器

6.3 为什么是三级缓存,二级不行吗

6.3.1 可以去掉第三级缓存吗?

不可以去掉第三级缓存。

浅层原因

Spring的设计原则是在IOC结束之后再AOP(bean实例化、属性设置、初始化之后再通过进行AOP(生成代理对象))。即:AOP的实现需要与bean的生命周期的创建分离。

为了解决循环依赖但又尽量不打破这个设计原则的情况下,使用了第三级缓存(key:bean名字,value:ObjectFactory)。它将一个函数式接口作为ObjectFactory,相当于延迟初始化。在AOP或者解决循环依赖时,通过调用Object的getObject()方法获取到第三级缓存中的对象。

如果去掉第三级缓存,将AOP的代理工作放到第二级缓存,这样的话,bean在创建过程中就先生成代理对象再初始化和其他工作,与Spring的AOP的设计原则相违背。

深层原因

如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:

  1. 不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
  2. 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。

Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?

Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存earlySingletonObjects

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

6.3.2 可以去掉第二级缓存吗?

不可以去掉第二级缓存。

原因

第二级缓存可以解决实例被重复代理的问题。(同时,也是设计上的需要)

详解

假如有这种情况:A同时依赖于B和C,B和C都依赖于A。即:

  1. A实例化时,先提前暴露objectFactoryA到第三级缓存,调用getBean(B)依赖注入B实例。
  2. B实例化,实例化之后,提前暴露objectFactoryB到第三级缓存,调用getBean(A)依赖注入A实例,由于提前暴露了objectFactoryA,此时从第三级缓存中获取到A实例,并将A实例放到第二级缓存。B实例完成了依赖注入,升级为一级缓存。
  3. A实例化开始填充属性,调用getBean(C)注入C实例
  4. C实例化之后,提前暴露objectFactoryC到第三级缓存,调用getBean(A)依赖注入A实例。由于第2步将A实例放到了第二级缓存,此时可以从第二级缓存中获取到A实例。

那么,如果没有第二级缓存,会如何处理呢?

  • 法1:从第三级获取到实例后,放到第一级缓存
    • 存在的问题:按照设计思路,第一级存放的是初始化好的对象,直接放到第一级缓存是不合适的。
  • 法2:从第三级获取到实例后,不处理,下次依然从第三级缓存获取
    • 如果有其他实例依赖了这个对象,会再次对这个对象进行代理,这就导致了重复代理。
posted @ 2022-04-25 15:17  夏尔_717  阅读(260)  评论(0编辑  收藏  举报