欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一篇文章徹底理解SpringIOC、DI

 更新時(shí)間:2020年09月18日 09:18:07   作者:lyowish  
這篇文章主要給大家介紹了關(guān)于對(duì)SpringIOC、DI的理解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

你可能會(huì)有如下問(wèn)題:

1、想看Spring源碼,但是不知道應(yīng)當(dāng)如何入手去看,對(duì)整個(gè)Bean的流程沒(méi)有概念,碰到相關(guān)問(wèn)題也沒(méi)有頭緒如何下手

2、看過(guò)幾遍源碼,沒(méi)辦法徹底理解,沒(méi)什么感覺(jué),沒(méi)過(guò)一陣子又忘了

本文將結(jié)合實(shí)際問(wèn)題,由問(wèn)題引出源碼,并在解釋時(shí)會(huì)盡量以圖表的形式讓你一步一步徹底理解Spring Bean的IOC、DI、生命周期、作用域等。

先看一個(gè)循環(huán)依賴問(wèn)題

現(xiàn)象

循環(huán)依賴其實(shí)就是循環(huán)引用,也就是兩個(gè)或則兩個(gè)以上的bean互相持有對(duì)方,最終形成閉環(huán)。比如A依賴于B,B依賴于C,C又依賴于A。如下圖:

如何理解“依賴”呢,在Spring中有:

  • 構(gòu)造器循環(huán)依賴
  • field屬性注入循環(huán)依賴

直接上代碼:

1.構(gòu)造器循環(huán)依賴

@Service
public class A { 
 public A(B b) { }
}
@Service
public class B { 
 public B(C c) { 
 }
}
@Service
public class C { 
 public C(A a) { }
}

結(jié)果:項(xiàng)目啟動(dòng)失敗,發(fā)現(xiàn)了一個(gè)cycle。

 2.field屬性注入循環(huán)依賴

@Service
public class A1 { 
 @Autowired 
 private B1 b1;
}
@Service
public class B1 { 
 @Autowired 
 public C1 c1;
}
@Service
public class C1 { 
 @Autowired public A1 a1;
}

結(jié)果:項(xiàng)目啟動(dòng)成功

3.field屬性注入循環(huán)依賴(prototype)

@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;
}

結(jié)果:項(xiàng)目啟動(dòng)失敗,發(fā)現(xiàn)了一個(gè)cycle。

現(xiàn)象總結(jié):同樣對(duì)于循環(huán)依賴的場(chǎng)景,構(gòu)造器注入和prototype類型的屬性注入都會(huì)初始化Bean失敗。因?yàn)锧Service默認(rèn)是單例的,所以單例的屬性注入是可以成功的。

更多關(guān)于 Spring 相關(guān)的文章,我已經(jīng)整理成了 PDF ,關(guān)注微信公眾號(hào):Java后端,回復(fù) 666 獲取。

分析原因

分析原因也就是在發(fā)現(xiàn)SpringIOC的過(guò)程,如果對(duì)源碼不感興趣可以關(guān)注每段源碼分析之后的總結(jié)和循環(huán)依賴問(wèn)題的分析即可。 

SpringBean的加載流程(源碼分析)

簡(jiǎn)單一段代碼作為入口

ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
ac.getBean(XXX.class);

ClassPathXmlApplicationContext是一個(gè)加載XML配置文件的類,與之相對(duì)的還有AnnotationConfigWebApplicationContext,這兩個(gè)類大差不差的,只是ClassPathXmlApplicationContext的Resource是XML文件而AnnotationConfigWebApplicationContext是Scan注解獲得的。

 看到第二行就已經(jīng)可以直接獲取bean的實(shí)例了,所以第一行構(gòu)造方法時(shí),就已經(jīng)完成了對(duì)所有bean的加載。

ClassPathXmlApplicationContext舉例,他里面儲(chǔ)存的東西如下:

