乐趣区

关于java:一张图彻底搞懂Spring循环依赖

1 什么是循环依赖?

如下图所示:

BeanA 类依赖了 BeanB 类,同时 BeanB 类又依赖了 BeanA 类。这种依赖关系造成了一个闭环,咱们把这种依赖关系就称之为循环依赖。同理,再如下图的状况:

上图中,BeanA 类依赖了 BeanB 类,BeanB 类依赖了 BeanC 类,BeanC 类依赖了 BeanA 类,如此,也造成了一个依赖闭环。再比方:

上图中,本人援用了本人,本人和本人造成了依赖关系。同样也是一个依赖闭环。那么,如果呈现此类循环依赖的状况,会呈现什么问题呢?

2 循环依赖问题复现

2.1 定义依赖关系

咱们持续扩大后面的内容,给 ModifyService 减少一个属性,代码如下:


@GPService
public class ModifyService implements IModifyService {

    @GPAutowired private QueryService queryService;
    
    ...

}

给 QueryService 减少一个属性,代码如下:


@GPService
@Slf4j
public class QueryService implements IQueryService {

    @GPAutowired private ModifyService modifyService;

    ...
    
}

如此,ModifyService 依赖了 QueryService,同时 QueryService 也依赖了 ModifyService,造成了依赖闭环。那么这种状况下会呈现什么问题呢?

2.2 问题复现

咱们来运行调试一下之前的代码,在 GPApplicationContext 初始化后打上断点,咱们来跟踪一下 IoC 容器外面的状况,如下图:

启动我的项目,咱们发现只有是有循环依赖关系的属性并没有主动赋值,而没有循环依赖关系的属性均有主动赋值,如下图所示:

这种状况是怎么造成的呢?咱们剖析起因之后发现,因为,IoC 容器对 Bean 的初始化是依据 BeanDefinition 循环迭代,有肯定的程序。这样,在执行依赖注入时,须要主动赋值的属性对应的对象有可能还没初始化,没有初始化也就没有对应的实例能够注入。于是,就呈现咱们看到的状况。

3 应用缓存解决循环依赖问题

3.1 定义缓存

具体代码如下:


// 循环依赖的标识 --- 以后正在创立的实例 bean
    private Set<String> singletonsCurrectlyInCreation = new HashSet<String>();

    // 一级缓存
    private Map<String, Object> singletonObjects = new HashMap<String, Object>();

    // 二级缓存: 为了将成熟的 bean 和污浊的 bean 拆散. 防止读取到不残缺的 bean.
private Map<String, Object> earlySingletonObjects = new HashMap<String, Object>();

3.2 判断循环依赖

减少 getSingleton() 办法:


/**
     * 判断是否是循环援用的进口.
     * @param beanName
     * @return
     */
    private Object getSingleton(String beanName,GPBeanDefinition beanDefinition) {

        // 先去一级缓存里拿,
        Object bean = singletonObjects.get(beanName);
        // 一级缓存中没有, 然而正在创立的 bean 标识中有, 阐明是循环依赖
        if (bean == null && singletonsCurrentlyInCreation.contains(beanName)) {bean = earlySingletonObjects.get(beanName);
            // 如果二级缓存中没有, 就从三级缓存中拿
            if (bean == null) {
                // 从三级缓存中取
                Object object = instantiateBean(beanName,beanDefinition);

                // 而后将其放入到二级缓存中. 因为如果有屡次依赖, 就去二级缓存中判断. 曾经有了就不在再次创立了
                earlySingletonObjects.put(beanName, object);


            }
        }
        return bean;
    }
        

3.3 增加缓存

批改 getBean() 办法,在 getBean() 办法中增加如下代码:


         //Bean 的实例化,DI 是从而这个办法开始的
    public Object getBean(String beanName){

        //1、先拿到 BeanDefinition 配置信息
        GPBeanDefinition beanDefinition = regitry.beanDefinitionMap.get(beanName);

        // 减少一个进口. 判断实体类是否曾经被加载过了
        Object singleton = getSingleton(beanName,beanDefinition);
        if (singleton != null) {return singleton;}

        // 标记 bean 正在创立
        if (!singletonsCurrentlyInCreation.contains(beanName)) {singletonsCurrentlyInCreation.add(beanName);
        }

        //2、反射实例化 newInstance();
        Object instance = instantiateBean(beanName,beanDefinition);

        // 放入一级缓存
        this.singletonObjects.put(beanName, instance);

        //3、封装成一个叫做 BeanWrapper
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        //4、执行依赖注入
        populateBean(beanName,beanDefinition,beanWrapper);
        //5、保留到 IoC 容器
        factoryBeanInstanceCache.put(beanName,beanWrapper);

        return beanWrapper.getWrapperInstance();}

3.4 增加依赖注入

批改 populateBean() 办法,代码如下:


    private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {

        ...

            try {//ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
                field.set(instance,getBean(autowiredBeanName));
            } catch (IllegalAccessException e) {e.printStackTrace();
                continue;
            }
        ...

    }

4 循环依赖对 AOP 创立代理对象的影响

4.1 循环依赖下的代理对象创立过程

