javascript
从源码深处体验Spring核心技术--基于Xml的IOC容器的初始化
IOC 容器的初始化包括?BeanDefinition?的?Resource 定位、加載和注冊這三個基本的過程。
我們以ApplicationContext 為例講解,ApplicationContext 系列容器也許是我們最熟悉的,因為 Web 項目中使用的 XmlWebApplicationContext 就屬于這個繼承體系,還有 ClasspathXmlApplicationContext 等,其繼承體系如下圖所示:
ApplicationContext 允許上下文嵌套,通過保持父上下文可以維持一個上下文體系。
對于 Bean 的查找可以在這個上下文體系中發生,首先檢查當前上下文,其次是父上下文,逐級向上,這樣為不同的 Spring應用提供了一個共享的 Bean 定義環境。
1、尋找入口
還有一個我們用的比較多的 ClassPathXmlApplicationContext,通過 main()方法啟動:
ApplicationContext app = new ClassPathXmlApplicationContext("application.xml");先看其構造函數的調用:
public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[]{configLocation}, true, (ApplicationContext)null); }其實際調用的構造函數為:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); this.setConfigLocations(configLocations); if(refresh) { this.refresh(); } }還 有 像 AnnotationConfigApplicationContext 、 FileSystemXmlApplicationContext 、 XmlWebApplicationContext 等都繼承自父容器 AbstractApplicationContext主要用到了裝飾器模式 和策略模式,最終都是調用 refresh()方法。
2、獲得配置路徑
通 過 分 析 ClassPathXmlApplicationContext 的 源 代 碼 可 以 知 道 , 在 創 建 ClassPathXmlApplicationContext 容器時,構造方法做以下兩項重要工作:
首先,調用父類容器的構造方法(super(parent)方法)為容器設置好 Bean 資源加載器。
然后,再調用父類AbstractRefreshableConfigApplicationContext 的 setConfigLocations(configLocations)方法設置 Bean 配置信息的定位路徑。
通 過 追 蹤 ClassPathXmlApplicationContext 的 繼 承 體 系 , 發 現 其 父 類 的 父 類 AbstractApplicationContext 中初始化 IOC 容器所做的主要源碼如下:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { //靜態初始化塊,在整個容器創建過程中只執行一次 static { //為了避免應用程序在 Weblogic8.1 關閉時出現類加載異常加載問題,加載 IOC 容 //器關閉事件(ContextClosedEvent)類 ContextClosedEvent.class.getName(); }public AbstractApplicationContext() { this.resourcePatternResolver = getResourcePatternResolver(); }public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); }//獲取一個 Spring Source 的加載器用于讀入 Spring Bean 配置信息 protected ResourcePatternResolver getResourcePatternResolver() { //AbstractApplicationContext 繼承 DefaultResourceLoader,因此也是一個資源加載器 //Spring 資源加載器,其 getResource(String location)方法用于載入資源 return new PathMatchingResourcePatternResolver(this); }... }AbstractApplicationContext 的默認構造方法中有調用 PathMatchingResourcePatternResolver 的 構造方法創建 Spring 資源加載器:
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); //設置 Spring 的資源加載器 this.resourceLoader = resourceLoader; }在設置容器的資源加載器之后,接下來 ClassPathXmlApplicationContext 執行setConfigLocations()方法通過調用其父類AbstractRefreshableConfigApplicationContext的方法進行對Bean配置信息的定位,該方法的源碼如下:
//處理單個資源文件路徑為一個字符串的情況 public void setConfigLocation(String location) { //String CONFIG_LOCATION_DELIMITERS = ",; /t/n"; //即多個資源文件路徑之間用” ,; \t\n”分隔,解析成數組形式 setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS)); } /** * Set the config locations for this application context. * <p>If not set, the implementation may use a default as appropriate. */ //解析 Bean 定義資源文件的路徑,處理多個資源文件字符串數組 public void setConfigLocations(@Nullable String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { // resolvePath 為同一個類中將字符串解析為路徑的方法 this.configLocations[i] = resolvePath(locations[i]).trim(); } }else { this.configLocations = null; } }通過這兩個方法的源碼我們可以看出,我們既可以使用一個字符串來配置多個 Spring Bean 配置信息,也可以使用字符串數組,即下面兩種方式都是可以的:
ClassPathResource res = new ClassPathResource("a.xml,b.xml");多個資源文件路徑之間可以是用” , ; \t\n”等分隔。
ClassPathResource res =new ClassPathResource(new String[]{"a.xml","b.xml"});至此,SpringIOC 容器在初始化時將配置的 Bean 配置信息定位為 Spring 封裝的Resource。
3、開始啟動
SpringIOC 容器對 Bean 配置資源的載入是從 refresh()函數開始的,refresh()是一個模板方法,規定了IOC 容 器 的 啟 動 流 程 , 有 些 邏 輯 要 交 給 其 子 類 去 實 現 。
它 對 Bean 配 置 資 源 進 行 載 入 ClassPathXmlApplicationContext 通過調用其父類 AbstractApplicationContext 的 refresh()函數啟動整個 IOC 容器對 Bean 定義的載入過程,現在我們來詳細看看 refresh()中的邏輯處理:
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //1、調用容器準備刷新的方法,獲取容器的當時時間,同時給容器設置同步標識 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. //2、告訴子類啟動 refreshBeanFactory()方法,Bean 定義資源文件的載入從 //子類的 refreshBeanFactory()方法啟動 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. //3、為 BeanFactory 配置容器特性,例如類加載器、事件處理器等 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. //4、為容器的某些子類指定特殊的 BeanPost 事件處理器 postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. //5、調用所有注冊的 BeanFactoryPostProcessor 的 Bean invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //6、為 BeanFactory 注冊 BeanPost 事件處理器. //BeanPostProcessor 是 Bean 后置處理器,用于監聽容器觸發的事件 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. //7、初始化信息源,和國際化相關. initMessageSource(); // Initialize event multicaster for this context. //8、初始化容器事件傳播器. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //9、調用子類的某些特殊 Bean 初始化方法 onRefresh(); // Check for listener beans and register them. //10、為事件傳播器注冊事件監聽器.registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //11、初始化所有剩余的單例 Bean finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. //12、初始化容器的生命周期事件處理器,并發布容器的生命周期事件 finishRefresh(); }catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); }// Destroy already created singletons to avoid dangling resources. //13、銷毀已創建的 Bean destroyBeans(); // Reset 'active' flag. //14、取消 refresh 操作,重置容器的同步標識. cancelRefresh(ex); // Propagate exception to caller. throw ex; }finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... //15、重設公共緩存 resetCommonCaches(); } } }refresh()方法主要為 IOC 容器 Bean 的生命周期管理提供條件,Spring IOC 容器載入 Bean 配置信息從 其 子 類 容 器 的 refreshBeanFactory() 方 法 啟 動 。
所 以 整 個 refresh() 中 “ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();”
這句以后代碼的 都是注冊容器的信息源和生命周期事件,我們前面說的載入就是從這句代碼開始啟動。
refresh()方法的主要作用是:在創建 IOC 容器前,如果已經有容器存在,則需要把已有的容器銷毀和關閉,以保證在 refresh 之后使用的是新建立起來的 IOC 容器。
它類似于對 IOC 容器的重啟,在新建立好的容器中對容器進行初始化,對 Bean 配置資源進行載入。
4、創建容器
obtainFreshBeanFactory()方法調用子類容器的 refreshBeanFactory()方法,啟動容器載入 Bean 配置
信息的過程,代碼如下:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //這里使用了委派設計模式,父類定義了抽象的 refreshBeanFactory()方法,具體實現調用子類容器的 refreshBeanFactory()方 法 refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); }return beanFactory; }AbstractApplicationContext 類中只抽象定義了 refreshBeanFactory()方法,容器真正調用的是其子類 AbstractRefreshableApplicationContext 實現的 refreshBeanFactory()方法,方法的源碼如下:
protected final void refreshBeanFactory() throws BeansException { //如果已經有容器,銷毀容器中的 bean,關閉容器 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); }try { //創建 IOC 容器 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); //對 IOC 容器進行定制化,如設置啟動參數,開啟注解的自動裝配等 customizeBeanFactory(beanFactory); //調用載入 Bean 定義的方法,主要這里又使用了一個委派模式,在當前類中只定義了抽象的 loadBeanDefinitions 方法,具體的實現調用子類容器 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory;} }catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);} }在這個方法中,先判斷 BeanFactory 是否存在,如果存在則先銷毀 beans 并關閉beanFactory,接著 創建 DefaultListableBeanFactory,并調用loadBeanDefinitions(beanFactory)裝載 bean 定義。
5、載入配置路徑
AbstractRefreshableApplicationContext 中只定義了抽象的 loadBeanDefinitions 方法,容器真正調用的是其子類 AbstractXmlApplicationContext 對該方法的實現,AbstractXmlApplicationContext 的主要源碼如下:
loadBeanDefinitions() 方 法 同 樣 是 抽 象 方 法 , 是 由 其 子 類 實 現 的 , 也 即 在 AbstractXmlApplicationContext 中。
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { ... //實現父類抽象的載入 Bean 定義方法 @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //創建 XmlBeanDefinitionReader,即創建 Bean 讀取器,并通過回調設置到容器中去,容器使用該讀取器讀取 Bean 配置資源 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //為 Bean 讀取器設置 Spring 資源加載器,AbstractXmlApplicationContext 的 //祖先父類 AbstractApplicationContext 繼承 DefaultResourceLoader,因此,容器本身也是一個資源加載器 beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); //為 Bean 讀取器設置 SAX xml 解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); //當 Bean 讀取器讀取 Bean 定義的 Xml 資源文件時,啟用 Xml 的校驗機制 initBeanDefinitionReader(beanDefinitionReader); //Bean 讀取器真正實現加載的方法 loadBeanDefinitions(beanDefinitionReader); }protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) { reader.setValidating(this.validating); }//Xml Bean 讀取器加載 Bean 配置資源 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //獲取 Bean 配置資源的定位 Resource[] configResources = getConfigResources(); if (configResources != null) { //Xml Bean 讀取器調用其父類 AbstractBeanDefinitionReader 讀取定位的 Bean 配置資源 reader.loadBeanDefinitions(configResources); }// 如果子類中獲取的 Bean 配置資源定位為空,則獲取 ClassPathXmlApplicationContext // 構造方法中 setConfigLocations 方法設置的資源 String[] configLocations = getConfigLocations(); if (configLocations != null) { //Xml Bean 讀取器調用其父類 AbstractBeanDefinitionReader 讀取定位 //的 Bean 配置資源 reader.loadBeanDefinitions(configLocations); } }//這里又使用了一個委托模式,調用子類的獲取 Bean 配置資源定位的方法 //該方法在 ClassPathXmlApplicationContext 中進行實現,對于我們 //舉例分析源碼的 ClassPathXmlApplicationContext 沒有使用該方法 @Nullable protected Resource[] getConfigResources() { return null; } }以 XmlBean 讀取器的其中一種策略 XmlBeanDefinitionReader 為例。
XmlBeanDefinitionReader 調用其父類AbstractBeanDefinitionReader的 reader.loadBeanDefinitions()方法讀取Bean配置資源。
由于我們使用 ClassPathXmlApplicationContext 作為例子分析,因此 getConfigResources 的返回值為 null,因此程序執行 reader.loadBeanDefinitions(configLocations)分支。
6、分配路徑處理策略
在 XmlBeanDefinitionReader 的抽象父類 AbstractBeanDefinitionReader 中定義了載入過程。
AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源碼如下:
//重載方法,調用下面的 loadBeanDefinitions(String, Set<Resource>);方法 @Override public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); } public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { //獲取在 IOC 容器初始化過程中設置的資源加載器 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); }if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //將指定位置的 Bean 配置信息解析為 Spring IOC 容器封裝的資源 //加載多個指定位置的 Bean 配置信息 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); //委派調用其子類 XmlBeanDefinitionReader 的方法,實現加載功能 int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } }if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); }return loadCount; }catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } }else { // Can only load single resources by absolute URL. //將指定位置的 Bean 配置信息解析為 Spring IOC 容器封裝的資源 //加載單個指定位置的 Bean 配置信息 Resource resource = resourceLoader.getResource(location); //委派調用其子類 XmlBeanDefinitionReader 的方法,實現加載功能 int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); }if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); }return loadCount; } }//重載方法,調用 loadBeanDefinitions(String); @Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int counter = 0; for (String location : locations) { counter += loadBeanDefinitions(location); }return counter; }AbstractRefreshableConfigApplicationContext 的 loadBeanDefinitions(Resource...resources) 方法實際上是調用 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法。
從對 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源碼分析可以看出該方法就做了兩件事:
首先,調用資源加載器的獲取資源方法 resourceLoader.getResource(location),獲取到要加載的資源。
其次,真正執行加載功能是其子類 XmlBeanDefinitionReader 的 loadBeanDefinitions()方法。
在loadBeanDefinitions()方法中調用了 AbstractApplicationContext 的 getResources()方法,跟進去之后發現 getResources()方法其實定義在 ResourcePatternResolver 中,此時,我們有必要來看一下ResourcePatternResolver 的全類圖:
從上面可以看到 ResourceLoader 與 ApplicationContext 的繼承關系,可以看出其實際調用的是DefaultResourceLoader中的getSource() 方法 定位Resource。
因 為 ClassPathXmlApplicationContext 本身就是 DefaultResourceLoader 的實現類,所以此時又回到了 ClassPathXmlApplicationContext 中來。
7、解析配置文件路徑
XmlBeanDefinitionReader通 過 調 用ClassPathXmlApplicationContext的父類 DefaultResourceLoader 的 getResource()方法獲取要加載的資源,其源碼如下 :
//獲取 Resource 的具體實現方法 @Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } }//如果是類路徑的方式,那需要使用 ClassPathResource 來得到 bean 文件的資源對象 if (location.startsWith("/")) { return getResourceByPath(location); }else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); }else { try { // 如果是 URL 方式,使用 UrlResource 作為 bean 文件的資源對象 URL url = new URL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); }catch (MalformedURLException ex) { //如果既不是 classpath 標識,又不是 URL 標識的 Resource 定位,則調用 //容器本身的 getResourceByPath 方法獲取 Resource return getResourceByPath(location); } } }DefaultResourceLoader 提供了 getResourceByPath()方法的實現,就是為了處理既不是 classpath標識,又不是 URL 標識的 Resource 定位這種情況。
protected Resource getResourceByPath(String path) { return new ClassPathContextResource(path, getClassLoader()); }在 ClassPathResource 中完成了對整個路徑的解析。這樣,就可以從類路徑上對 IOC 配置文件進行加載,當然我們可以按照這個邏輯從任何地方加載,在 Spring 中我們看到它提供的各種資源抽象,比如 ClassPathResource、URLResource、FileSystemResource 等來供我們使用。
上面我們看到的是定位Resource 的一個過程,而這只是加載過程的一部分。例如 FileSystemXmlApplication 容器就重寫了getResourceByPath()方法:
@Override protected Resource getResourceByPath(String path) { if (path.startsWith("/")) { path = path.substring(1); }//這里使用文件系統資源對象來定義 bean 文件 return new FileSystemResource(path); }通過子類的覆蓋,巧妙地完成了將類路徑變為文件路徑的轉換。
8、開始讀取配置內容
繼續回到 XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …)方法看到代表 bean 文件的資源定義以后的載入過程。
//XmlBeanDefinitionReader 加載資源的入口方法 @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { //將讀入的 XML 資源進行特殊編碼處理 return loadBeanDefinitions(new EncodedResource(resource)); } //這里是載入 XML 形式 Bean 配置信息方法 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { ... try { //將資源文件轉為 InputStream 的 IO 流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { //從 InputStream 中得到 XML 的解析源 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); }//這里是具體的讀取過程 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); }finally { //關閉從 Resource 中得到的 IO 流 inputStream.close(); } }... } //從特定 XML 文件中實際載入 Bean 配置資源的方法 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //將 XML 文件轉換為 DOM 對象,解析過程由 documentLoader 實現 Document doc = doLoadDocument(inputSource, resource); //這里是啟動對 Bean 定義解析的詳細過程,該解析過程會用到 Spring 的 Bean 配置規則 return registerBeanDefinitions(doc, resource); }... }通過源碼分析,載入 Bean 配置信息的最后一步是將 Bean 配置信息轉換為 Document 對象,該過程由documentLoader()方法實現。
9、準備文檔對象
DocumentLoader 將 Bean 配置資源轉換成 Document 對象的源碼如下:
//使用標準的 JAXP 將載入的 Bean 配置資源轉換成 document 對象 @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //創建文件解析器工廠 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); }//創建文檔解析器 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); //解析 Spring 的 Bean 配置資源 return builder.parse(inputSource); }protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { //創建文檔解析工廠 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); //設置解析 XML 的校驗 if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { factory.setValidating(true); if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { // Enforce namespace aware for XSD... factory.setNamespaceAware(true); try { factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); }catch (IllegalArgumentException ex) { ParserConfigurationException pcex = new ParserConfigurationException( "Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " + "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); pcex.initCause(ex); throw pcex; } } }return factory; }上面的解析過程是調用 JavaEE 標準的 JAXP 標準進行處理。
至此 Spring IOC 容器根據定位的 Bean 配置信息,將其加載讀入并轉換成為 Document 對象過程完成。
接下來我們要繼續分析 Spring IOC 容器將載入的 Bean 配置信息轉換為 Document 對象之后,是如何將其解析為 Spring IOC 管理的 Bean 對象并將其注冊到容器中的。
10、分配解析策略
XmlBeanDefinitionReader 類中的 doLoadBeanDefinition()方法是從特定 XML 文件中實際載入Bean 配置資源的方法,該方法在載入 Bean 配置資源之后將其轉換為 Document 對象。
接下來調用registerBeanDefinitions()啟動Spring IOC容器對Bean定 的解析過程 , registerBeanDefinitions()方法源碼如下:
//按照 Spring 的 Bean 語義要求將 Bean 配置資源解析并轉換為容器內部數據結構 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //得到 BeanDefinitionDocumentReader 來對 xml 格式的 BeanDefinition 解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //獲得容器中注冊的 Bean 數量 int countBefore = getRegistry().getBeanDefinitionCount(); //解析過程入口,這里使用了委派模式,BeanDefinitionDocumentReader 只是個接口, //具體的解析實現過程有實現類 DefaultBeanDefinitionDocumentReader 完成 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //統計解析的 Bean 數量 return getRegistry().getBeanDefinitionCount() - countBefore; }Bean 配置資源的載入解析分為以下兩個過程:
首先,通過調用 XML 解析器將 Bean 配置信息轉換得到 Document 對象,但是這些 Document 對象并沒有按照 Spring 的 Bean 規則進行解析。
這一步是載入的過程 。
其次,在完成通用的 XML 解析之后,按照 Spring Bean 的定義規則對 Document 對象進行解析,其解析過程是在接口BeanDefinitionDocumentReader的實現類DefaultBeanDefinitionDocumentReader 中實現。
11、將配置載入內存
BeanDefinitionDocumentReader 接口通過 registerBeanDefinitions()方法調用其實現類DefaultBeanDefinitionDocumentReader 對 Document 對象進行解析,解析的代碼如下:
通過上述 Spring IOC 容器對載入的 Bean 定義 Document 解析可以看出,我們使用 Spring 時,在Spring 配置文件中可以使用<import>元素來導入 IOC 容器所需要的其他資源,Spring IOC 容器在解析時會首先將指定導入的資源加載進容器中。
使用<ailas>別名時,Spring IOC 容器首先將別名元素所定義的別名注冊到容器中。
對于既不是<import>元素,又不是<alias>元素的元素,即 Spring 配置文件中普通的<bean>元素的解析由 BeanDefinitionParserDelegate 類的parseBeanDefinitionElement()方法來實現。
這個解析的過程非常復雜。
12、載入<bean>元素
Bean 配置信息中的<import>和<alias>元素解析在DefaultBeanDefinitionDocumentReader 中已經完成,對 Bean 配置信息中使用最多的<bean>元素交由 BeanDefinitionParserDelegate來解析, 其解析實現的源碼如下:
只要使用過 Spring,對 Spring 配置文件比較熟悉的人,通過對上述源碼的分析,就會明白我們在 Spring配置文件中<Bean>元素的中配置的屬性就是通過該方法解析和設置到 Bean 中去的。
注意:在解析<Bean>元素過程中沒有創建和實例化 Bean 對象,只是創建了 Bean 對象的定義類BeanDefinition,將<Bean>元素中的配置信息設置到 BeanDefinition 中作為記錄,當依賴注入時才使用這些記錄信息創建和實例化具體的 Bean 對象。
上面方法中一些對一些配置如元信息(meta)、qualifier 等的解析,我們在 Spring 中配置時使用的也不多,我們在使用 Spring 的<Bean>元素時,配置最多的是<property>屬性,因此我們下面繼續分析源碼,了解 Bean 的屬性在解析時是如何設置的。
13、載入<property>元素
BeanDefinitionParserDelegate 在解析<Bean>調用 parsePropertyElements()方法解析<Bean>元素中的<property>屬性子元素,解析源碼如下:
通過對上述源碼的分析,我們可以了解在 Spring 配置文件中,<Bean>元素中<property>元素的相關配置是如何處理的:
1、ref 被封裝為指向依賴對象一個引用。
2、value 配置都會封裝成一個字符串類型的對象。
3、ref 和 value 都通過“解析的數據類型屬性值.setSource(extractSource(ele));”方法將屬性值/引用與所引用的屬性關聯起來。
在方法的最后對于<property>元素的子元素通過 parsePropertySubElement ()方法解析,我們繼續分析該方法的源碼,了解其解析過程。
14、載入<property>的子元素
在 BeanDefinitionParserDelegate 類中的 parsePropertySubElement()方法對<property>中的子元素解析,源碼如下:
通過上述源碼分析,我們明白了在 Spring 配置文件中,對<property>元素中配置的 array、list、set、map、prop 等各種集合子元素的都通過上述方法解析,生成對應的數據對象,比如 ManagedList、 ManagedArray、ManagedSet 等,這些 Managed 類是 Spring 對象 BeanDefiniton 的數據封裝,對集合數據類型的具體解析有各自的解析方法實現,解析方法的命名非常規范,一目了然,我們對<list>集合元素的解析方法進行源碼分析,了解其實現過程。
15、載入<list>的子元素
在 BeanDefinitionParserDelegate 類中的 parseListElement()方法就是具體實現解析<property>元素中的<list>集合子元素,源碼如下:
經過對 Spring Bean 配置信息轉換的 Document 對象中的元素層層解析,Spring IOC 現在已經將 XML形式定義的 Bean 配置信息轉換為 Spring IOC 所識別的數據結構——BeanDefinition,它是 Bean 配置信息中配置的 POJO 對象在 Spring IOC 容器中的映射,我們可以通過 AbstractBeanDefinition 為入口,看到了 IOC 容器進行索引、查詢和操作。
通過 Spring IOC 容器對 Bean 配置資源的解析后,IOC 容器大致完成了管理 Bean 對象的準備工作,即初始化過程,但是最為重要的依賴注入還沒有發生,現在在 IOC 容器中 BeanDefinition 存儲的只是一些靜態信息,接下來需要向容器注冊 Bean 定義信息才能全部完成 IOC 容器的初始化過程
16、分配注冊策略
讓我們繼續跟蹤程序的執行順序,接下來我們來分析DefaultBeanDefinitionDocumentReader 對Bean 定 義轉 換的 Document 對 象解 析的 流程 中, 在其 parseDefaultElement() 方法中完成對Document 對 象 的 解 析 后 得 到 封 裝 BeanDefinition的BeanDefinitionHold 對象 , 然后調用BeanDefinitionReaderUtils 的 registerBeanDefinition()方向IOC容器注冊解析的Bean , BeanDefinitionReaderUtils 的注冊的源碼如下:
當調用 BeanDefinitionReaderUtils 向 IOC 容器注冊解析的 BeanDefinition 時,真正完成注冊功能的是DefaultListableBeanFactory。
17、向容器注冊
DefaultListableBeanFactory 中 使 用 一 個 HashMap 的 集 合 對 象 存 放 IOC 容 器 中 注 冊解析的 BeanDefinition,向 IOC 容器注冊的主要源碼如下:
至此,Bean 配置信息中配置的 Bean 被解析過后,已經注冊到 IOC 容器中,被容器管理起來,真正完成了 IOC 容器初始化所做的全部工作。現在 IOC 容器中已經建立了整個 Bean 的配置信息,這些BeanDefinition 信息已經可以使用,并且可以被檢索,IOC 容器的作用就是對這些注冊的 Bean 定義信息進行處理和維護。這些的注冊的 Bean 定義信息是 IOC 容器控制反轉的基礎,正是有了這些注冊的數據,容器才可以進行依賴注入。
?
總結
以上是生活随笔為你收集整理的从源码深处体验Spring核心技术--基于Xml的IOC容器的初始化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从源码深处体验Spring核心技术--I
- 下一篇: 从源码深处体验Spring核心技术--基