對(duì)象名 類 型 作 用 歸屬類
configResources Resource[] 配置文件資源對(duì)象數(shù)組 ClassPathXmlApplicationContext
configLocations String[] 配置文件字符串?dāng)?shù)組,存儲(chǔ)配置文件路徑 AbstractRefreshableConfigApplicationContext
beanFactory DefaultListableBeanFactory 上下文使用的Bean工廠 AbstractRefreshableApplicationContext
beanFactoryMonitor Object Bean工廠使用的同步監(jiān)視器 AbstractRefreshableApplicationContext
id String 上下文使用的唯一Id,標(biāo)識(shí)此ApplicationContext AbstractApplicationContext
parent ApplicationContext 父級(jí)ApplicationContext AbstractApplicationContext
beanFactoryPostProcessors List<BeanFactoryPostProcessor> 存儲(chǔ)BeanFactoryPostProcessor接口,Spring提供的一個(gè)擴(kuò)展點(diǎn) AbstractApplicationContext
startupShutdownMonitor Object refresh方法和destory方法公用的一個(gè)監(jiān)視器,避免兩個(gè)方法同時(shí)執(zhí)行 AbstractApplicationContext
shutdownHook Thread Spring提供的一個(gè)鉤子,JVM停止執(zhí)行時(shí)會(huì)運(yùn)行Thread里面的方法 AbstractApplicationContext
resourcePatternResolver ResourcePatternResolver 上下文使用的資源格式解析器 AbstractApplicationContext
lifecycleProcessor LifecycleProcessor 用于管理Bean生命周期的生命周期處理器接口 AbstractApplicationContext
messageSource MessageSource 用于實(shí)現(xiàn)國(guó)際化的一個(gè)接口 AbstractApplicationContext
applicationEventMulticaster ApplicationEventMulticaster Spring提供的事件管理機(jī)制中的事件多播器接口 AbstractApplicationContext
applicationListeners Set<ApplicationListener> Spring提供的事件管理機(jī)制中的應(yīng)用監(jiān)聽(tīng)器 AbstractApplicationContext

構(gòu)造方法如下:

接下來(lái)大概看看refresh方法:

子方法先不看,先看看refresh方法的結(jié)構(gòu),其實(shí)就有幾點(diǎn)值得學(xué)習(xí):

1、方法為什么加鎖?是為了避免多線程的場(chǎng)景下同時(shí)刷新Spring上下文

2、雖然整個(gè)方法是加鎖的,但是卻用了Synchronized關(guān)鍵字的對(duì)象鎖startUpShutdownMonitor,這樣做有兩個(gè)好處:

(1)關(guān)閉資源的時(shí)候會(huì)調(diào)用close()方法,close()方法也使用了同樣的對(duì)象鎖,而關(guān)閉資源的close和refresh的兩個(gè)沖突的方法,這樣可以避免沖突

(2)此處對(duì)象鎖相對(duì)于整個(gè)方法加鎖的話,同步的范圍更小了,鎖的粒度更小,效率更高

3、這個(gè)方法refresh定義了整個(gè)Spring IOC的流程,每一個(gè)方法名字都清晰易懂,可維護(hù)性、可讀性很強(qiáng)

總結(jié):看源碼需要找準(zhǔn)入口,看的時(shí)候多思考,學(xué)習(xí)Spring的巧妙的設(shè)計(jì)。ApplicationContext的構(gòu)造方法中最關(guān)鍵是方法是refresh,其中有一些比價(jià)好的設(shè)計(jì)。

obtainFreshBeanFactory方法

這個(gè)方法作用是獲取刷新Spring上下文的Bean工廠:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { 
 this.refreshBeanFactory(); 
 return this.getBeanFactory();
}
protected final void refreshBeanFactory() throws BeansException { 
 if (this.hasBeanFactory()) { 
 this.destroyBeans(); 
 this.closeBeanFactory(); 
 } 
 try { 
 DefaultListableBeanFactory beanFactory = this.createBeanFactory(); 
 beanFactory.setSerializationId(this.getId()); 
 this.customizeBeanFactory(beanFactory); 
 this.loadBeanDefinitions(beanFactory); 
 synchronized(this.beanFactoryMonitor) { 
  this.beanFactory = beanFactory; } 
 } catch (IOException var5) { 
  throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5); 
 }
}

這斷代碼的核心是DefaultListableBeanFactory,核心類我們?cè)僬硪幌?,以圖表格式:

下面有三個(gè)加粗的Map,這些個(gè)Map是解決問(wèn)題的關(guān)鍵。。。我們之后詳細(xì)分析

