前言
Spring 解决 Bean 之间的循环引用关系用到了三级缓存,那么问题来了。三级缓存是怎么用的?每一层的作用是什么?非得用三级吗?两级缓存行不行?
理解循环引用
所谓的“循环引用”是指 Bean 之间的依赖关系形成了一个循环,例如 a 依赖 b,b 又依赖 a。
@Component
public class A {
@Autowired
B b;
}
@Component
public class A {
@Autowired
B b;
}
开发者在设计阶段,应该尽量避免出现循环引用,因为这种依赖关系本身就违反了“单一职责”的设计原则,增加了代码的复杂性和维护成本。
抛开设计原则不谈,循环引用给 Spring 带来了依赖注入的问题。在这个例子中,Spring 实例化 a 时发现要为其注入 b,于是赶忙去实例化 b,实例化 b 的过程中又发现要为其注入 a,但此时 a 还没创建完,陷入一个死循环,就像死锁一样。
为什么多级缓存可以解决循环引用的问题呢?
理解多级缓存
Spring 里的三级缓存其实就是三个 Map 容器,先抛开 Spring 不谈,如果让我们设计一个多级缓存方案来解决循环引用的问题,我们会怎么做呢?
一级缓存
一级缓存的作用是为了保证单例。在 Spring 里面,Bean 默认是单例的,多次调用getBean()
得到的是同一个实例。基于这个规则,我们可以用一级缓存来实现一个保证单例的 IOC 容器。
public class Ioc1 {
private final Map<Class, Object> singletonObjects = new ConcurrentHashMap<>();
public synchronized <T> T getBean(Class<T> clazz) {
Object bean = singletonObjects.get(clazz);
if (bean == null) {
singletonObjects.put(clazz, bean = createBean(clazz));
}
return (T) bean;
}
@SneakyThrows
private <T> Object createBean(Class<T> clazz) {
T bean = clazz.newInstance();
return bean;
}
}
二级缓存
一级缓存只能保证单例,对于循环引用是没办法解决的。因此,我们可以再加一级缓存,引入二级缓存来解决循环引用问题。于是,我们实现了第二版 IOC 容器,核心思路是把未被初始化的 bean 提前暴露到二级缓存,依赖注入时允许注入半成品 bean。
public class Ioc2 {
private final Map<Class, Object> singletonObjects = new ConcurrentHashMap<>();
private final Map<Class, Object> earlySingletonObjects = new ConcurrentHashMap<>();
public synchronized <T> T getBean(Class<T> clazz) {
Object bean = singletonObjects.get(clazz);
if (bean == null) {
bean = earlySingletonObjects.get(clazz);
if (bean == null) {
singletonObjects.put(clazz, bean = createBean(clazz));
}
}
return (T) bean;
}
@SneakyThrows
private <T> Object createBean(Class<T> clazz) {
T bean = clazz.newInstance();
// 把未被初始化的bean提前暴露到二级缓存
earlySingletonObjects.put(clazz, bean);
populateBean(bean);
return bean;
}
// 属性注入
@SneakyThrows
private <T> void populateBean(T bean) {
for (Field field : bean.getClass().getDeclaredFields()) {
Object fieldValue = getBean(field.getType());
field.setAccessible(true);
field.set(bean, fieldValue);
}
}
}
三级缓存
大多数情况下,二级缓存已经够用了,但是 Spring 还有一项强大的功能:基于 Bean 生成代理对象做增强。
此时,用二级缓存就面临一个问题:代理对象何时生成?
- 如果等 Bean 初始化后再生成 Proxy,那已经被注入的属性却是个未被代理 Bean,这显然是不能接受的
- 如果 Bean 初始化前就提前生成代理对象,这样能保证注入的属性也是被代理的 Bean,但是这不符合 Spring 的设计原则
为什么提前生成代理对象不符合 Spring 的设计原则呢???
因为在 Spring 的 Bean 和 AOP 的生命周期里,应该是先实例化并初始化 Bean 以后,再调用 BeanPostProcessor 的子类 AbstractAutoProxyCreator 的后处理器方法来生成代理对象。提前基于未被初始化的半成品 Bean 生成代理对象,这一点违背了 Spring 的设计原则,后处理器理应认为要扩展的 Bean 是一个完整的 Bean,万一需要访问其属性,拿到的却是 null,可能导致程序错误。
所以,我们可以得出一个结论:Spring 循环引用的问题,只用二级缓存是完全没问题的,前提是要提前生成代理 Bean 并暴露到二级缓存。功能是没问题,但是这一点违背了 Spring 的设计原则,所以 Spring 要尽量避免这个问题,才引入的三级缓存。
Spring 给出的方案是:引入一个三级缓存,尽可能避免提前创建代理对象,万一真的发生了循环引用,不得已而为之,也只能提前生成了。
所以,我们现在可以给出一个最终版的 IOC 实现了,逻辑基本和 Spring 一致。核心思路是,Bean 实例化以后,提前暴露一个 ObjectFactory 到三级缓存,如果没有循环引用,代理对象不会提前创建,Bean 的生命周期保持一致。如果发生了循环引用,就只能提前创建代理对象,并把它从三级缓存挪到二级缓存,避免重复创建,其它 Bean 注入的也是被代理后的 Bean。
public class Ioc3 {
private final Map<Class, Object> singletonObjects = new ConcurrentHashMap<>();
private final Map<Class, Object> earlySingletonObjects = new ConcurrentHashMap<>();
private final Map<Class, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>();
// 代理对象缓存
private final Map<Class, Object> proxyCache = new ConcurrentHashMap<>();
public synchronized <T> T getBean(Class<T> clazz) {
Object bean = singletonObjects.get(clazz);
if (bean == null) {
bean = earlySingletonObjects.get(clazz);
if (bean == null) {
ObjectFactory<?> objectFactory = singletonFactories.get(clazz);
if (objectFactory != null) {
bean = objectFactory.getObject();
earlySingletonObjects.put(clazz, bean);
singletonFactories.remove(clazz);
} else {
bean = createBean(clazz);
singletonObjects.put(clazz, bean);
}
}
}
return (T) bean;
}
@SneakyThrows
private <T> Object createBean(Class<T> clazz) {
T bean = clazz.newInstance();
singletonFactories.put(clazz, () -> wrapIfNecessary(bean));
populateBean(bean);
return wrapIfNecessary(bean);
}
private <T> T wrapIfNecessary(final T bean) {
if (true) {
Object proxy = proxyCache.get(bean.getClass());
if (proxy == null) {
proxy = ProxyFactory.getProxy(bean.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (method.getDeclaringClass().isAssignableFrom(Object.class)) {
return method.invoke(this, args);
}
System.err.println("before...");
Object result = method.invoke(bean, args);
System.err.println("after...");
return result;
}
});
proxyCache.put(bean.getClass(), proxy);
}
return (T) proxy;
}
return bean;
}
@SneakyThrows
private <T> void populateBean(T bean) {
for (Field field : bean.getClass().getDeclaredFields()) {
Object fieldValue = getBean(field.getType());
field.setAccessible(true);
field.set(bean, fieldValue);
}
}
}
Spring三级缓存实现
Spring 三级缓存对应的 Map 声明在 DefaultSingletonBeanRegistry 类,如下:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
一级缓存 singletonObjects:存放完整的,初始化后的 Bean
二级缓存 singletonFactories:存放未被初始化的半成品 Bean
三级缓存 earlySingletonObjects:存放 Bean 对应的 ObjectFactory,用于提前生成代理对象
在获取单例 Bean 时,Spring 会先查找一级和二级缓存,都没有时再查找三级缓存,如果找到了,就提前创建代理对象并返回。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 查一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
// 没有且Bean在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 查二级缓存取
singletonObject = this.earlySingletonObjects.get(beanName);
// 没有且允许引用半成品Bean
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// double check
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 三级缓存如果有,创建代理对象,挪到二级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
如果不存在循环引用,ObjectFactory#getObject
就不会被调用,也就不会提前创建代理对象。
在doCreateBean()
方法里,会提前把未被初始化的半成品 Bean 封装成 ObjectFactory 暴露到三级缓存:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
......
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
......
}
getObject()
方法也就是getEarlyBeanReference()
,它会调用后处理器SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
生成代理对象(未必所有的Bean都需要被代理),目前只有一个实现类 AbstractAutoProxyCreator:
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
尾巴
Spring 为了解决循环引用的问题,设计了三级缓存。一级缓存的作用是保证单例;二级缓存的作用是解决循环引用;三级缓存是为了尽量避免基于半成品 Bean 提前创建代理对象。单从功能上说,只用二级缓存完全没问题,前提是被代理的 Bean 都要提前创建代理对象,这一点违背了 Spring 的设计原则。在 Spring Bean 的生命周期里,应该是先实例化并初始化 Bean 后再通过后处理器进行扩展,对一个未被初始化的 Bean 做扩展,万一要访问其属性可能就会导致程序错误。Spring 的做法是提前暴露一个 ObjectFactory 对象,如果没有发生循环引用,其getObject()
就不会被调用,代理对象就不会提前生成,尽可能的保证 Bean 生命周期一致。
但是,如果真的发生了循环引用,且引用的 Bean 又是需要被代理的,也只能提前生成代理对象了。因为这么做违背了 Spring 的设计原则,所以新版本的 Spring 默认已经不允许循环引用了,必须手动开启。