spring bean 循环依赖

1,147 阅读2分钟

前提

在我们的就旧项目中所有的service bean都带上@scope(“prototype”)这样的注解,在开发新的业务时,出现两bean之间的相互依赖,A依赖B、 B依赖A导致项目启动不了。

bean 循环依赖

模拟上面的业务场景,我们创建3个类,这3个类的依赖关系如下:
┌─────┐
|    AA 
↑     ↓
|    BB 
↑     ↓
|    CC 
└─────┘

@Component
@Scope("prototype")
public class AA {
    private BB bb;
    public AA(BB bb) {
        this.bb =bb;
    }
}

@Component
@Scope("prototype")
public class BB {
    private CC cc;
    public BB(CC cc) {
        this.cc =cc;
    }
}

@Component
@Scope("prototype")
public class CC {
    private AA aa;
    public CC(AA aa) {
        this.aa =aa;
    }
}

由于spring bean加载机制是用到才会去创建bean的,所以在入口方法我们注入AA类。
启动时,我们发现启动不了,提示 The dependencies of some of the beans in the application context form a cycle
   

我们尝试通过@Autowired注入,重新启动下,启动不成功。

我们尝试将@Scope("prototype")注释后,重新启动下,启动成功。

Spring解决循环依赖

基于构造器注入

spring 在创建bean的时候,基于构造器注入对象的bean,但是如果bean之间是循环关系的,A bean 要注入b bean ,b bean 在创建的时候又要注入A bean,两个bean就不能完成注入了,并抛出beanccurrentlyincreationexception,典型的先有鸡还是先有蛋的场景。

基于字段注入 单例模式

首先在解决注入问题先,我们先逆向推理下,spring是怎么实现注入的。

    public static Map<String,Object> map = new ConcurrentHashMap();

    public static Object getBean(Class classz) throws Exception {
        // 将对象本身实例化
        Object object = classz.getDeclaredConstructor().newInstance();
        map.put(classz.getSimpleName(),object);
        // 把所有字段当成需要注入的bean,创建并注入到当前bean中
        Field[] fields = object.getClass().getDeclaredFields();
        for(Field f:fields){
            f.setAccessible(true);
            //获取字段类型
            Class<?> fieldClass = f.getType();
            //从缓存中获取已经实例化的bean
            Object waitInjectObject = map.get(fieldClass.getSimpleName());
            if(waitInjectObject !=null){
                f.set(object,waitInjectObject);
            }else {
                f.set(object,getBean(fieldClass));
            }

        }
        return object;
    }

我的想法是通过一个缓存对象,将已创建好的bean存储起来,当有需要的时候从缓存获取。

接下来我们再看下spring是怎么解决的。

#AbstractAutowireCapableBeanFactory

image.png

在AbstractAutowireCapableBeanFactory有这么一句话Eagerly cache singletons to be able to resolve circular references 主动缓存单例,以便能够解析循环引用。

image.png

Spring内部维护了三个Map,也就是我们通常说的三级缓存

  • singletonObjects 完整的单例bean
  • singletonFactories bean工厂,bean的信息
  • earlySingletonObjects 早期的bean对象,不完整的bean

image.png

singletonFactories 获取到bean工厂,然后创建未解决依赖的bean存在在earlySingletonObjects,最后调用doResolveDependency方法从earlySingletonObjects获取到bean注入到字段中,完成注入,最后将完整的bean存在到singletonObjects中。

多例的情况,由于无法使用三级缓存,所以无法解决循环依赖的问题。