咱们都晓得 Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由主动代理创立器来主动实现的。也就是说 Spring 最终给咱们放进容器外面的是一个代理对象,而非原始对象。

这里咱们联合循环依赖,再剖析一下 AOP 代理对象的创立过程和最终放进容器内的动作,看如下代码:


@Service
public class MyServiceImpl implements MyService {
    @Autowired
    private MyService myService;
    
    @Transactional
    @Override
    public Object hello(Integer id) {return "service hello";}
}

此 Service 类应用到了事务,所以最终会生成一个 JDK 动静代理对象 Proxy。刚好它又存在本人援用本人的循环依赖的状况。跟进到 Spring 创立 Bean 的源码局部,来看 doCreateBean() 办法:


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) {
                // 后面讲到因为本人被循环依赖了,所以此时候代理对象还寄存在二级缓存中
                // 因而,此处 getSingleton(),就会把代理对象拿进去
                // 而后赋值给 exposedObject 对象并返回,最终被 addSingleton() 增加进一级缓存中
                // 这样就保障了咱们容器里缓存的对象实际上是代理对象,而非原始对象

                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {// 这个判断不可少(因为 initializeBean() 办法中给 exposedObject 对象从新赋过值,否则就是是两个不同的对象实例)if (exposedObject == bean) {exposedObject = earlySingletonReference;}
                }
                ...
        }
    
}

以上代码剖析的是代理对象有本人存在循环依赖的状况,Spring 用三级缓存很奇妙的进行解决了这个问题。

4.2 非循环依赖下的代理对象创立过程

如果本人并不存在循环依赖的状况,Spring 的处理过程就略微不同,持续跟进源码:


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

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

        ...
    
        // 此处留神,因为没有循环援用, 所以下面 getEarlyBeanReference() 办法不会执行
        // 也就是说此时二级缓存里并不会存在
        populateBean(beanName, mbd, instanceWrapper);

        // 重点在此
        //AnnotationAwareAspectJAutoProxyCreator 主动代理创立器此处的 postProcessAfterInitialization() 办法里,会给创立一个代理对象返回
        // 所以此局部执行实现后,exposedObject() 容器中缓存的曾经是代理对象,不再是原始对象
     // 此时二级缓存里仍旧无它,更别提一级缓存了
     exposedObject = initializeBean(beanName, exposedObject, mbd);

        ...
    
        // 循环依赖校验
        if (earlySingletonExposure) {
                // 后面讲到一级、二级缓存里都没有缓存,而后这里传参数是 false,示意不从三级缓存中取值
                // 因而,此时 earlySingletonReference = null,并间接返回

                // 而后执行 addSingleton() 办法,由此可知,容器里最终存在的也还是代理对象

                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}
                }
             ...
}

依据以上代码剖析可知,只有用到代理,没有被循环援用的,最终存在 Spring 容器里缓存的仍旧是代理对象。如果咱们敞开 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 后给其属性赋值时,Spring 会去实例化 B。B 实例化实现后会持续给 B 属性赋值,因为咱们敞开了循环依赖,所以不存在提前裸露援用。因而 B 无奈间接拿到 A 的援用地址,只能又去创立 A 的实例。而此时咱们晓得 A 其实曾经正在创立中了,不能再创立了。所有就呈现了异样。对照演示代码,来剖析一下程序运行过程:


@Service
public class MyServiceImpl implements MyService {

    // 因为敞开了循环依赖,所以此处不能再依赖本人
    // 然而 MyService 须要创立 AOP 代理对象
    //@Autowired
    //private MyService myService;
    
    @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);

        // 若是开启事务,此处会为原生 Bean 创立代理对象
        exposedObject = initializeBean(beanName, exposedObject, mbd);

        if (earlySingletonExposure) {
                ... 

                // 因为下面没有提前裸露代理对象,所以下面的代理对象 exposedObject 间接返回。}
}

由下面代码可知,即便敞开循环依赖开关,最终缓存到容器中的对象仍旧是代理对象,显然 @Autowired 给属性赋值的也肯定是代理对象。

最初,以 AbstractAutoProxyCreator 为例看看主动代理创立器实现循环依赖代理对象的细节。

AbstractAutoProxyCreator 是抽象类,它的三大实现子类 InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator 小伙伴们应该比拟相熟,该抽象类实现了创立代理的动作:


// 该类实现了 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() 返回值必定是原始对象
            
            // 若没有被循环援用,getEarlyBeanReference() 不执行
            // 所以 remove() 办法返回 null,此时进入 if 执行逻辑,调用创立代理对象办法
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;

    }
    ...
}

依据以上剖析可得悉,主动代理创立器它保障了代理对象只会被创立一次,而且反对循环依赖的主动注入的仍旧是代理对象。由下面剖析得出结论,在 Spring 容器中,不管是否存在循环依赖的状况,甚至敞开 Spring 容器的循环依赖性能,它对 Spring AOP 代理的创立流程有影响,但对后果是无影响的。也就是说 Spring 很好地屏蔽了容器中对象的创立细节,让使用者齐全无感知。

本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!
如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『Tom 弹架构』可获取更多技术干货!

退出移动版