徹底講透Spring三級緩存,原理源碼深度剖析!


一、前言
循環依賴:就是N個類循環(嵌套)引用。
通俗的講就是N個Bean互相引用對方,最終形成閉環。在日常的開發中,我們都會碰到類似如下的代碼

@Service
public class AServiceImpl implements AService {
@Autowired
private BService bService;
...
}
@Service
public class BServiceImpl implements BService {
@Autowired
private AService aService;
...
}
1
2
3
4
5
6
7
8
9
10
11
12
這其實就是Spring環境下典型的循環依賴場景。但是很顯然,這種循環依賴場景,Spring已經完美的幫我們解決和規避了問題。那么Spring是如何實現的呢?

二、Spring循環依賴分析
2.1 循環依賴場景
在Spring環境中,因為我們的Bean的實例化、初始化都是交給了容器,因此它的循環依賴主要表現為下面三種場景。

2.1.1 構造器注入循環依賴
@Service
public class A {
public A(B b) {
}
}
@Service
public class B {
public B(A a) {
}
}
1
2
3
4
5
6
7
8
9
10
構造器注入構成的循環依賴,此種循環依賴方式是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環依賴。這也是構造器注入的最大劣勢(它有很多獨特的優勢,請小伙伴自行發掘)

根本原因:Spring解決循環依賴依靠的是Bean的“中間態”這個概念,而這個中間態指的是已經實例化,但還沒初始化的狀態。而構造器是完成實例化的東東,所以構造器的循環依賴無法解決~~~

2.1.2 field屬性注入(setter方法注入)循環依賴
這種方式是我們最最最最為常用的依賴注入方式(所以猜都能猜到它肯定不會有問題啦):

@Service
public class A {
@Autowired
private B b;
}

@Service
public class B {
@Autowired
private A a;
}
1
2
3
4
5
6
7
8
9
10
11
2.1.3 prototype field屬性注入循環依賴
prototype在平時使用情況較少,但是也並不是不會使用到,因此此種方式也需要引起重視。

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
@Autowired
private B b;
}

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
@Autowired
private A a;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
結果:需要注意的是本例中啟動時是不會報錯的(因為非單例Bean默認不會初始化,而是使用時才會初始化),所以很簡單咱們只需要手動getBean()或者在一個單例Bean內@Autowired一下它即可

// 在單例Bean內注入
@Autowired
private A a;
1
2
3
這樣子啟動就報錯(在一個單例bean中,自動注入一個多例bean實例,肯定報錯的)。

對於Spring循環依賴的情況總結如下:

不能解決的情況:
(1) 構造器注入循環依賴
(2) prototype field屬性注入循環依賴
能解決的情況:
(1) field屬性注入(setter方法注入)循環依賴

2.2 原理分析
Spring的循環依賴的理論依據基於Java的引用傳遞,當獲得對象的引用時,對象的屬性是可以延后設置的。

2.2.1 Spring創建Bean的流程
首先需要了解是Spring它創建Bean的流程,我把它的大致調用棧繪圖如下:

 

 


對Bean的創建最為核心三個方法解釋如下:

createBeanInstance:例化,其實也就是調用對象的構造方法實例化對象;
populateBean:填充屬性,這一步主要是對bean的依賴屬性進行注入(@Autowired);
initializeBean:回到一些形如initMethod、InitializingBean等方法;
從對單例Bean的初始化可以看出,循環依賴主要發生在第二步(populateBean),也就是field屬性注入的處理。

2.2.2 Spring容器的三級緩存
在Spring容器的整個聲明周期中,單例Bean有且僅有一個對象。這很容易讓人想到可以用緩存來加速訪問。

從源碼中也可以看出Spring大量運用了Cache的手段,在循環依賴問題的解決過程中甚至不惜使用了“三級緩存”,這也便是它設計的精妙之處~

三級緩存其實它更像是Spring容器工廠的內的術語,采用三級緩存模式來解決循環依賴問題,這三級緩存分別指:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
// 從上至下 分表代表這“三級緩存”
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一級緩存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二級緩存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三級緩存
...

/** Names of beans that are currently in creation. */
// 這個緩存也十分重要:它表示bean創建過程中都會在里面呆着~
// 它在Bean開始創建時放值,創建完成時會將其移出~
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

/** Names of beans that have already been created at least once. */
// 當這個Bean被創建完成后,會標記為這個 注意:這里是set集合 不會重復
// 至少被創建了一次的 都會放進這里~~~~
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
注:AbstractBeanFactory繼承自DefaultSingletonBeanRegistry

singletonObjects:用於存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用;
earlySingletonObjects:提前曝光的單例對象的cache,存放原始的 bean 對象(尚未填充屬性),用於解決循環依賴;
singletonFactories:單例對象工廠的cache,存放 bean 工廠對象,用於解決循環依賴;
為啥Spring需要設計成三級緩存?

