原文出自:http://cmsblogs.comspring
先看一段熟悉的代碼:編程
ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource);
這段代碼是 Spring 中編程式使用 IOC 容器,經過這四段簡單的代碼,咱們能夠初步判斷 IOC 容器的使用過程。緩存
整個過程就分爲三個步驟:資源定位、裝載、註冊,以下:數據結構
<bean>
都對應着一個BeanDefinition對象。getBean()
向容器索要 Bean 時。固然咱們能夠經過設置預處理,即對某個 Bean 設置 lazyinit 屬性,那麼這個 Bean 的依賴注入就會在容器初始化的時候完成。資源定位在前面已經分析了,下面咱們直接分析加載,上面提過 reader.loadBeanDefinitions(resource)
纔是加載資源的真正實現,因此咱們直接從該方法入手。this
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
從指定的 xml 文件加載 Bean Definition,這裏會先對 Resource 資源封裝成 EncodedResource。這裏爲何須要將 Resource 封裝成 EncodedResource呢?主要是爲了對 Resource 進行編碼,保證內容讀取的正確性。封裝成 EncodedResource 後,調用 loadBeanDefinitions()
,這個方法纔是真正的邏輯實現。以下:編碼
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } // 獲取已經加載過的資源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } // 將當前資源加入記錄中 if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { // 從 EncodedResource 獲取封裝的 Resource 並從 Resource 中獲取其中的 InputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); // 設置編碼 if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 核心邏輯部分 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { // 從緩存中剔除該資源 currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
首先經過 resourcesCurrentlyBeingLoaded.get()
來獲取已經加載過的資源,而後將 encodedResource 加入其中,若是 resourcesCurrentlyBeingLoaded 中已經存在該資源,則拋出 BeanDefinitionStoreException 異常。完成後從 encodedResource 獲取封裝的 Resource 資源並從 Resource 中獲取相應的 InputStream ,最後將 InputStream 封裝爲 InputSource 調用 doLoadBeanDefinitions()
。方法 doLoadBeanDefinitions()
爲從 xml 文件中加載 Bean Definition 的真正邏輯,以下:spa
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 獲取 Document 實例 Document doc = doLoadDocument(inputSource, resource); // 根據 Document 實例****註冊 Bean信息 return registerBeanDefinitions(doc, resource); } 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); } }
核心部分就是 try 塊的兩行代碼。code
doLoadDocument()
方法,根據 xml 文件獲取 Document 實例。其實在 doLoadDocument()
方法內部還獲取了 xml 文件的驗證模式。以下:xml
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
調用getValidationModeForResource()
獲取指定資源(xml)的驗證模式。因此 doLoadBeanDefinitions()
主要就是作了三件事情。對象
getValidationModeForResource()
獲取 xml 文件的驗證模式loadDocument()
根據 xml 文件獲取相應的 Document 實例。registerBeanDefinitions()
註冊 Bean 實例。