Spring Bean 中的生命周期和獲取方式詳解
一、Spring Bean 的生命周期,如何被管理的
對(duì)于普通的 Java對(duì)象,當(dāng) new的時(shí)候創(chuàng)建對(duì)象,當(dāng)它沒(méi)有任何引用的時(shí)候被垃圾回收機(jī)制回收。而由 Spring IoC容器托管的對(duì)象,它們的生命周期完全由容器控制。Spring 中每個(gè) Bean的生命周期如下:
主要對(duì)幾個(gè)重要的步驟進(jìn)行說(shuō)明:
【1】實(shí)例化 Bean: 對(duì)于 BeanFactory容器,當(dāng)客戶(hù)向容器請(qǐng)求一個(gè)尚未初始化的 bean時(shí),或初始化 bean的時(shí)候需要注入另一個(gè)尚未初始化的依賴(lài)時(shí),容器就會(huì)調(diào)用 createBean進(jìn)行實(shí)例化。對(duì)于 ApplicationContext容器,當(dāng)容器啟動(dòng)結(jié)束后,便實(shí)例化所有的單實(shí)例 bean。容器通過(guò)獲取 BeanDefinition對(duì)象中的信息進(jìn)行實(shí)例化。并且這一步僅僅是簡(jiǎn)單的實(shí)例化,并未進(jìn)行依賴(lài)注入。實(shí)例化對(duì)象被包裝在 BeanWrapper 對(duì)象中,BeanWrapper 提供了設(shè)置對(duì)象屬性的接口,從而避免了使用反射機(jī)制設(shè)置屬性。通過(guò)工廠(chǎng)方法或者執(zhí)行構(gòu)造器解析執(zhí)行即可:創(chuàng)建的對(duì)象是個(gè)空對(duì)象。
【2】設(shè)置對(duì)象屬性(依賴(lài)注入): 實(shí)例化后的對(duì)象被封裝在 BeanWrapper對(duì)象中,并且此時(shí)對(duì)象仍然是一個(gè)原生的狀態(tài),并沒(méi)有進(jìn)行依賴(lài)注入。緊接著獲取所有的屬性信息通過(guò) populateBean(beanName,mbd,bw,pvs),Spring 根據(jù) BeanDefinition 中的信息進(jìn)行依賴(lài)注入。并且通過(guò) BeanWrapper提供的設(shè)置屬性的接口完成依賴(lài)注入。賦值之前獲取所有的 InstantiationAwareBeanPostProcessor 后置處理器的 postProcessAfterInstantiation() 第二次獲取InstantiationAwareBeanPostProcessor 后置處理器;執(zhí)行 postProcessPropertyValues()最后為應(yīng)用 Bean屬性賦值:為屬性利用 setter 方法進(jìn)行賦值 applyPropertyValues(beanName,mbd,bw,pvs)。
【3】bean 初始化: initializeBean(beanName,bean,mbd)。
1)執(zhí)行xxxAware 接口的方法,調(diào)用實(shí)現(xiàn)了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware接口的方法。
2)執(zhí)行后置處理器之前的方法:applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)所有后置處理器的 BeanPostProcessor.postProcessBeforeInitialization()。
3)執(zhí)行初始化方法: InitializingBean 與 init-methodinvoke 當(dāng) BeanPostProcessor的前置處理完成后就會(huì)進(jìn)入本階段。先判斷是否實(shí)現(xiàn)了 InitializingBean接口的實(shí)現(xiàn);執(zhí)行接口規(guī)定的初始化。其次自定義初始化方法。
InitializingBean 接口只有一個(gè)函數(shù):afterPropertiesSet()這一階段也可以在 bean正式構(gòu)造完成前增加我們自定義的邏輯,但它與前置處理不同,由于該函數(shù)并不會(huì)把當(dāng)前 bean對(duì)象傳進(jìn)來(lái),因此在這一步?jīng)]辦法處理對(duì)象本身,只能增加一些額外的邏輯。若要使用它,我們需要讓 bean實(shí)現(xiàn)該接口,并把要增加的邏輯寫(xiě)在該函數(shù)中。然后 Spring會(huì)在前置處理完成后檢測(cè)當(dāng)前 bean是否實(shí)現(xiàn)了該接口,并執(zhí)行 afterPropertiesSet函數(shù)。當(dāng)然,Spring 為了降低對(duì)客戶(hù)代碼的侵入性,給 bean的配置提供了 init-method屬性,該屬性指定了在這一階段需要執(zhí)行的函數(shù)名。Spring 便會(huì)在初始化階段執(zhí)行我們?cè)O(shè)置的函數(shù)。init-method 本質(zhì)上仍然使用了InitializingBean接口。
4)applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);執(zhí)行初始化之后的后置處理器的方法。BeanPostProcessor.postProcessAfterInitialization(result, beanName);
【4】Bean的銷(xiāo)毀: DisposableBean 和 destroy-method:和 init-method 一樣,通過(guò)給 destroy-method 指定函數(shù),就可以在bean 銷(xiāo)毀前執(zhí)行指定的邏輯。
Bean 的管理就是通過(guò) IOC容器中的 BeanDefinition信息進(jìn)行管理的。
二、Spring Bean 的加載和獲取過(guò)程
Bean 的加載過(guò)程,主要是對(duì)配置文件的解析,并注冊(cè) bean 的過(guò)程 。
【1】根據(jù)注解或者 XML中 定義 Bean 的基本信息。例如:spring-core.xml
<bean id="myBean" class="com.taobao.pojo"></bean>
【2】獲取配置文件:這里使用最原始的方式獲取。
Resource resource = new ClassPathResource("spring-core.xml")
【3】 利用 XmlBeanFactory 解析并注冊(cè) bean 定義:已經(jīng)完成將配置文件包裝成了 Spring 定義的資源,并觸發(fā)解析和注冊(cè)。XmlBeanFactory 實(shí)際上是對(duì) DefaultListableBeanFactory(非常核心的類(lèi),它包含了基本 IOC 容器所具有的重要功能,是一個(gè) IOC 容器的基本實(shí)現(xiàn)。然后是調(diào)用了this.reader.loadBeanDefinitions(resource)
,從這里開(kāi)始加載配置文件) 和 XmlBeanDefinitionReader 組合使用方式的封裝,所以這里我們?nèi)匀粚⒗^續(xù)分析基于 XmlBeanFactory 加載 bean 的過(guò)程。
XmlBeanFactory beanFactory = new XmlBeanFactory(resource);
Spring 使用了專(zhuān)門(mén)的資源加載器對(duì)資源進(jìn)行加載,這里的 reader 就是 XmlBeanDefinitionReader
對(duì)象,專(zhuān)門(mén)用來(lái)加載基于 XML 文件配置的 bean。這里的加載過(guò)程為:
①、利用 EncodedResource 二次包裝資源文件;
②、獲取資源輸入流,并構(gòu)造 InputSource 對(duì)象:
// 獲取資源的輸入流 InputStream inputStream = encodedResource.getResource().getInputStream(); // 構(gòu)造InputSource對(duì)象 InputSource inputSource = new InputSource(inputStream); // 真正開(kāi)始從 XML文件中加載 Bean定義 return this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
這里的 this.doLoadBeanDefinitions(inputSource, encodedResource.getResource())
就是真正開(kāi)始加載 XMl 的入口,該方法源碼如下:第一步獲取 org.w3c.dom.Document 對(duì)象,第二步由該對(duì)象解析得到 BeanDefinition 對(duì)象,并注冊(cè)到 IOC 容器中。
protected intdoLoadBeanDefinitions(InputSource inputSource, Resource resource){ try { // 1. 加載xml文件,獲取到對(duì)應(yīng)的Document(包含獲取xml文件的實(shí)體解析器和驗(yàn)證模式) Document doc = this.doLoadDocument(inputSource, resource); // 2. 解析Document對(duì)象,并注冊(cè)bean return this.registerBeanDefinitions(doc, resource); } }
③、獲取 XML 文件的實(shí)體解析器和驗(yàn)證模式:this.doLoadDocument(inputSource, resource)
包含了獲取實(shí)體解析器、驗(yàn)證模式,以及 Document 對(duì)象的邏輯,XML 是半結(jié)構(gòu)化數(shù)據(jù),XML 的驗(yàn)證模式用于保證結(jié)構(gòu)的正確性,常見(jiàn)的驗(yàn)證模式有 DTD 和 XSD 兩種。
④、加載 XML 文件,獲取對(duì)應(yīng)的 Document 對(duì)象和驗(yàn)證模式與解析器,解析器就可以加載 Document 對(duì)象了,這里本質(zhì)上調(diào)用的是 DefaultDocumentLoader
的 loadDocument() 方法,源碼如下:整個(gè)過(guò)程類(lèi)似于我們平常解析 XML 文件的流程。
public DocumentloadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware); DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
⑤、由 Document 對(duì)象解析并注冊(cè) bean:完成了對(duì) XML 文件的到 Document 對(duì)象的解析,我們終于可以解析 Document 對(duì)象,并注冊(cè) bean 了,這一過(guò)程發(fā)生在 this.registerBeanDefinitions(doc, resource)
中,源碼如下:
public intregisterBeanDefinitions(Document doc, Resource resource)throwsBeanDefinitionStoreException{ // 使用DefaultBeanDefinitionDocumentReader構(gòu)造 BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader(); // 記錄之前已經(jīng)注冊(cè)的BeanDefinition個(gè)數(shù) int countBefore = this.getRegistry().getBeanDefinitionCount(); // 加載并注冊(cè)bean documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 返回本次加載的bean的數(shù)量 return getRegistry().getBeanDefinitionCount() - countBefore; }
這里方法的作用是創(chuàng)建對(duì)應(yīng)的 BeanDefinitionDocumentReader,并計(jì)算返回了過(guò)程中新注冊(cè)的 bean 的數(shù)量,而具體的注冊(cè)過(guò)程,則是由 BeanDefinitionDocumentReader 來(lái)完成的,具體的實(shí)現(xiàn)位于子類(lèi) DefaultBeanDefinitionDocumentReader 中:
publicvoidregisterBeanDefinitions(Document doc, XmlReaderContext readerContext){ this.readerContext = readerContext; // 獲取文檔的root結(jié)點(diǎn) Element root = doc.getDocumentElement(); this.doRegisterBeanDefinitions(root); }
還是按照 Spring 命名習(xí)慣,doRegisterBeanDefinitions 才是真正干活的地方,這也是真正開(kāi)始解析配置的核心所在:
protectedvoiddoRegisterBeanDefinitions(Element root){ BeanDefinitionParserDelegate parent = this.delegate; this.delegate = this.createDelegate(getReaderContext(), root, parent); // 處理profile標(biāo)簽(其作用類(lèi)比pom.xml中的profile) String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); // 解析預(yù)處理,留給子類(lèi)實(shí)現(xiàn) this.preProcessXml(root); // 解析并注冊(cè)BeanDefinition this.parseBeanDefinitions(root, this.delegate); // 解析后處理,留給子類(lèi)實(shí)現(xiàn) this.postProcessXml(root); }
方法在解析并注冊(cè) BeanDefinition 前后各設(shè)置一個(gè)模板方法,留給子類(lèi)擴(kuò)展實(shí)現(xiàn),而在this.parseBeanDefinitions(root, this.delegate)
中執(zhí)行解析和注冊(cè)邏輯:方法中判斷當(dāng)前標(biāo)簽是默認(rèn)標(biāo)簽還是自定義標(biāo)簽,并按照不同的策略去解析。
protectedvoidparseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate){ if (delegate.isDefaultNamespace(root)) { // 解析默認(rèn)標(biāo)簽 NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 解析默認(rèn)標(biāo)簽 this.parseDefaultElement(ele, delegate); } else { // 解析自定義標(biāo)簽 delegate.parseCustomElement(ele); } } } } else { // 解析自定義標(biāo)簽 delegate.parseCustomElement(root); } }
到這里我們已經(jīng)完成了靜態(tài)配置到動(dòng)態(tài) BeanDefinition 的解析,這個(gè)時(shí)候 bean 的定義已經(jīng)處于內(nèi)存中。
【4】 從 IOC容器加載獲取 bean:我們可以調(diào)用 beanFactory.getBean("myBean")
方法來(lái)獲取目標(biāo)對(duì)象。
MyBean myBean = (MyBean) beanFactory.getBean("myBean");
到此這篇關(guān)于Spring Bean 的生命周期和獲取方式詳解的文章就介紹到這了,更多相關(guān)Spring Bean 生命周期內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Idea實(shí)現(xiàn)接口的方法上無(wú)法添加@Override注解的解決方案
文章介紹了在IDEA中實(shí)現(xiàn)接口方法時(shí)無(wú)法添加@Override注解的問(wèn)題及其解決方法,主要步驟包括更改項(xiàng)目結(jié)構(gòu)中的Language level到支持該注解的版本,以及在pom.xml文件中指定maven-compiler-plugin的版本以解決自動(dòng)更新后的問(wèn)題2025-02-02Java?如何用二維數(shù)組創(chuàng)建空心菱形
這篇文章主要介紹了Java?如何用二維數(shù)組創(chuàng)建空心菱形,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03使用Java獲取文件樹(shù)的代碼實(shí)現(xiàn)
Java語(yǔ)言提供了豐富的庫(kù)和工具,使得我們可以方便地獲取和操作Java文件的語(yǔ)法樹(shù)(AST, Abstract Syntax Tree),在這篇博客中,我們將探討如何使用Java來(lái)獲取一個(gè)Java文件的語(yǔ)法樹(shù),并展示詳細(xì)的代碼示例和運(yùn)行結(jié)果,需要的朋友可以參考下2024-08-08Java實(shí)現(xiàn)JSON與XML相互轉(zhuǎn)換的簡(jiǎn)明教程
Java實(shí)現(xiàn)復(fù)雜數(shù)據(jù)結(jié)構(gòu)(如嵌套對(duì)象、數(shù)組)在 JSON 與 XML 之間的相互轉(zhuǎn)換,可以使用 Jackson 和 Jackson XML 擴(kuò)展庫(kù)來(lái)完成,Jackson 是一個(gè)流行的 JSON 處理庫(kù),通過(guò) Jackson 的 XML 擴(kuò)展庫(kù),可以實(shí)現(xiàn) JSON 和 XML 之間的轉(zhuǎn)換,需要的朋友可以參考下2024-08-08java操作mysql實(shí)現(xiàn)增刪改查的方法
這篇文章主要介紹了java操作mysql實(shí)現(xiàn)增刪改查的方法,結(jié)合實(shí)例形式分析了java操作mysql數(shù)據(jù)庫(kù)進(jìn)行增刪改查的具體實(shí)現(xiàn)技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-05-05Spring Security角色繼承實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Spring Security角色繼承實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Java獲取任意http網(wǎng)頁(yè)源代碼的方法
這篇文章主要介紹了Java獲取任意http網(wǎng)頁(yè)源代碼的方法,可實(shí)現(xiàn)獲取網(wǎng)頁(yè)代碼以及去除HTML標(biāo)簽的代碼功能,涉及Java正則操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-09-09java金額數(shù)字轉(zhuǎn)中文工具類(lèi)詳解
這篇文章主要為大家詳細(xì)介紹了java金額數(shù)字轉(zhuǎn)中文工具類(lèi)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04jdbc連接數(shù)據(jù)庫(kù)實(shí)例詳解
在本篇內(nèi)容里小編給大家分享了關(guān)于jdbc如何連接數(shù)據(jù)庫(kù)的相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的朋友們學(xué)習(xí)下。2019-02-02