一級緩存需要的原因,大家應該都已了解,現在簡單說下為啥Spring需要設計成三級緩存。

(1) 為啥需要二級緩存?

一級緩存的問題在於,就1個map,里面既有完整的已經ready的bean,也有不完整的,尚未設置field的bean。如果這時候,有其他線程去這個map里獲取bean來用怎么辦?拿到的bean,不完整,怎么辦呢?屬性都是null,直接空指針了。所以,我們就要加一個map,這個map,用來存放那種不完整的bean。也就是需要二級緩存。

(2) 為啥需要三級緩存?

怎么理解呢? 以io流舉例,我們一開始都是用的原始字節流,然后給別人用的也是字節流,但是,最后,我感覺不方便,我自己悄悄弄了個緩存字符流(類比代理對象),我是方便了,但是,別人用的,還是原始的字節流啊。你bean不是單例嗎?不能這么玩吧?所以,這就是二級緩存,不能解決的問題。
注:為什么不直接將類的代理對象生成,然后放入二級緩存?

因為類的代理對象必須是在類的實例對象已生成的基礎上去生成的,如果中間存在類的代理對象的循環依賴,是無法先生成類的代理對象,然后放入二級緩存。也就是二級緩存只能解決普通實例對象的循環依賴,如果存在代理對象的循環依賴,是無法解決的。

三級緩存設計:

@Service
public class A {
@Autowired
private B b;
}

@Service
public class B {
@Autowired
private A a;
}
1
2
3
4
5
6
7
8
9
10
11
還是拿上面這個示例舉例,另外類A、B上假如都有切面AOP,那么Spring容器中會存在proxyA、proxyB,並分別自動注入到對方實例的屬性上。

要解決類代理對象的循環依賴這個問題,比如beanA在填充beanB的實例時,查找到最終形態的beanB,即代理后的proxyB。
怎么做到這點呢?
加個三級緩存,里面不存具體的bean,里面存一個工廠對象。通過工廠對象,是可以拿到最終形態的代理后的proxyB。

具體是如何實現的,我們可以后面看源碼分析下。

2.2.3 源碼分析
前面提到過,Spring創建bean過程主要是:

 

 

源碼分析過程中,主要要注意的方法如下:
注:主要是分析類第一次被Spring容器創建的流程

(1) org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class, java.lang.Object[])
(2) org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
(3) org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory)
(4) org.springframework.beans.factory.support.AbstractBeanFactory#createBean
(5) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
(6) org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
(7) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
(8) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)
(9) org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
1
2
3
4
5
6
7
8
9
下面一起探討下Spring容器中getBean()方法源碼的流程,看其是如何解決循環依賴問題的。

前面的源碼流程已經貼出,跟着一步步看過來即可,

// Create bean instance.
if (mbd.isSingleton()) {
// (1) 首先從一級緩存中獲取實例,如果沒有,則從ObjectFactory中獲取
sharedInstance = getSingleton(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
try {
// (2) ObjectFactory中創建bean實例的方法
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ObjectFactory中創建bean實例的方法中的核心方法doCreateBean,也是最重要的一部分,如下所示:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper) this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 創建Bean對象,並且將對象包裹在BeanWrapper 中
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//注意:此處是原始對象,這點非常的重要
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
mbd.postProcessed = true;
}
}

// earlySingletonExposure 用於表示是否”提前暴露“原始對象的引用,用於解決循環依賴。
// 對於單例Bean,該變量一般為 true 但你也可以通過屬性allowCircularReferences = false來關閉循環引用
// isSingletonCurrentlyInCreation(beanName) 表示當前bean必須在創建中才行
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//注釋1
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}

//exposedObject 是最終返回的對象
Object exposedObject = bean;
try {
// 填充屬性,解決@Autowired依賴~
populateBean(beanName, mbd, instanceWrapper);
// 執行初始化回調方法們
// 注釋2
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);
}
}

