摘要:下面跟蹤代碼到這個實現中看看是怎么做的在實例化的過程中,在構造函數中調用了其超類的構造函數,而在超類中對其所處換環境進行的判斷,所謂的環境呢,事實上指得就是是通過,還是通過加載的上下文,這也就意味著不同方式加載可能存在某些不同。
前言
本文基于《Spring源碼深度解析》學習, 《Spring源碼深度解析》講解的Spring版本低于Spring3.1,當前閱讀的版本為Spring5.x,所以在文章內容上會有所不同。
這篇文章基于有一定Spring 基礎的人進行講解,所以有些問題并不做詳細的實現, 如有分析不合理或是錯誤的地方請指教指正,不勝感激。
在《Spring源碼深度解析》中有這樣一個實例:
public class BeanFactoryTest { @test public void testSimpleLoad() { BeanFactory bf = new XmBeanFactory(new ClassPathResource("beanFactoryTest.xml"); MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); assertEquals("testStr", bean.getTestStr()); } }
當然在這里會有一個Spring的配置文件 beanFactoryTest.xml, 當使用xml文件的時候,會發現文件頭有一些
這樣的標簽, 建議學習一下DOM,DOM2, DOM3結構, 以便更加清晰的了解xml文件頭中的內容的真正意義。
這里的配置文件只寫一個相關的bean
這段代碼的作用就是以下幾點:
讀取配置文件。
在Spring的配置中找到bean,并實例化。
使用斷言判斷實例的屬性。
@deprecated as of Spring 3.1 in favor of {@link DefaultListableBeanFactory} and {@link XmlBeanDefinitionReader}
這是該類在當前版本的部分注釋,在Spring3.1以后這個類被棄用了,Spring官方不建議使用這個類。建議使用以上這兩個類。XmlBeanDefinitionReader本就是以前定義在這個類中的一個final的實例,而DefaultListableBeanFactory則是該類的超類。加載配置文件可以這樣使用:
Resource resource = new ClassPathResource("beanFactoryTest.xml"); BeanFactory beanFactory = new DefaultListableBeanFactory(); BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory); beanDefinitionReader.loadBeanDefinitions(resource); MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); assertEquals("testStr", bean.getTestStr());
這個過程和上面的過程實際上的實現只用一點不同、前者是在創建時就直接實例化了bean, 后者則是在加載的時候才實例化bean:
讀取配置文件。
創建BeanFactory。
創建BeanDefinitionReader。
加載resource資源。
獲取bean實例(實例化bean)。
使用斷言判斷實例的屬性。
事實上在實際的使用中,絕大多數時候都會通過以下這種ApplicationContext的方式來加載Spring的配置文件并進行解析, 以后會寫到這里的實現:
ApplicationContext sc = new ClassPathXmlApplicationContext("applicationContext.xml");二、加載并解析配置文件
Resource resource = new ClassPathResource("beanFactoryTest.xml");
通過ClassPathResource 加載配置文件,并構建該實例的時候,是使用Resource接口進行定義的, 這也就說明了創建的實際上是Resource的實例,通過查看Resource 的源碼不難發現,Resource對Java中將要使用的資源進行了抽象,Spring的設計中幾乎所有可以加載資源的
類需要直接或間接的實現Resource 這個接口。下面可以看一下這個接口:
boolean exists(); // 判斷是否資源是否存在 default boolean isReadable() { // 判斷資源是否可讀 return exists(); } default boolean isOpen() { // 判斷文件是否打開 return false; } default boolean isFile() { // 判斷文件是否是文件系統中的文件,Spring5.0后加入的 return false; } URL getURL() throws IOException; // 獲取文件的URL URI getURI() throws IOException; // 獲取文件的URI File getFile() throws IOException; // 獲取文件 default ReadableByteChannel readableChannel() throws IOException { // 返回一個Channel, 擁有最大效率的讀操作 return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; // 返回資源解析后的長度 long lastModified() throws IOException; // 最后一次休干時間 Resource createRelative(String relativePath) throws IOException; // 基于當前資源創建一個相對資源 @Nullable String getFilename(); // 獲取文件名 for example, "myfile.txt" String getDescription(); // 獲取資源描述, 當發生錯誤時將被打印
通過查看源碼,還有一點可以發現, Resource接口繼承了InputStreamSource 接口,下面來看下這個接口:
public interface InputStreamSource { /** * Return an {@link InputStream} for the content of an underlying resource. *It is expected that each call creates a fresh stream. *
This requirement is particularly important when you consider an API such * as JavaMail, which needs to be able to read the stream multiple times when * creating mail attachments. For such a use case, it is required * that each {@code getInputStream()} call returns a fresh stream. * @return the input stream for the underlying resource (must not be {@code null}) * @throws java.io.FileNotFoundException if the underlying resource doesn"t exist * @throws IOException if the content stream could not be opened */ InputStream getInputStream() throws IOException; }
這個接口的作用非常簡單并且是頂層接口,它的作用就是返回一個InputStream, 最簡單的作用卻提供了最大的方便, 因為所有資源加載類幾乎都直接或間接的實現了Resource, 這也就意味著, 幾乎所有的資源加載類都可以得到一個InputStream, 這將使得在資源加載之后能輕易地得到一個InputStream, 這非常重要。通過InputStream, Spring使所有的資源文件都能進行統一的管理了, 優點是不言而喻的。至于實現是非常簡單的, ClassPathResource 中的實現方式便是通過class或者classLoader提供的底層方法進行調用的, 對于FileSystemResource的實現其實更加簡單, 就是直接使用FileInputStream對文件進行實例化。
三、DefaultListableBeanFactory、XmlBeanDefinitionReader和BeanDefinitionRegistry配置文件加載完成后進行的下一步操作是這樣的,這和3.1之前的版本不太一樣,至于為什么要棄用XmlBeanFactory(我猜是為了對其他部分進行設計,從而讓這部分代碼更加充分的進行解耦):
BeanFactory beanFactory = new DefaultListableBeanFactory(); BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory); beanDefinitionReader.loadBeanDefinitions(resource);
配置文件加載完成后,創建了一個BeanFactory的實例。DefaultListableBeanFactory: 見名知意,這是一個默認可列的bean工廠類,注釋用說道,典型的一個應用就是在第一次定義時注冊所有bean。接下來的操作是利用多態將上一步創建的BeanFactory的實例轉成BeanDefinitionRegistry, 因為下一步需要讀取xml文件中定義的內容,這也就是XmlBeanDefinitionReader的作用,而XmlBeanDefinitionReader在實例化的時候需要一個bean的定義注冊機,所以就進行了以上操作, 事實上:在創建BeanFactory實例時,同樣可以定義為BeanDefinitionRegistry類型。下面詳細說下一這三個類的作用:
DefaultListableBeanFactory:在定義時通過當前類的類加載器(如果不存在就向上級加載器尋找直到系統加載器)下的所有的bean進行注冊,注意這里只是進行注冊而已。
BeanDefinitionRegistry: 為了注冊Spring持有的bean的一個接口,是在BeanFactory,AbstractBeanDefinition中間的一層接口。
XmlBeanDefinitionReader:注釋中這樣寫道
/** * Bean definition reader for XML bean definitions. * Delegates the actual XML document reading to an implementation * of the {@link BeanDefinitionDocumentReader} interface. * *Typically applied to a * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} * or a {@link org.springframework.context.support.GenericApplicationContext}. * *
This class loads a DOM document and applies the BeanDefinitionDocumentReader to it. * The document reader will register each bean definition with the given bean factory, * talking to the latter"s implementation of the * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} interface. */ 只說最重要的一個部分, 在這里它需要委托一個真正的xml文檔讀取器來讀取文檔內容,也就是BeanDefinitionDocumentReader,而這個文檔讀取器將讀取所有的bean注冊內容,而這些資源正是1、2中所得到的。
接下來就是重要的一步,beanDefinitionReader.loadBeanDefinitions(resource); 在解析了配置文件中的bean后,事實上配置文件中bean并沒有被真正的加載,并且上面的步驟也只是對所有的bean進行了一次注冊, 所以,這個時候load了resoure中的內容, 在編碼沒有問題以后,并且resource中bean可以在類加載器下找到這些類,這時就對這些bean進行加載,實例化。下面跟蹤代碼到這個實現中看看Spring 是怎么做的:
/** * Create new XmlBeanDefinitionReader for the given bean factory. * @param registry the BeanFactory to load bean definitions into, * in the form of a BeanDefinitionRegistry */ public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); } protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; // Determine ResourceLoader to use. if (this.registry instanceof ResourceLoader) { this.resourceLoader = (ResourceLoader) this.registry; } else { this.resourceLoader = new PathMatchingResourcePatternResolver(); } // Inherit Environment if possible if (this.registry instanceof EnvironmentCapable) { this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); } else { this.environment = new StandardEnvironment(); } }
在實例化XmlBeanDefinitionReader 的過程中,在構造函數中調用了其超類的構造函數,而在超類中對其所處換環境進行的判斷,所謂的環境呢,事實上指得就是是通過BeanFactory, 還是通過ApplicationContext加載的上下文,這也就意味著不同方式加載可能存在某些不同。寫這些的目的其實是為了引出這里的一個我們十分關注的東西, 就是自動裝配。在AbstractAutowireCapableBeanFactory這個抽象類的構造方法中實現了相關的自動裝配,在BeanDefinitionRegistry 和DefaultListableBeanFactory中都繼承了這個抽象類, 并在其構造函數內直接調用了其超類的構造函數也就是:
/** * Create a new AbstractAutowireCapableBeanFactory. */ public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
這里有必要提及一下ignoreDependencyInterface();這個方法。它的主要功能就是忽略接口中的自動裝配, 那么這樣做的目的是什么呢?會產生什么樣的效果呢?舉例來說, 當A中有屬性B, 那么Spring在獲取A的時候就會去先去獲取B, 然而有些時候Spring不會這樣做,就是Spring通過BeanNameAware、BeanFactoryAware和BeanClassLoaderAware進行注入的, 也就是根據環境的不同, Spring會選擇相應的自從裝配的方式。在不是當前環境中的注入,Spring并不會再當前環境對Bean進行自動裝配。類似于,BeanFactory通過BeanFactoryAwar進行注入或者ApplicationContext通過ApplicationContextAware進行注入。
經過了這么長時間的鋪墊,終于應該進入正題了, 就是進入通過loadBeanDefinitions(resource)方法加載這個文件。這個方法這樣實現:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } SetcurrentResources = 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 { 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(); } } }
這段代碼很容易理解,不過真正實現的核心代碼是在return doLoadBeanDefinitions(inputSource, encodedResource.getResource());這里實現的。下面是這個方法的核心代碼:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } }
這段處理可以說非常容易了,對resource流進行再封裝,封裝為Docment對象,然后解析并注冊這個doc中的bean對象,返回定義的bean的個數。在Spring3.1之前上面這個方法中還要驗證加載Xml是否符合規范。而Spring5.x之后Spring將驗證的工作放到了獲取Document中。
三、獲取Document看一下Document doc = doLoadDocument(inputSource, resource);這個方法的源碼:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
在這個方法中做了三件事:
getEntityResolver();這個方法將根據當前的resource創建一個ResourceLoader實例,然后根據這個對ResourceLoader進行封裝,封裝為EntityResolver實例, 這個EntityResolver的作用是進行處理實體映射。
getValidationModeForResource(); 這個方法的作用是獲取資源的驗證模式,通過自動或手動的方式對已經加載到的資源進行檢驗。這里是真正對xml文件進行驗證的地方。
isNamespaceAware(); 這個方法用來判斷加載的xml文件是否支持明明空間。
實現上面方法的類繼承了這個接口:DocumentLoader,并且實現了這個接口中的唯一的抽象:
/** * Load a {@link Document document} from the supplied {@link InputSource source}. * @param inputSource the source of the document that is to be loaded * @param entityResolver the resolver that is to be used to resolve any entities * @param errorHandler used to report any errors during document loading * @param validationMode the type of validation * {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD} * or {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD}) * @param namespaceAware {@code true} if support for XML namespaces is to be provided * @return the loaded {@link Document document} * @throws Exception if an error occurs */
Document loadDocument(
InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception;
那么詳細講一下上面提及的EntityResolver, 如果SAX(Simple API for XML:簡單的來講,它的作用就是不去構建DOM,而是以應用程序的方式以最有效率的方式實現XML與應用實體之間的映射;當然還有一種方式是解析DOM,具體兩種方式,我也沒有做過相應深入探究)應用驅動程序需要實現自定義的處理外部實體,在必須實現此接口并通過某種方式向SAX驅動器注冊一個實例。這需要根據XML頭部的DTD中的網絡地址下載聲明并認證,而EntityResolver實際上就是在提供一個尋dtd找聲明的方法,這樣就可以在項目中直接定義好聲明,而通過本地尋找的方式避免了網絡尋找的過程,編譯器也避免了在網絡延遲高或沒有網絡的情況下報錯。
四、解析及注冊BeanDefinitions當文件轉換為Document后,接下來提取及注冊Bean就是我們后頭的重頭戲。事實上,這一部分內容并不會在我們的使用中出現了。
同樣在XmlBeanDefinitionReader這個類中,可以發現隨著Docment的獲取完成后,直接做的是下面的這個事情registerBeanDefinitions();:
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. *Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
作用在注釋中寫的很清楚,注冊DOM文檔(Spring的配置信息中. 也就是解析后的xml)中包含的bean。
首先創建一個bean定義文檔讀取器,這個對象是根據DefaultBeanDefinitionDocumentReader的class通過反射的方式來創建的,
DefaultBeanDefinitionDocumentReader實現了BeanDefinitionDocumentReader這個接口,這里唯一的抽象方法就是registerBeanDefinitions(Document doc, XmlReaderContext readerContext);,這也就意味著上面的第三行代碼是一個自然的應用。
提示:這里的doc參數就是通過之前的doLoadDocument方法獲得的,而這很好的應用了面向對象的單一職責原則, 將轉換為Docment的復雜過程交給一個單一的類處理,而這個類就是BeanDefinitionDocumentReader, 事實上這是一個接口,而具體的實例化是在createBeanDefinitionDocumentReader這個方法中完成的。
getRegistry();的作用實際上是獲得一個BeanDefinitionRegistry對象。下面的圖片是程序最開始時,容器開始實現時候的代碼,這里可以看到BeanDefinitionRegistry這個接口。注意這里創建的BeanDefinitionRegistry是final的,也就是這里獲取的是Spring發現的所有的bean個數,是不許改變的, 熟悉設計模式的同學肯定知道,這個BeanDefinitionRegistry是一個單例的。
而接下來做的就是就是,記錄所有對我們的資源文件進行加載,這里是真正解析xml文件并加載的地方,而這個邏輯就是那么簡單了, 先統計當前的bean defintions個數然后加載一些bean定義進來,然后在統計bean 的個數,然后用后來的減去開始的就是加載的。沒錯了,就是學前班加減法。
到這里我已經不想探究xml文件是如何讀取的了,如果想看的話,可以去看下一篇《Spring源碼一(容器的基本實現2)》!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72023.html
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結果如下本小節,我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業級應用開發框架,于 2004 年由 Rod Johnson 發布了 1.0 版本。經過十幾年的迭代,現在的 Spring 框架已經非常成熟了...
摘要:進一步解析其他所有屬性并統一封裝至類型的實例中。是一個接口,在中存在三種實現以及。通過將配置文件中配置信息轉換為容器的內部表示,并將這些注冊到中。容器的就像是配置信息的內存數據庫,主要是以的形式保存。而代碼的作用就是實現此功能。 前言:繼續前一章。 一、porfile 屬性的使用 如果你使用過SpringBoot, 你一定會知道porfile配置所帶來的方便, 通過配置開發環境還是生產...
摘要:從使用到原理學習線程池關于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現在軟件開發中,分散于應用中多出的功能被稱為橫切關注點如事務安全緩存等。 Java 程序媛手把手教你設計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經風雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當初愛情萌芽的模樣…… Java 進階面試問題列表 -...
摘要:對于開發者來說,無疑是最常用也是最基礎的框架之一。概念上的東西還是要提一嘴的用容器來管理。和是容器的兩種表現形式。定義了簡單容器的基本功能。抽象出一個資源類來表示資源調用了忽略指定接口的自動裝配功能委托解析資源。 對于Java開發者來說,Spring無疑是最常用也是最基礎的框架之一。(此處省略1w字吹Spring)。相信很多同行跟我一樣,只是停留在會用的階段,比如用@Component...
閱讀 890·2021-10-25 09:44
閱讀 1262·2021-09-23 11:56
閱讀 1183·2021-09-10 10:50
閱讀 3131·2019-08-30 15:53
閱讀 2134·2019-08-30 13:17
閱讀 617·2019-08-29 18:43
閱讀 2491·2019-08-29 12:57
閱讀 855·2019-08-26 12:20