對(duì)象名 類 型 作 用 歸屬類
aliasMap Map<String, String> 存儲(chǔ)Bean名稱->Bean別名映射關(guān)系 SimpleAliasRegistry
singletonObjects Map<String, Object> 存儲(chǔ)單例Bean名稱->單例Bean實(shí)現(xiàn)映射關(guān)系 DefaultSingletonBeanRegistry
singletonFactories Map<String, ObjectFactory> 存儲(chǔ)Bean名稱->ObjectFactory實(shí)現(xiàn)映射關(guān)系 DefaultSingletonBeanRegistry
earlySingletonObjects Map<String, Object> 存儲(chǔ)Bean名稱->預(yù)加載Bean實(shí)現(xiàn)映射關(guān)系 DefaultSingletonBeanRegistry
registeredSingletons Set<String> 存儲(chǔ)注冊(cè)過(guò)的Bean名 DefaultSingletonBeanRegistry
singletonsCurrentlyInCreation Set<String> 存儲(chǔ)當(dāng)前正在創(chuàng)建的Bean名 DefaultSingletonBeanRegistry
disposableBeans Map<String, Object>

存儲(chǔ)Bean名稱->Disposable接口實(shí)現(xiàn)Bean實(shí)現(xiàn)映射關(guān)系

DefaultSingletonBeanRegistry
factoryBeanObjectCache Map<String, Object> 存儲(chǔ)Bean名稱->FactoryBean接口Bean實(shí)現(xiàn)映射關(guān)系 FactoryBeanRegistrySupport
propertyEditorRegistrars Set<PropertyEditorRegistrar> 存儲(chǔ)PropertyEditorRegistrar接口實(shí)現(xiàn)集合 AbstractBeanFactory
embeddedValueResolvers List<StringValueResolver> 存儲(chǔ)StringValueResolver(字符串解析器)接口實(shí)現(xiàn)列表 AbstractBeanFactory
beanPostProcessors List<BeanPostProcessor> 存儲(chǔ) BeanPostProcessor接口實(shí)現(xiàn)列表 AbstractBeanFactory
mergedBeanDefinitions Map<String, RootBeanDefinition> 存儲(chǔ)Bean名稱->合并過(guò)的根Bean定義映射關(guān)系 AbstractBeanFactory
alreadyCreated Set<String> 存儲(chǔ)至少被創(chuàng)建過(guò)一次的Bean名集合 AbstractBeanFactory
ignoredDependencyInterfaces Set<Class> 存儲(chǔ)不自動(dòng)裝配的接口Class對(duì)象集合 AbstractAutowireCapableBeanFactory
resolvableDependencies Map<Class, Object> 存儲(chǔ)修正過(guò)的依賴映射關(guān)系 DefaultListableBeanFactory
beanDefinitionMap Map<String, BeanDefinition> 存儲(chǔ)Bean名稱-->Bean定義映射關(guān)系 DefaultListableBeanFactory
beanDefinitionNames List<String> 存儲(chǔ)Bean定義名稱列表 DefaultListableBeanFactory  

BeanDefinition在IOC容器中的注冊(cè)

接下來(lái)簡(jiǎn)要分析一下loadBeanDefinitions。

對(duì)于這個(gè)BeanDefinition,我是這么理解的: 它是SpringIOC過(guò)程中間的一個(gè)產(chǎn)物,可以看成是對(duì)Bean定義的抽象,里面封裝的數(shù)據(jù)都是與Bean定義相關(guān)的,封裝了一些基本的bean的Property、initi-method、destroy-method等。

這里的主要方法是loadBeanDefinitions,這里不詳細(xì)展開(kāi)說(shuō),它主要做了幾件事:

1、初始化了BeanDefinitionReader

2、通過(guò)BeanDefinitionReader獲取Resource,也就是xml配置文件的位置,并且把文件轉(zhuǎn)換成一個(gè)叫Document的對(duì)象

3、接下來(lái)需要將Document對(duì)象轉(zhuǎn)化成容器內(nèi)部的數(shù)據(jù)結(jié)構(gòu)(也就是BeanDefinition),也即是將Bean定義的List、Map、Set等各種元素進(jìn)行解析,轉(zhuǎn)換成Managed類(Spring對(duì)BeanDefinition數(shù)據(jù)的封裝)放在BeanDefinition中;這個(gè)方法是RegisterBeanDefinition(),也就是解析的過(guò)程。

