Java Spring處理循環(huán)依賴(lài)詳解
01-前言:什么是循環(huán)依賴(lài)?
首先,我們先明確下依賴(lài)的定義。 如果一個(gè) Bean bar 的屬性,引用了容器中的另外一個(gè) Bean foo,那么稱(chēng) foo 為 bar 的依賴(lài),或稱(chēng) bar 依賴(lài) foo。 如果用代碼表示,可以表示為:
@Component("foo")
public Class Foo {
@Autowired
private Bar bar; // 稱(chēng) foo 依賴(lài) bar
}
@Component("bar")
public Class Bar {
@Autowired
private Baz baz; // bar 依賴(lài) baz
}其次,循環(huán)引用的概念是指多個(gè) Bean 之間的依賴(lài)關(guān)系形成了環(huán)。 接著上面的例子,如果 baz 依賴(lài) foo 或 bar 都將形成循環(huán)依賴(lài)。
@Component("baz")
public class Baz {
@Autowired
private Foo foo; // 形成了循環(huán)依賴(lài),baz -> (依賴(lài)) foo -> bar -> baz ...
}02-Spring 如何處理循環(huán)依賴(lài)?
在之前的文章中,我跟大家一塊學(xué)習(xí)了 Spring 創(chuàng)建 Bean 過(guò)程的源碼。 我們知道:在 createBeanInstance 階段,需要解決構(gòu)造器、工廠(chǎng)方法參數(shù)的依賴(lài); 在 populateBean 階段,需要解決類(lèi)屬性中對(duì)其他 Bean 的依賴(lài)。 這其實(shí)對(duì)應(yīng)了 Spring 中支持的兩種依賴(lài)注入方式,基于構(gòu)造器的依賴(lài)注入和基于 setter 方法的依賴(lài)注入,分別對(duì)應(yīng)前面的兩種情況。
接下來(lái),我會(huì)通過(guò)上節(jié)介紹的示例,來(lái)分情況討論產(chǎn)生循環(huán)依賴(lài)的場(chǎng)景。 為了使討論過(guò)程更清楚、更簡(jiǎn)潔,我會(huì)讓 foo 依賴(lài) bar,而 bar 依賴(lài) foo。 在接下來(lái)的描述中,我假設(shè) Spring 會(huì)先創(chuàng)建 Bar 的對(duì)象,再創(chuàng)建 Foo 的對(duì)象。 針對(duì)不同的依賴(lài)情況,可以分為四種場(chǎng)景:
- 第一種場(chǎng)景,Bar 在構(gòu)造器參數(shù)中依賴(lài) Foo,F(xiàn)oo 在構(gòu)造器參數(shù)中依賴(lài) Bar。這種場(chǎng)景下,依賴(lài)的注入發(fā)生在 Bar 和 Foo 的實(shí)例化階段。
- 第二種場(chǎng)景,Bar 在構(gòu)造器參數(shù)中依賴(lài) Foo,F(xiàn)oo 通過(guò) setter 函數(shù)依賴(lài) Bar。這種場(chǎng)景下,Bar 中注入 Foo 發(fā)生在實(shí)例化階段,F(xiàn)oo 中注入 Bar 發(fā)生在屬性填充階段。
- 第三種場(chǎng)景,Bar 通過(guò) setter 函數(shù)依賴(lài) Foo,F(xiàn)oo 在構(gòu)造器參數(shù)中依賴(lài) Bar。這種場(chǎng)景下,Bar 中輸入 Foo 發(fā)生在屬性填充階段,而 Foo 中注入 Bar 發(fā)生在實(shí)例化階段。
- 第四種場(chǎng)景,Bar 通過(guò) setter 函數(shù)依賴(lài) Foo,F(xiàn)oo 通過(guò) setter 函數(shù)依賴(lài) Bar。 這種場(chǎng)景下,依賴(lài)注入均發(fā)生在屬性填充階段。
在具體分析上述四種場(chǎng)景之前,先說(shuō)下結(jié)論: Spring 可以解決場(chǎng)景三、四中出現(xiàn)循環(huán)依賴(lài)的情況,而第一、二種場(chǎng)景,Spring 無(wú)法解決,需要重構(gòu)依賴(lài)或者延遲延遲依賴(lài)注入的時(shí)機(jī)(例如使用 @Lazy 等)。 細(xì)心的讀者可能會(huì)問(wèn)第二種、第三種場(chǎng)景有什么不同呢? 其實(shí)第二、第三種場(chǎng)景本質(zhì)上是同一種情況,唯一的不同是實(shí)例化的先后順序。 結(jié)合這個(gè)信息,可以得出,先創(chuàng)建的類(lèi)以構(gòu)造器參數(shù)方式依賴(lài)其他 Bean,則會(huì)發(fā)生循環(huán)依賴(lài)異常。 反過(guò)來(lái),如果先創(chuàng)建的類(lèi)以 setter 方式依賴(lài)其他 Bean,則不會(huì)發(fā)生循環(huán)依賴(lài)異常。
接下來(lái),我會(huì)詳細(xì)分析每一種場(chǎng)景,并指出拋循環(huán)依賴(lài)異常的時(shí)機(jī)。
首先,所有的單例 Bean 會(huì)在容器啟動(dòng)后被創(chuàng)建 ConfigurableListableBeanFactory#preInstantiateSingletons,即所謂的 “eager registration of singletons” 過(guò)程。
第一種場(chǎng)景,會(huì)先觸發(fā) Bar 類(lèi)的實(shí)例 bar 的創(chuàng)建。在 createBeanInstance 階段,會(huì)通過(guò) ConstructorResolver#autowireConstructor 來(lái)創(chuàng)建實(shí)例。 ConstructorResolver#createArgumentArray 會(huì)解析構(gòu)造器中的參數(shù),并處理對(duì)其他 Bean 依賴(lài)的引用 ConstructorResolver#resolveAutowiredArgument。 處理依賴(lài)的方式就是通過(guò) DefaultListableBeanFactory#resolveDependency 來(lái)查找符合條件的 Bean,最終還是通過(guò) AbstractBeanFactory#getBean 來(lái)從容器中取。 當(dāng)通過(guò) getBean("bar") 來(lái)觸發(fā) Spring 創(chuàng)建 bar 時(shí),在實(shí)例化階段,根據(jù)構(gòu)造器參數(shù)來(lái) getBean("foo") 并觸發(fā) foo 的創(chuàng)建。 在 foo 的實(shí)例化過(guò)程與 bar 的是完全一樣的,最終 getBean("bar")。這是容器中的 bar 還沒(méi)有創(chuàng)建好,所以會(huì)再次觸發(fā)創(chuàng)建過(guò)程。 在真正創(chuàng)建過(guò)程之前,在 DefaultSingletonBeanRegistry#getSingleton 中會(huì)有一次檢查,DefaultSingletonBeanRegistry#beforeSingletonCreation 如果發(fā)現(xiàn)要?jiǎng)?chuàng)建的 bean 正在創(chuàng)建過(guò)程中,則拋出異常。
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?
第二種場(chǎng)景,同樣先構(gòu)建 bar 實(shí)例。與第一種場(chǎng)景不同之處在于 foo 創(chuàng)建時(shí),它的 createBeanInstance 階段能夠執(zhí)行完畢。 原因是 foo 只有一個(gè)無(wú)參構(gòu)造器(即默認(rèn)構(gòu)造器),不需要注入其他依賴(lài)。 foo 的 createBeanInstance 階段執(zhí)行完畢后,會(huì)進(jìn)入 populateBean 階段。 在這個(gè)階段中,AutowiredAnnotationBeanPostProcessor#postProcessProperties 會(huì)處理 setter 函數(shù)依賴(lài)的 Bean。 大致處理過(guò)程為:AutowiredAnnotationBeanPostProcessor 識(shí)別到 foo 中包含需要注入依賴(lài)的 setter 函數(shù),將其映射為 AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement 對(duì)象。 然后調(diào)用 AutowiredMethodElement#inject 方法注入依賴(lài)。 在 inject 方法中,會(huì)調(diào)用 DefaultListableBeanFactory#resolveDependency 來(lái)查找對(duì)應(yīng)的依賴(lài)。 到這里為止,后續(xù)的過(guò)程與第一種場(chǎng)景完全一致了。 從容器中嘗試獲取 bar,發(fā)現(xiàn)不存在,會(huì)出發(fā) bar 的再次創(chuàng)建,最終在 DefaultSingletonBeanRegistry#beforeSingletonCreation 中拋出異常。
第三種場(chǎng)景,同樣先構(gòu)建 bar 實(shí)例。由于它只包含一個(gè)默認(rèn)構(gòu)造器,所以它的 createBeanInstance 階段會(huì)順利完成,然后進(jìn)入 populateBean 階段。 當(dāng)你仔細(xì)回看一下 Spring 創(chuàng)建 Bean 過(guò)程的源碼,你會(huì)發(fā)現(xiàn)下面這段代碼:
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}這段邏輯發(fā)生在 createBeanInstance 之后,尚未進(jìn)入 populateBean 之前。 這里其實(shí)就是 Spring 解決循環(huán)依賴(lài)機(jī)制的核心點(diǎn)之一,這里我暫且不深入介紹,后面會(huì)有詳細(xì)的分析。
繼續(xù)前面的分析。bar 實(shí)例創(chuàng)建進(jìn)入到 populateBean 階段后,會(huì)檢查其自身依賴(lài)情況,然后注入對(duì)應(yīng)的依賴(lài) Bean。 這里的處理邏輯依然是 AutowiredAnnotationBeanPostProcessor#postProcessProperties 來(lái)處理。 當(dāng)嘗試注入 foo 時(shí),會(huì)出發(fā) foo 實(shí)例的創(chuàng)建過(guò)程。foo 通過(guò)構(gòu)造器依賴(lài) bar,因此在其 createBeanInstance 階段,會(huì)通過(guò) ConstructorResolver#autowireConstructor 完成依賴(lài)注入。 此時(shí)通過(guò) getBean("bar") 從容器中嘗試獲取 bar 時(shí),能夠“獲取到”。 注:這里為什么不會(huì)出發(fā) bar 的創(chuàng)建,反而能夠直接得到 bar 對(duì)象呢?上面的獲取到我加了引號(hào),它其實(shí)獲得的并不是一個(gè)完整、可用的 bar。 它獲得的是通過(guò) earlySingletonExposure 提前暴露出的對(duì)象。 這個(gè)過(guò)程在后面介紹三級(jí)緩存時(shí)會(huì)詳細(xì)介紹。
篇幅原因,第四種場(chǎng)景我不在這里繼續(xù)分析,感興趣的讀者可以自己嘗試分析下。 簡(jiǎn)單提示下,它的過(guò)程有點(diǎn)像第三種場(chǎng)景前半段、第二種場(chǎng)景的后半段結(jié)合起來(lái)。
在上述四種場(chǎng)景下,第一種情況,依賴(lài)雙方都是通過(guò)構(gòu)造器依賴(lài)對(duì)方,這種情況下 Spring 是無(wú)法處理的。 而且,我認(rèn)為出現(xiàn)這一情況,屬于是設(shè)計(jì)上的缺陷,應(yīng)當(dāng)通過(guò)重新設(shè)計(jì)依賴(lài)關(guān)系來(lái)解決,例如可以將基于構(gòu)造器的注入修改為基于 setter 的注入,或者通過(guò) @Lazy 將依賴(lài)的初始化延遲到使用時(shí)。 通過(guò) Foo、Bar 類(lèi)來(lái)舉例說(shuō)明。
@Component
public class Foo {
private Bar bar;
@Autowired
public Foo(@Lazy Bar bar) {
this.bar = bar;
}
}
@Component
public class Bar {
private Foo foo;
@Autowired
public Bar(Foo foo) { // 或者將對(duì) foo 的依賴(lài),注解為 @Lazy 表示使用時(shí)才初始化
this.foo = foo;
}
}另一種修改方式就是,將第一種情況,修改為第二種情況,即:
@Component
public class Bar {
private Foo foo;
@Autowired
public void setFoo(Foo foo) {
this.foo = foo;
}
}03-Spring 中解決循環(huán)依賴(lài)的三級(jí)緩存
Spring 中設(shè)計(jì)了一個(gè)三級(jí)緩存用來(lái)解決前面介紹的循環(huán)依賴(lài)問(wèn)題的處理。三級(jí)緩存包括:
- singletonObjects,為一級(jí)緩存,保存了 beanName -> bean instance 的映射關(guān)系。存放的是完全可用的單例 Bean 對(duì)象。
- earlySingletonObjects,為二級(jí)緩存,保存了 beanName -> bean instance 的映射關(guān)系。 在一級(jí)、二級(jí)緩存都沒(méi)有發(fā)現(xiàn)目標(biāo)對(duì)象,但三級(jí)緩存中存在 ObjectFactory 對(duì)象時(shí),調(diào)用 ObjectFactory#getObject 創(chuàng)建實(shí)例,放入二級(jí)緩存,刪除三級(jí)緩存中的 ObjectFactory 對(duì)象。
- singletonFactories,為三級(jí)緩存,保存了 beanName -> ObjectFactory 的映射關(guān)系。 在 doCreateBean 時(shí),會(huì)向這個(gè) map 中添加
beanName: () -> getEarlyBeanReference(beanName, mbd, bean)的映射關(guān)系,value 是一個(gè)函數(shù)式接口 ObjectFactory。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}大概了解了 Spring 中的三級(jí)緩存后,我們?cè)倩剡^(guò)頭來(lái)看一下 AbstractBeanFactory#getBean 過(guò)程。 它的實(shí)際工作是在 AbstractBeanFactory#doGetBean 中完成的。 doGetBean 方法的具體實(shí)現(xiàn)可以簡(jiǎn)化、抽象為:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 緩存中存在
/**
* 如果 beanName 是一個(gè) FactoryBean,則獲取對(duì)應(yīng)的 Bean
* 如果 beanName 是一個(gè)普通的 Bean,則返回這個(gè) Bean 本身
*/
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 緩存中不存在
// 創(chuàng)建對(duì)象
if (mbd.isSingleton()) {
/**
* 這里的 getSingleton 是 getSingleton(beanName) 的重載版本
* 它接受一個(gè) beanName 和 一個(gè) ObjectFactory 作為參數(shù)
* 調(diào)用 ObjectFactory#getObject 產(chǎn)生一個(gè)實(shí)例
* 并通過(guò) addSingleton(beanName, singletonObject); 將實(shí)例添加到 singletonObjects 中
* 這里 createBean 的代碼就是前面提到的 Spring 創(chuàng)建 Bean 實(shí)例的過(guò)程 doCreateBean
*/
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
});
/**
* 同前面分支中的作用一樣
*/
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
/**
* 判斷 Bean 類(lèi)型與 requiredType 類(lèi)型是否一直,一致則直接返回,不一致則需要進(jìn)行轉(zhuǎn)換
*/
return adaptBeanInstance(name, beanInstance, requiredType);
}從上面的源碼可以知道,當(dāng) DefaultSingletonBeanRegistry#getSingleton(beanName) 時(shí),會(huì)先從多級(jí)緩存中取對(duì)象(可能是 bean instance,也可能是對(duì)應(yīng)的 ObjectFactory)。 從多級(jí)緩存中取對(duì)象的源碼如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 判斷第一級(jí)緩存中是否有完全可以可用的 Bean 實(shí)例,若有則返回;
* 若沒(méi)有,則根據(jù)情況判斷
* isSingletonCurrentlyInCreation(beanName) 檢查的是在 `Set<String> singletonsCurrentlyInCreation` 集合中是否包含要獲取的 Bean 實(shí)例
* beanName 只在調(diào)用 beforeSingletonCreation(String beanName) 時(shí)被添加到 singletonsCurrentlyInCreation 集合中
* beforeSingletonCreation 在創(chuàng)建 bean,即 doCreateBean 之前調(diào)用,在創(chuàng)建過(guò)程完成以后,調(diào)用 afterSingletonCreation 從集合中移除 beanName
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
/**
* 執(zhí)行到這個(gè)分支,其實(shí)說(shuō)明 Bean 已經(jīng)在創(chuàng)建過(guò)程中了,只不過(guò)是尚未完全可用(即一級(jí)緩存中沒(méi)有)
* 檢查二級(jí)緩存,是否包含指定的 Bean
*
* 二級(jí)緩存里的內(nèi)容何時(shí)被添加或設(shè)置進(jìn)來(lái)的呢?
* 我們可以檢查下 earlySingletonObjects.put 方法都在哪里調(diào)用。
* 檢查后發(fā)現(xiàn),其實(shí) earlySingletonObjects 就是在當(dāng)前方法中設(shè)置的,我們接著往下看。
*/
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
/**
* 這里的 allowEarlyReference 的意思就是指是否允許在二級(jí)緩存中創(chuàng)建一個(gè)對(duì)象,即是否允許暴露未完全可用的對(duì)象
* 如果 allowEarlyReference 為假,則不會(huì)操作二級(jí)、三級(jí)緩存,而僅檢查一級(jí)緩存中是否有完全可用的 Bean 實(shí)例
* 這也意味著,不允許返回未完全可用狀態(tài)的 Bean
*
* 當(dāng)發(fā)現(xiàn)二級(jí)緩存中沒(méi)有對(duì)象,同時(shí)又允許提前引用(即 allowEarlyReference 值為真)
* 則檢查三級(jí)緩存中是否有對(duì)應(yīng)的 ObjectFactory 對(duì)象,若有,則調(diào)用它的 getObject 方法產(chǎn)生對(duì)象,然后將其放置到二級(jí)緩存中,同時(shí)刪除三級(jí)緩存中的對(duì)象工廠(chǎng)實(shí)例
* 若三級(jí)緩存中也沒(méi)有對(duì)象工廠(chǎng)實(shí)例,則說(shuō)面 bean 還未創(chuàng)建
*/
synchronized (this.singletonObjects) {
/**
* 這里會(huì)進(jìn)行一個(gè) double-check,避免多線(xiàn)程間的線(xiàn)程安全問(wèn)題
*/
// Consistent creation of early reference within full singleton lock
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) {
/**
* 三級(jí)緩存中存在對(duì)象工廠(chǎng)實(shí)例,則通過(guò)它產(chǎn)生一個(gè) Bean 實(shí)例
* 加入到二級(jí)緩存中,同時(shí)刪除三級(jí)緩存中的對(duì)象工廠(chǎng)實(shí)例
*/
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}注:
- isSingletonCurrentlyInCreation(beanName) 意味著什么呢?意味著對(duì)應(yīng)的 Bean 在 doCreateBean 過(guò)程中,可能在 createBeanInstance \ populateBean \ initializeBean 階段中。
- 在前面提到,createBeanInstance 后,Bean 會(huì)被添加到上述多級(jí)緩存中的第三級(jí)緩存中,存入對(duì)象是 beanName -> objectFactory 映射關(guān)系。 當(dāng)其他的 Bean 依賴(lài)當(dāng)前 Bean 時(shí),而且允許引用提前暴露的 Bean(即未完全可用的 Bean),會(huì)檢查第二級(jí)緩存,如果沒(méi)有還會(huì)檢查第三級(jí)緩存,并在得到對(duì)應(yīng) objectFactory 時(shí),獲得對(duì)象并將其從第三級(jí)移動(dòng)到第二級(jí)。
有些讀者看到這里可能會(huì)有個(gè)疑問(wèn),那二級(jí)緩存中的對(duì)象什么時(shí)候刪除呢? 我們?cè)賮?lái)回頭看下 doGetBean 中的代碼片段:
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
});這里的 singleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
/**
* 檢查 Bean 是否在創(chuàng)建過(guò)程中,避免重復(fù)創(chuàng)建,
* 不能解決的循環(huán)依賴(lài)也是在這里拋出異常
*/
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
/**
* 這里調(diào)用的其實(shí)就是 AbstractAutowireCapableBeanFactory#createBean
* 然后會(huì)執(zhí)行 doCreateBean(三個(gè)階段)
*/
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
throw ex;
}
finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
/**
* 這里說(shuō)明一個(gè) bean 創(chuàng)建過(guò)程的三個(gè)階段都執(zhí)行完畢了
*/
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
/**
* 將 Bean 實(shí)例添加到第一級(jí)緩存
* 將第二級(jí)、第三級(jí)緩存中的對(duì)象刪除
*/
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);
}
}到這里為止,相信你對(duì) Spring 創(chuàng)建 Bean 的過(guò)程以及處理循環(huán)依賴(lài)的機(jī)制能夠有個(gè)大致的了解。 如果想了解的更深或跟多細(xì)節(jié),可以自己?jiǎn)尾秸{(diào)試下。 希望今天的內(nèi)容能夠?qū)δ阌兴鶐椭?。如果有?xiě)得不對(duì)的地方,也請(qǐng)大家多批評(píng)指正。
以上就是Java Spring處理循環(huán)依賴(lài)詳解的詳細(xì)內(nèi)容,更多關(guān)于Java Spring循環(huán)依賴(lài)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中的volatile實(shí)現(xiàn)機(jī)制詳細(xì)解析
這篇文章主要介紹了Java中的volatile實(shí)現(xiàn)機(jī)制詳細(xì)解析,本文的主要內(nèi)容就在于要理解volatile的緩存的一致性協(xié)議導(dǎo)致的共享變量可見(jiàn)性,以及volatile在解析成為匯編語(yǔ)言的時(shí)候?qū)ψ兞考渔i兩塊理論內(nèi)容,需要的朋友可以參考下2024-01-01
SpringBoot中的@RestControllerAdvice注解詳解
這篇文章主要介紹了SpringBoot中的@RestControllerAdvice注解詳解,RestControllerAdvice注解用于創(chuàng)建全局異常處理類(lèi),用于捕獲和處理整個(gè)應(yīng)用程序中的異常,需要的朋友可以參考下2024-01-01
設(shè)計(jì)模式系列之組合模式及其在JDK和MyBatis源碼中的運(yùn)用詳解
這篇文章主要介紹了組合模式及其在JDK和MyBatis源碼中的運(yùn)用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
spring學(xué)習(xí)之util:properties的使用
這篇文章主要介紹了spring學(xué)習(xí)之util:properties的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
IDEA中的maven沒(méi)有dependencies解決方案
這篇文章主要介紹了IDEA中的maven沒(méi)有dependencies解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
獲取Java線(xiàn)程轉(zhuǎn)儲(chǔ)的常用方法(推薦)
這篇文章主要介紹了獲取Java線(xiàn)程轉(zhuǎn)儲(chǔ)的常用方法,本文給大家介紹的非常想詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
IDEA?+?Maven環(huán)境下的SSM框架整合及搭建過(guò)程
這篇文章主要介紹了IDEA?+?Maven環(huán)境下的SSM框架整合及搭建過(guò)程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01

