標籤:name blog jsb interface 通過 封裝 屬性 parse 2.3
基本概念
BeanDefinitionReader ,該介面的作用就是載入 Bean。
在 Spring 中,Bean 一般來說都在設定檔中定義。而在配置的路徑由在 web.xml 中定義。所以載入 Bean 的步驟大致就是:
下面來看它的介面定義:
public interface BeanDefinitionReader { BeanDefinitionRegistry getRegistry(); ResourceLoader getResourceLoader(); ClassLoader getBeanClassLoader(); BeanNameGenerator getBeanNameGenerator(); // 通過 Resource 載入 Bean int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; // 通過 location 載入資源 int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;}
具體的繼承關係如下:
流程分析
首先來看 Spring Ioc 容器從啟動開始到調用 BeanDefinitionReader 載入 Bean 的過程如下:
注意:由於這裡採用 XML 檔案作為 Spring 的設定檔,所以預設調用 XmlBeanDefinitionReader 來處理。
1.通過 BeanFactory 載入 Bean
在 Spring 容器(ApplicationContext)內部存在一個內部容器(BeanFactory)負責 Bean 的建立與管理。
在建立完 BeanFactory ,下一步就是要去載入 Bean。它由 loadBeanDefinitions 方法負責。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 1.建立 BeanDefinitionReader XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 2.設定 BeanDefinitionReader 的相關屬性 // 2.1.設定 Environment,即環境,與容器的環境一致 beanDefinitionReader.setEnvironment(getEnvironment()); // 2.2.設定 ResourceLoader,即資源載入器,因為容器實現了該介面,具體載入資源的功能 beanDefinitionReader.setResourceLoader(this); // 2.3.設定 EntityResolver,即實體解析器,這裡用於解析資源載入器載入的資源內容 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 3.初始化 BeanDefinitionReader ,空方法 initBeanDefinitionReader(beanDefinitionReader); // 4.通過 BeanDefinitionReader 載入 Bean loadBeanDefinitions(beanDefinitionReader);}// 建構函式public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { // 內部 BeanFactory 被當作 Bean 註冊器 super(registry);}
觀察代碼,該方法的主要目的是建立了 BeanDefinitionReader ,並由它去載入 Bean。具體過程如下:
- 建立 BeanDefinitionReader
- 設定 BeanDefinitionReader 的相關屬性
- 初始化 BeanDefinitionReader
- 通過 BeanDefinitionReader 載入 Bean
2.通過 BeanDefinitionReader 載入 Bean
在建立完 BeanDefinitionReader 後,則就需要通過它來 載入 Bean,過程如下:
// 通過 BeanDefinitionReader 載入 Bean protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { // 取得 Spring 容器的所有設定檔 String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { // 調用 BeanDefinitionReader 載入 Bean reader.loadBeanDefinitions(configLocation); } }}
3.通過 Location 載入 Bean
載入的 Bean 的責任被交給了 BeanDefinitionReader ,下面來看看該類的 loadBeanDefinitions 方法。
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null);}public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { // 1.取得資源載入器,即容器本身 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { // 拋出異常... } // 判斷資源載入器類型 if (resourceLoader instanceof ResourcePatternResolver) { // 說明該 ResourceLoader 可以基於路徑載入多個資源 try { // 2.載入資源 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 3.通過 Resource 載入 Bean int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } // 省略代碼... return loadCount; }catch (IOException ex) { // 拋出異常... } }else { // 表示 ResourceLoader 只能載入一個資源 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } // 省略代碼... return loadCount; }}
觀察代碼,該方法的工作流程可分為四個步驟:
4.通過 Resource 載入 Bean
在得到資源(Resource)後,該方法對它進行了封裝,使其變成一個 EncodedResource 對象。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { // EncodedResource 表示編碼類別型的資源 return loadBeanDefinitions(new EncodedResource(resource));}// 建構函式public EncodedResource(Resource resource, String encoding) { // 封裝資源,並帶上編碼類別型 this(resource, encoding, null);}
5.通過 EncodedResource 載入 Bean
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { // 省略代碼... // 1.取得[已載入的資源]的集合,用於存在已載入的資源。空,則建立。 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } // 2.將當前資源加入集合 if (!currentResources.add(encodedResource)) { // 拋出異常... } try { // 3.將資源轉換成流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { //5.通過流建立 InputSource ,並設定編碼 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //6.通過 InputSource 載入 Bean return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); }finally { inputStream.close(); } }catch (IOException ex) { // 拋出異常... }finally { // 移除已完成解析的資源 currentResources.remove(encodedResource); // 集合為空白,則一併刪除 if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } }}
- 取得已載入的資源集合
- 將當前資源添加到集合
- 將資源轉換成流
- 通過流建立 InputSource ,並設定編碼
- 通過 InputSource 載入 Bean
6.通過 InputSource 載入 Bean
之所以把 Resource 轉換成 InputSource ,就是為了 SAX 解析作準備。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 1.解析 XML 檔案 Document doc = doLoadDocument(inputSource, resource); // 2.註冊 Bean return registerBeanDefinitions(doc, resource); }catch (BeanDefinitionStoreException ex) { // 拋出異常... }catch (SAXParseException ex) { // 拋出異常... }catch (SAXException ex) { // 拋出異常... }catch (ParserConfigurationException ex) { // 拋出異常... }catch (IOException ex) { // 拋出異常... }catch (Throwable ex) { // 拋出異常... }}// 解析 XML 檔案,並返回 Document 對象。protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware());}
3.Bean 註冊
上一步中 XmlBeanDefinitionReader 已經取得了 XML 的 Document 對象,完成了資源的解析過程。
下一步就是 Bean 的註冊過程。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 利用 documentReader 對設定檔的內容進行解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 取得已經註冊 BeanDefinition int countBefore = getRegistry().getBeanDefinitionCount(); // 關鍵 -> 註冊 BeanDefinition (包含解析過程) documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore;}
總結
分析完 BeanDefinitionReader 具體工作流程,最後通過一個圖簡單闡述:
07.Spring Bean 載入 - BeanDefinitionReader