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