- 前言 -
提問:
我們都知道Spring通過三級快取來解決回圈依賴的問題,那么是不是必須是三級快取?二級快取不能解決嗎?
要分析是否能夠去掉其中一級快取,我們需要先過一遍Spring是如何通過三級快取來解決回圈依賴的,2021Java面試寶典
- 回圈依賴 -
所謂的回圈依賴,就是兩個或者兩個以上的bean互相依賴對方,最終形成倍訓,
比如“A物件依賴B物件,而B物件也依賴A物件”,或者“A物件依賴B物件,B物件依賴C物件,C物件依賴A物件”;類似以下代碼:
public class A {
private B b;
}
public class B {
private A a;
}
常規情況下,會出現以下情況:
1、通過構建函式創建A物件(A物件是半成品,還沒注入屬性和呼叫init方法),
2、A物件需要注入B物件,發現物件池(快取)里還沒有B物件(物件在創建并且注入屬性和初始化完成之后,會放入物件快取里),
3、通過構建函式創建B物件(B物件是半成品,還沒注入屬性和呼叫init方法),
4、B物件需要注入A物件,發現物件池里還沒有A物件,
5、創建A物件,回圈以上步驟,
- 三級快取 -
Spring解決回圈依賴的核心思想在于提前曝光:
1、通過構建函式創建A物件(A物件是半成品,還沒注入屬性和呼叫init方法),
2、A物件需要注入B物件,發現快取里還沒有B物件,將半成品物件A放入半成品快取,
3、通過構建函式創建B物件(B物件是半成品,還沒注入屬性和呼叫init方法),
4、B物件需要注入A物件,從半成品快取里取到半成品物件A,
5、B物件繼續注入其他屬性和初始化,之后將完成品B物件放入完成品快取,
6、A物件繼續注入屬性,從完成品快取中取到完成品B物件并注入,
7、A物件繼續注入其他屬性和初始化,之后將完成品A物件放入完成品快取,
其中快取有三級:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

?
要了解原理,最好的方法就是閱讀原始碼,從創建Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手,
一. 在構造Bean物件之后,將物件提前曝光到快取中,這時候曝光的物件僅僅是構造完成,還沒注入屬性和初始化,
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
……
// 是否提前曝光
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");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
……
}
}
二. 提前曝光的物件被放入Map<String, ObjectFactory<?>> singletonFactories快取中,這里并不是直接將Bean放入快取,而是包裝成ObjectFactory物件再放入,
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 一級快取
if (!this.singletonObjects.containsKey(beanName)) {
// 三級快取
this.singletonFactories.put(beanName, singletonFactory);
// 二級快取
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
}
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
三. 為什么要包裝一層ObjectFactory物件?
如果創建的Bean有對應的代理,那其他物件注入時,注入的應該是對應的代理物件;但是Spring無法提前知道這個物件是不是有回圈依賴的情況,而正常情況下(沒有回圈依賴情況),Spring都是在創建好完成品Bean之后才創建對應的代理,這時候Spring有兩個選擇:
1、不管有沒有回圈依賴,都提前創建好代理物件,并將代理物件放入快取,出現回圈依賴時,其他物件直接就可以取到代理物件并注入,
2、不提前創建好代理物件,在出現回圈依賴被其他物件注入時,才實時生成代理物件,這樣在沒有回圈依賴的情況下,Bean就可以按著Spring設計原則的步驟來創建,
Spring選擇了第二種方式,那怎么做到提前曝光物件而又不生成代理呢?
Spring就是在物件外面包一層ObjectFactory,提前曝光的是ObjectFactory物件,在被注入時才在ObjectFactory.getObject方式內實時生成代理物件,并將生成好的代理物件放入到第二級快取Map<String, Object> earlySingletonObjects, addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));:
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
}
為了防止物件在后面的初始化(init)時重復代理,在創建代理時,earlyProxyReferences快取會記錄已代理的物件,
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
四. 注入屬性和初始化
提前曝光之后:
1、通過populateBean方法注入屬性,在注入其他Bean物件時,會先去快取里取,如果快取沒有,就創建該物件并注入,
2、通過initializeBean方法初始化物件,包含創建代理,
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
……
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
……
}
}
// 獲取要注入的物件
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
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;
}
}
五. 放入已完成創建的單例快取
在經歷了以下步驟之后,最終通過addSingleton方法將最終生成的可用的Bean放入到單例快取里,
1、
AbstractBeanFactory.doGetBean ->
2、
DefaultSingletonBeanRegistry.getSingleton ->
3、
AbstractAutowireCapableBeanFactory.createBean ->
4、
AbstractAutowireCapableBeanFactory.doCreateBean ->
5、
DefaultSingletonBeanRegistry.addSingleton
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
- 二級快取 -
上面第三步《為什么要包裝一層ObjectFactory物件?》里講到有兩種選擇:
1、不管有沒有回圈依賴,都提前創建好代理物件,并將代理物件放入快取,出現回圈依賴時,其他物件直接就可以取到代理物件并注入,
2、不提前創建好代理物件,在出現回圈依賴被其他物件注入時,才實時生成代理物件,這樣在沒有回圈依賴的情況下,Bean就可以按著Spring設計原則的步驟來創建,
Sping選擇了第二種,如果是第一種,就會有以下不同的處理邏輯:
1、在提前曝光半成品時,直接執行getEarlyBeanReference創建到代理,并放入到快取earlySingletonObjects中,
2、有了上一步,那就不需要通過ObjectFactory來延遲執行getEarlyBeanReference,也就不需要singletonFactories這一級快取,
這種處理方式可行嗎?
這里做個試驗,對AbstractAutowireCapableBeanFactory做個小改造,在放入三級快取之后立刻取出并放入二級快取,這樣三級快取的作用就完全被忽略掉,就相當于只有二級快取,
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
……
// 是否提前曝光
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");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 立刻從三級快取取出放入二級快取
getSingleton(beanName, true);
}
……
}
}
測驗結果是可以的,并且從原始碼上分析可以得出兩種方式性能是一樣的,并不會影響到Sping啟動速度,
那為什么Sping不選擇二級快取方式,而是要額外加一層快取?
如果要使用二級快取解決回圈依賴,意味著Bean在構造完后就創建代理物件,這樣違背了Spring設計原則,
Spring結合AOP跟Bean的生命周期,是在Bean創建完全之后通過AnnotationAwareAspectJAutoProxyCreator這個后置處理器來完成的,在這個后置處理的postProcessAfterInitialization方法中對初始化后的Bean完成AOP代理,
如果出現了回圈依賴,那沒有辦法,只有給Bean先創建代理,但是沒有出現回圈依賴的情況下,設計之初就是讓Bean在生命周期的最后一步完成代理而不是在實體化后就立馬完成代理,2021Java面試寶典
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/254335.html
標籤:其他
上一篇:想讓行程后臺運行,試試Linux的nohup命令,3分鐘學會。
下一篇:Soul網關使用感受