4、解析完成后,會(huì)把解析的結(jié)果放到BeanDefinition對(duì)象中并設(shè)置到一個(gè)Map中

以上這個(gè)過(guò)程就是BeanDefinition在IOC容器中的注冊(cè)。

再回到Refresh方法,總結(jié)每一步如下圖:

總結(jié):這一部分步驟主要是Spring如何加載Xml文件或者注解,并把它解析成BeanDefinition。

Spring創(chuàng)建Bean的過(guò)程

先回到之前的refresh方法(也就是在構(gòu)造ApplicationContext時(shí)的方法),我們跳過(guò)不重要的部分:

我們直接看finishBeanFactoryInitialization里面的preInstantiateSingletons方法,顧名思義初始化所有的單例bean,截取部分如下:

現(xiàn)在來(lái)看核心的getBean方法,對(duì)于所有獲取Bean對(duì)象是實(shí)例,都是用這個(gè)getBean方法,這個(gè)方法最終調(diào)用的是doGetBean方法,這個(gè)方法就是所謂的DI(依賴注入)發(fā)生的地方。

程序=數(shù)據(jù)+算法,之前的BeanDefinition就是“數(shù)據(jù)”,依賴注入也就是在BeanDefinition準(zhǔn)備好情況下進(jìn)行進(jìn)行的,這個(gè)過(guò)程不簡(jiǎn)單,因?yàn)镾pring提供了很多參數(shù)配置,每一個(gè)參數(shù)都代表了IOC容器的特性,這些特性的實(shí)現(xiàn)需要在Bean的生命周期中完成。

代碼比較多,就不貼了,大家可以自行查看AbstractBeanFactory里面的doGetBean方法,這里直接上圖,這個(gè)圖就是依賴注入的整個(gè)過(guò)程:

總結(jié):Spring創(chuàng)建好了BeanDefinition之后呢,會(huì)開(kāi)始實(shí)例化Bean,并且對(duì)Bean的依賴屬性進(jìn)行填充。實(shí)例化時(shí)底層使用了CGLIB或Java反射技術(shù)。上圖中instantiateBean核PupulateBean方法很重要!

循環(huán)依賴問(wèn)題分析

我們先總結(jié)一下之前的結(jié)論:

1、構(gòu)造器注入和prototype類型的field注入發(fā)生循環(huán)依賴時(shí)都無(wú)法初始化

2、field注入單例的bean時(shí),盡管有循環(huán)依賴,但bean仍然可以被成功初始化

針對(duì)這幾個(gè)結(jié)論,提出問(wèn)題

單例的設(shè)值注入bean是如何解決循環(huán)依賴問(wèn)題呢?如果A中注入了B,那么他們初始化的順序是什么樣子的?

為什么prototype類型的和構(gòu)造器類型的Spring無(wú)法解決循環(huán)依賴呢?

之前在DefaultListableBeanFactory類中,列出了一個(gè)表格;現(xiàn)在我把關(guān)鍵的精華屬性列出來(lái):

一級(jí)緩存:
/** 保存所有的singletonBean的實(shí)例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

二級(jí)緩存:
/** 保存所有早期創(chuàng)建的Bean對(duì)象,這個(gè)Bean還沒(méi)有完成依賴注入 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
三級(jí)緩存:
/** singletonBean的生產(chǎn)工廠*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
/** 保存所有已經(jīng)完成初始化的Bean的名字(name) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
 
/** 標(biāo)識(shí)指定name的Bean對(duì)象是否處于創(chuàng)建狀態(tài) 這個(gè)狀態(tài)非常重要 */
private final Set<String> singletonsCurrentlyInCreation =
	Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));

前面三個(gè)Map,我們稱為單例初始化的三級(jí)緩存,理解這個(gè)問(wèn)題,我們目前只需關(guān)注“三級(jí)”,也就是singletonFactories

分析:

對(duì)于問(wèn)題1,單例的設(shè)值注入,如果A中注入了B,B應(yīng)該是A中的一個(gè)屬性,那么猜想應(yīng)該是A已經(jīng)被instantiate(實(shí)例化)之后,在populateBean(填充A中的屬性)時(shí),對(duì)B進(jìn)行初始化。

對(duì)于問(wèn)題2,instantiate(實(shí)例化)其實(shí)就是理解成new一個(gè)對(duì)象的過(guò)程,而new的時(shí)候肯定要執(zhí)行構(gòu)造方法,所以猜想對(duì)于應(yīng)該是A在instantiate(實(shí)例化)時(shí),進(jìn)行B的初始化。

有了分析和猜想之后呢,圍繞關(guān)鍵的屬性,根據(jù)從上圖的doGetBean方法開(kāi)始到populateBean所有的代碼,我整理了如下圖:

上圖是整個(gè)過(guò)程中關(guān)鍵的代碼路徑,感興趣的可以自己debug幾回,最關(guān)鍵的解決循環(huán)依賴的是如上的兩個(gè)標(biāo)紅的方法,第一個(gè)方法getSingleton會(huì)從singletonFactories里面拿Singleton,而addSingletonFactory會(huì)把Singleton放入singletonFactories。

對(duì)于問(wèn)題1:?jiǎn)卫脑O(shè)值注入bean是如何解決循環(huán)依賴問(wèn)題呢?如果A中注入了B,那么他們初始化的順序是什么樣子的?

假設(shè)循環(huán)注入是A-B-A:A依賴B(A中autowire了B),B又依賴A(B中又autowire了A):

本質(zhì)就是三級(jí)緩存發(fā)揮作用,解決了循環(huán)。

對(duì)于當(dāng)時(shí)問(wèn)題2,instantiate(實(shí)例化)其實(shí)就是理解成new一個(gè)對(duì)象的過(guò)程,而new的時(shí)候肯定要執(zhí)行構(gòu)造方法,所以猜想對(duì)于應(yīng)該是A在instantiate(實(shí)例化)時(shí),進(jìn)行B的初始化。

答案也很簡(jiǎn)單,因?yàn)锳中構(gòu)造器注入了B,那么A在關(guān)鍵的方法addSingletonFactory()之前就去初始化了B,導(dǎo)致三級(jí)緩存中根本沒(méi)有A,所以會(huì)發(fā)生死循環(huán),Spring發(fā)現(xiàn)之后就拋出異常了。至于Spring是如何發(fā)現(xiàn)異常的呢,本質(zhì)上是根據(jù)Bean的狀態(tài)給Bean進(jìn)行mark,如果遞歸調(diào)用時(shí)發(fā)現(xiàn)bean當(dāng)時(shí)正在創(chuàng)建中,那么久拋出循環(huán)依賴的異常即可。

那么prototype的Bean是如何初始化的呢?

prototypeBean有一個(gè)關(guān)鍵的屬性:

/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
	new NamedThreadLocal<Object>("Prototype beans currently in creation");

保存著正在創(chuàng)建的prototype的beanName,在流程上并沒(méi)有暴露任何factory之類的緩存。并且在beforePrototypeCreation(String beanName)方法時(shí),把每個(gè)正在創(chuàng)建的prototype的BeanName放入一個(gè)set中:

protected void beforePrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal == null) {
			this.prototypesCurrentlyInCreation.set(beanName);
		}
		else if (curVal instanceof String) {
			Set<String> beanNameSet = new HashSet<String>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
		}
		else {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.add(beanName);
		}
}

并且會(huì)循環(huán)依賴時(shí)檢查beanName是否處于創(chuàng)建狀態(tài),如果是就拋出異常:

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
 Object curVal = this.prototypesCurrentlyInCreation.get();
 return (curVal != null &&
 (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

從流程上就可以查看,無(wú)論是構(gòu)造注入還是設(shè)值注入,第二次進(jìn)入同一個(gè)Bean的getBean方法是,一定會(huì)在校驗(yàn)部分拋出異常,因此不能完成注入,也就不能實(shí)現(xiàn)循環(huán)引用。

總結(jié):Spring在InstantiateBean時(shí)執(zhí)行構(gòu)造器方法,構(gòu)造出實(shí)例,如果是單例的話,會(huì)將它放入一個(gè)singletonBeanFactory的緩存中,再進(jìn)行populateBean方法,設(shè)置屬性。通過(guò)一個(gè)singletonBeanFactory的緩存解決了循環(huán)依賴的問(wèn)題。

再解決一個(gè)問(wèn)題

現(xiàn)在大家已經(jīng)對(duì)Spring整個(gè)流程有點(diǎn)感覺(jué)了,我們?cè)賮?lái)解決一個(gè)簡(jiǎn)單的常見(jiàn)的問(wèn)題:

考慮一下如下的singleton代碼:

 @Service
 public class SingletonBean{

  @Autowired 
  private PrototypeBean prototypeBean;

  public void doSomething(){
   System.out.println(prototypeBean.toString());  }

 }
  @Component 
  @Scope(value="prototype")
  public class PrototypeBean{
  }

一個(gè)Singleton的Bean中Autowired了一個(gè)prototype的Bean,那么問(wèn)題來(lái)了,每次調(diào)用SingletonBean.doSomething()時(shí)打印的對(duì)象是不是同一個(gè)呢?

有了之前的知識(shí)儲(chǔ)備,我們簡(jiǎn)單分析一下:因?yàn)镾ingleton是單例的,所以在項(xiàng)目啟動(dòng)時(shí)就會(huì)初始化,prototypeBean本質(zhì)上只是它的一個(gè)Property,那么ApplicationContex中只存在一個(gè)SingletonBean和一個(gè)初始化SingletonBean時(shí)創(chuàng)建的一個(gè)prototype類型的PrototypeBean。

那么每次調(diào)用SingletonBean.doSomething()時(shí),Spring會(huì)從ApplicationContex中獲取SingletonBean,每次獲取的SingletonBean是同一個(gè),所以即便PrototypeBean是prototype的,但PrototypeBean仍然是同一個(gè)。每次打印出來(lái)的內(nèi)存地址肯定是同一個(gè)。

 那這個(gè)問(wèn)題如何解決呢?

解決辦法也很簡(jiǎn)單,這種情況我們不能通過(guò)注入的方式注入一個(gè)prototypeBean,只能在程序運(yùn)行時(shí)手動(dòng)調(diào)用getBean("prototypeBean")方法,我寫了一個(gè)簡(jiǎn)單的工具類:

@Service
public class SpringBeanUtils implements ApplicationContextAware { 
 private static ApplicationContext appContext; 
 @Override 
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 
  SpringBeanUtils.appContext=applicationContext; 
 } 
 public static ApplicationContext getAppContext() { 
  return appContext; 
 } 
 public static Object getBean(String beanName) { 
  checkApplicationContext(); 
  return appContext.getBean(beanName); 
 } 
 private static void checkApplicationContext() { 
  if (null == appContext) {  
   throw new IllegalStateException("applicaitonContext未注入"); 
   } 
 } 
 @SuppressWarnings("unchecked") 
 public static <T> T getBean(Class<T> clazz) { 
  checkApplicationContext(); 
  Map<?, ?> map = appContext.getBeansOfType(clazz); 
  return map.isEmpty() ? null : (T) map.values().iterator().next(); 
 }
 }

對(duì)于這個(gè)ApplicationContextAware接口:

在某些特殊的情況下,Bean需要實(shí)現(xiàn)某個(gè)功能,但該功能必須借助于Spring容器才能實(shí)現(xiàn),此時(shí)就必須讓該Bean先獲取Spring容器,然后借助于Spring容器實(shí)現(xiàn)該功能。為了讓Bean獲取它所在的Spring容器,可以讓該Bean實(shí)現(xiàn)ApplicationContextAware接口。

感興趣的讀者自己可以試試。

總結(jié):

回到循環(huán)依賴的問(wèn)題,有的人可能會(huì)問(wèn)singletonBeanFactory只是一個(gè)三級(jí)緩存,那么一級(jí)緩存和二級(jí)緩存有什么用呢?

其實(shí)大家只要理解整個(gè)流程就可以切入了,Spring在初始化Singleton的時(shí)候大致可以分幾步,初始化——設(shè)值——銷毀,循環(huán)依賴的場(chǎng)景下只有A——B——A這樣的順序,但在并發(fā)的場(chǎng)景下,每一步在執(zhí)行時(shí),都有可能調(diào)用getBean方法,而單例的Bean需要保證只有一個(gè)instance,那么Spring就是通過(guò)這些個(gè)緩存外加對(duì)象鎖去解決這類問(wèn)題,同時(shí)也可以省去不必要的重復(fù)操作。Spring的鎖的粒度選取也是很吊的,這里暫時(shí)不深入研究了。

解決此類問(wèn)題的關(guān)鍵是要對(duì)SpringIOC和DI的整個(gè)流程做到心中有數(shù),看源碼一般情況下不要求每一行代碼都了解透徹,但是對(duì)于整個(gè)的流程和每個(gè)流程中在做什么事需要了然,這樣實(shí)際遇到問(wèn)題時(shí)才可以很快的切入進(jìn)行分析解決。

希望這篇文章可以幫助你對(duì)Spring的IOC和DI的流程有一個(gè)更深刻的認(rèn)識(shí)!

到此這篇關(guān)于理解SpringIOC、DI的文章就介紹到這了,更多相關(guān)理解SpringIOC、DI內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java IO流常用字節(jié)字符流原理解析

    Java IO流常用字節(jié)字符流原理解析

    這篇文章主要介紹了Java IO流常用字節(jié)字符流原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • oracle數(shù)據(jù)庫(kù)導(dǎo)入TXT文件方法介紹

    oracle數(shù)據(jù)庫(kù)導(dǎo)入TXT文件方法介紹

    這篇文章主要介紹了oracle數(shù)據(jù)庫(kù)導(dǎo)入TXT文件方法介紹,文中向大家展示了具體代碼示例,需要的朋友可以參考下。
    2017-09-09
  • Java文件操作之IO流 File類的使用詳解

    Java文件操作之IO流 File類的使用詳解

    在java中提供有對(duì)于文件操作系統(tǒng)的支持,這個(gè)支持在java.io.File類中進(jìn)行了定義,也就是說(shuō)在整個(gè)java.io包中File類是唯一一個(gè)與文件本身操作有關(guān)的類(創(chuàng)建,刪除,重命名)有關(guān)的類,而如果想要進(jìn)行File類的操作,我們需要提供有完整的路徑支持,而后可以調(diào)用相應(yīng)的方法進(jìn)行處理
    2021-09-09
  • Java程序開(kāi)發(fā)環(huán)境配置圖文教程

    Java程序開(kāi)發(fā)環(huán)境配置圖文教程

    這篇文章主要為大家詳細(xì)介紹了Java程序開(kāi)發(fā)環(huán)境配置圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Java fastjson解析json字符串實(shí)現(xiàn)過(guò)程解析

    Java fastjson解析json字符串實(shí)現(xiàn)過(guò)程解析

    這篇文章主要介紹了Java fastjson解析json字符串實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • 完美解決idea創(chuàng)建文件時(shí),文件不分級(jí)展示的情況

    完美解決idea創(chuàng)建文件時(shí),文件不分級(jí)展示的情況

    這篇文章主要介紹了完美解決idea創(chuàng)建文件時(shí),文件不分級(jí)展示的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-02-02
  • Java服務(wù)器宕機(jī)的解決方法論

    Java服務(wù)器宕機(jī)的解決方法論

    這篇文章主要介紹了Java服務(wù)器宕機(jī)的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java使用AES加密和解密的實(shí)例詳解

    Java使用AES加密和解密的實(shí)例詳解

    這篇文章主要介紹了Java使用AES加密和解密的實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-07-07
  • mybatis中foreach嵌套if標(biāo)簽方式

    mybatis中foreach嵌套if標(biāo)簽方式

    這篇文章主要介紹了mybatis中foreach嵌套if標(biāo)簽方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 深入淺析Spring 中的Null-Safety

    深入淺析Spring 中的Null-Safety

    Spring Framework 本身利用了上面這幾個(gè)注釋,但它們也可以運(yùn)用在任何基于Spring的Java 項(xiàng)目中,以聲明空安全api 和 空安全字段。這篇文章主要介紹了Spring 中的Null-Safety相關(guān)知識(shí) ,需要的朋友可以參考下
    2019-06-06

最新評(píng)論