2020-9-1更新
大白话
先说下spring注入属性为null
最主要的原因就是自己new的,是不给spring分配的,要么改成spring扫描注入,要么自己去获取bean
如何理解“依赖”呢,在Spring中有:
- 构造器循环依赖
- field属性注入循环依赖
构造器循环依赖(失败)
@Service
public class A {
public A(B b) { }
}
@Service
public class B {
public B(C c) {
}
}
@Service
public class C {
public C(A a) { }
}
field属性注入循环依赖(成功)
@Service
public class A1 {
@Autowired
private B1 b1;
}
@Service
public class B1 {
@Autowired
public C1 c1;
}
@Service
public class C1 {
@Autowired public A1 a1;
}
field属性注入循环依赖,多例(失败)
@Service
@Scope("prototype")
public class A1 {
@Autowired
private B1 b1;
}
@Service
@Scope("prototype")
public class B1 {
@Autowired
public C1 c1;
}
@Service
@Scope("prototype")
public class C1 {
@Autowired public A1 a1;
}
现象总结:同样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。因为@Service默认是单例的,所以单例的属性注入是可以成功的。
基于构造器的循环依赖,就更不用说了,官方文档都摊牌了,你想让构造器注入支持循环依赖,是不存在的,不如把代码改了。
那么默认单例的属性注入场景,Spring是如何支持循环依赖的?
Spring解决循环依赖
首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。
在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map:
-
singletonObjects 它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。
-
singletonFactories 映射创建Bean的原始工厂,仅在当前 bean 创建时存在, 是尚未调用 createBean 的 bean。用于 setter 循环依赖时实现注入。
-
earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance。仅在当前 bean 创建时存在,用于检测代理 bean 循环依赖
-
其实还有一个,singletonsCurrentlyInCreation 是 beanName 的集合,用于检测构造器循环依赖。
后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。
一级缓存:
/** 保存所有的singletonBean的实例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
二级缓存:
/** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
三级缓存:
/** singletonBean的生产工厂*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** 保存所有已经完成初始化的Bean的名字(name) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
/** 标识指定name的Bean对象是否处于创建状态 这个状态非常重要 */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
流程大概是
- A开始
- 到getSingleton
- 再到doCreateBean
- 然后提前暴露,earlySingletonExposure = true,在singletonFactories中有【0,暴露的A工厂】
- A再到populateBean
- 填充属性,发现需要属性B
- B就开始实例,从getSingleton过来
- B到了doCreateBean
- B做的跟A一样,提前暴露,earlySingletonExposure = true。所以调用addSingletonFactory,扔到工厂缓存
- singletonFactories中有【0,暴露的A工厂】,【1,暴露的B工厂】
- B也到populateBean,发现自己需要属性A
- 于是A又开始实例,进入getSingleton
- 根据集合singletonsCurrentlyInCreation,发现A已经在创建中的状态,于是从A工厂产出“早期”A,挪到“早期”缓存
- 利用工厂在earlySingletonObjects创建A【0,早期A】,并移除在singletonFactories的A工厂
- 【0,早期A】放入addSingleton,populateBean里的B拿到了早期A
- 于是B实例完成,到singletonObjects
- B实例完,populateBean里的A也到addSingleton,最后到singletonObjects
大白话说就是
A第一次初始化,缓存肯定没有,所以get不到
因为B是通过Autowired关联的,所以初始化A并不会触发B的初始化
此时初始化结束,但还没设置,所以把A放到三级缓存singletonFactory
这时候要设置值了,也就是populateBean(A),因为A中auttowired了B,所以触发getBean(B),也就是B的初始化
B也是第一次初始化,所以缓存也没有
所以B做了跟A一样的事,初始化,放入三级缓存singletonFactory中
然后给B设置值,B里autowired了A,触发A的初始化
因为A是第二次初始化,直接从三级缓存中singletonFactory拿到了之前放进去的值
拿到缓存中的值,虽然没有初始化完全,但spring认为是可以使用的状态
于是B实例化结束,于是A就拿到B也实例化结束