本文主要分析 spring
中 BeanDefinition
的加載,對於其解析咱們在後面的文章中專門分析。
BeanDefinition
是屬於 Spring Bean
模塊的,它是對 spring bean 的統一抽象描述定義接口,咱們知道在spring中定義bean的方式有不少種,如XML、註解以及自定義標籤,同事Bean的類型也有不少種,如常見的工廠Bean、自定義對象、Advisor等等,咱們在分析加載BeanDefinition以前,首先來了解它的定義和註冊設計。
上面類圖咱們作一個簡單介紹,具體詳細介紹在後面的相關文章說明java
AliasRegistry
爲 Bean註冊一個別名的頂級接口spring
BeanDefinitionRegistry
主要用來把bean的描述信息註冊到容器中,spring在註冊bean時通常是獲取到bean後經過 BeanDefinitionRegistry
來註冊噹噹前的 BeanFactory
中緩存
BeanDefinition
是用來定義描述 Bean的名字、做用域、角色、依賴、懶加載等基礎信息,以及包含與spring容器運行和管理Bean信息相關的屬性。spring中經過它實現了對bean的定製化統一,這也是一個核心接口層微信
AnnotatedBeanDefinition
是一個接口,繼承了 BeanDefinition
, 對其作了必定的擴展,主要用來描述註解Bean的定義信息ide
AttributeAccessor
主要用來設置 Bean配置信息中的屬性和屬性值的接口,實現key-value的映射關係this
AbstractBeanDefinition
是對 BeanDefintion
的一個抽象化實現,是一個模板,具體的詳細實現交給子類編碼
ClassPathResource resource = new ClassPathResource("bean.xml"); // <1> DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // <2> XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // <3> reader.loadBeanDefinitions(resource);
上面這段代碼是 spring
中從資源的定位到加載過程,咱們能夠簡單分析一下:線程
ClassPathResource
進行資源的定位,獲取到資源BeanFactory
,即上下文XmlBeanDefinitionReader
對象,該 Reader
是一個資源解析器, 實現了 BeanDefinitionReader
接口整個過程分爲三個大步驟,示意圖:
咱們文章主要分析的就是第二步,裝載的過程,debug
資源的定位咱們以前文章分析過了,不在闡述,這裏咱們關心 reader.loadBeanDefinitions(resource);
這句的具體實現,
經過代碼追蹤咱們能夠知道方法 #loadBeanDefinitions(...)
是定義在 BeanDefinitionReader
中的,而他的具體實現是在 XmlBeanDefinitionReader
類中,代碼以下:設計
/** * 從指定的xml文件中加載bean的定義 * Load bean definitions from the specified XML file. * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { //調用私有方法處理 這裏將resource進行了編碼處理,保證瞭解析的正確性 return loadBeanDefinitions(new EncodedResource(resource)); } /** * 裝載bean定義的真實處理方法 * Load bean definitions from the specified XML file. * @param encodedResource the resource descriptor for the XML file, * allowing to specify an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { //1.對資源判空 Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } //2.獲取當前線程中的 EncodedResource 集合 -> 已經加載過的資源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); //3.若當前已加載資源爲空,則建立並添加 if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //4.添加資源到集合若是已加載資源中存在 則拋出異常 if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } //5.獲取 encodedResource 中的 Resource ,在獲取 intputSteram 對象 try (InputStream inputStream = encodedResource.getResource().getInputStream()) { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //6. 真實執行加載beanDefinition業務邏輯的方法 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { //7.從已加載集合中去除資源 currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
經過 resourcesCurrentlyBeingLoaded.get()
代碼,來獲取已經加載過的資源,而後將 encodedResource
加入其中,若是 resourcesCurrentlyBeingLoaded
中已經存在該資源,則拋出 BeanDefinitionStoreException
異常。
爲何須要這麼作呢?答案在 "Detected cyclic loading" ,避免一個 EncodedResource
在加載時,還沒加載完成,又加載自身,從而致使死循環。也所以,,當一個 EncodedResource
加載完成後,須要從緩存中剔除。
從 encodedResource
獲取封裝的 Resource
資源,並從 Resource
中獲取相應的 InputStream
,而後將 InputStream
封裝爲 InputSource
,最後調用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法,執行加載 BeanDefinition
的真正邏輯
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //1. 獲取到 Document 實例 Document doc = doLoadDocument(inputSource, resource); //2. 註冊bean實列,經過document int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
上面 #registerBeanDefinitions(...)
方法是 beanDefinition
的具體加載過程, #doLoadDocument(...)
是解析 document
的方法內部包含 spring
的驗證模型與 document
解析兩塊,這些咱們在後面專門進行分析
本文由AnonyStar 發佈,可轉載但需聲明原文出處。
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公帳號 :雲棲簡碼 獲取更多優質文章
更多文章關注筆者博客 :雲棲簡碼