// earlySingletonExposure:如果你的bean允許被早期暴露出去 也就是說可以被循環引用 那這里就會進行檢查
if (earlySingletonExposure) {
//注意,注意:第二參數為false 表示不會再去三級緩存里查了
//注釋3
Object earlySingletonReference = getSingleton(beanName, false);
//注釋4
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// (3) 注釋5
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
for (int i = 0; i < dependentBeans.length; i++) {
String dependentBean = dependentBeans[i];
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

// Register bean as disposable.
registerDisposableBeanIfNecessary(beanName, bean, mbd);

return exposedObject;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
注釋說明:

注釋1:

上面講過調用此方法放進一個ObjectFactory,二級緩存會對應刪除的
getEarlyBeanReference的作用:調用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()這個方法 否則啥都不做;
也就是給調用者個機會,自己去實現暴露這個bean的應用的邏輯;
比如在getEarlyBeanReference()里可以實現AOP的邏輯,參考自動代理創建器AbstractAutoProxyCreator 實現了這個方法來創建代理對象;
若不需要執行AOP的邏輯,直接返回Bean;
注釋2

initializeBean方法會執行BeanPostProcessor接口的兩個方法,在這過程中可能會將原始的bean對象給手動代理替換掉(注意不是Spring容器管理的自動代理,正常的AOP都是受Spring容器管理的,如果手動使用ProxyFactory為bean創建代理,那么原始的bean就會被手動創建的代理對象給替換掉,且該手動創建的代理對象不受Spring容器自動管理)
注釋3

此處非常巧妙的一點,因為上面各式各樣的實例化、初始化的后置處理器都執行了, 那么此處得到的earlySingletonReference的引用只有兩種結果,一種是null,代表着這個bean不存在被其它bean引用的循環依賴情況,直接返回原始對象即可,一種是該bean的二級緩存,代表着該bean被其它bean引用的循環依賴情況。
對象的循環依賴和代理對象的循環依賴都是靠三級緩存生成最終對象引用,放入二級緩存(三級緩存主要是解決代理對象的循環依賴)。因為前面通過addSingletonFactory方法暴露了三級緩存的ObjectFactory,參考注釋1中getEarlyBeanReference()說明。
注釋4

這個判斷主要是針對代理對象的循環引用場景。
這個意思是如果經過了initializeBean()后,exposedObject還是沒有變,則需要返回拿到的二級緩存earlySingletonReference。因為前面說過,存在循環依賴的場景時,不管普通對象還是代理對象的循環依賴,都是靠三級緩存生成對象並放入二級緩存的,如果exposedObject沒有變,返回的就是exposedObject的代理對象的引用。
注釋5

如果exposedObject變了,則會去檢查這個bean的依賴是否已經創建好,如果沒有則會拋異常。
allowRawInjectionDespiteWrapping這個值默認是false
hasDependentBean:若它有依賴的bean 那就需要繼續校驗了(若沒有依賴的 就放過它~)
上述有些地方可能比較難理解,結合下面示例就清楚了,比如實現BeanPostProcessor接口,手動為bean通過ProxyFactory創建代理類:

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
Class<?> clazz = bean.getClass();

boolean flag = AopUtils.isJdkDynamicProxy(bean) &&
AnnotationUtils.findAnnotation(clazz, Repository.class) != null &&
AnnotationUtils.findAnnotation(clazz, DataSourceAdapter.class) != null &&
Arrays.stream(clazz.getInterfaces()).anyMatch(cls -> cls.getName().startsWith("com.ggj.business"));

if (flag) {
ProxyFactory factory = new ProxyFactory();
factory.setTarget(bean);
factory.addAdvice(new DataSourceAdapterAdvice(applicationContext));
return factory.getProxy();
}
return bean;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
后面判斷exposedObject == bean時,肯定是不相等的,那么就會檢查bean的依賴是否已經創建好,如果沒有創建好會拋異常,這是因為Spring容器覺得既然你自己手動創建了代理對象,代理對象是原始對象的基礎上生成的,那么會檢查原始對象的依賴,如果發現依賴的對象還沒有創建好,就拋異常,避免使用時報錯。

前面我們已經了解,如果當前bean被其它bean依賴,則會生成二級緩存,二級緩存可能是普通對象,也可能是普通對象的代理對象,代理對象主要是通過三級緩存的ObjectFactory的getEarlyBeanReference()方法生成的引用,然后最終返回exposedObject,exposedObject可能是二級緩存,也可能是原始對象,最后通過org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton放進一級緩存,至此整個Spring的循環依賴基本分析完成。

三、總結
(1) 什么是循環依賴,循環依賴的場景有哪些,Spring容器能解決什么樣場景的循環依賴;
(2) 要了解Spring容器bean的創建流程。
(3) 了解一、二、三級緩存的設計原因以及各級緩存之間的關聯;

三級緩存的是ObjectFactory,當前bean被其它bean依賴時,就會去循環allowEarlyReference=true去查找依賴,並放入二級緩存;

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = (ObjectFactory) this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
最后再通過addSingleton方法將已經創建並實例化好的bean放入一級緩存。

protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
1
2
3
4
5
6
7
8
(4) 了解當前beanA依賴beanB的幾種場景:

beanB是受Spring容器管理的普通對象;
beanB是受Spring容器管理的代理對象;
beanB是被BeanPostProcessor接口替換原始bean對象的新對象;
參考:
https://blog.csdn.net/f641385712/article/details/92801300
https://www.cnblogs.com/grey-wolf/p/13034371.html#_label6


淡淡的倔強
關注

————————————————
版權聲明:本文為CSDN博主「淡淡的倔強」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u012834750/article/details/108968103


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM