日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

从源码深处体验Spring核心技术--基于Xml的IOC容器的初始化

發(fā)布時間:2024/4/13 javascript 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从源码深处体验Spring核心技术--基于Xml的IOC容器的初始化 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

IOC 容器的初始化包括?BeanDefinition?的?Resource 定位加載注冊這三個基本的過程。

我們以ApplicationContext 為例講解,ApplicationContext 系列容器也許是我們最熟悉的,因為 Web 項目中使用的 XmlWebApplicationContext 就屬于這個繼承體系,還有 ClasspathXmlApplicationContext 等,其繼承體系如下圖所示:

ApplicationContext 允許上下文嵌套,通過保持父上下文可以維持一個上下文體系。

對于 Bean 的查找可以在這個上下文體系中發(fā)生,首先檢查當(dāng)前上下文,其次是父上下文,逐級向上,這樣為不同的 Spring應(yīng)用提供了一個共享的 Bean 定義環(huán)境。

1、尋找入口

還有一個我們用的比較多的 ClassPathXmlApplicationContext,通過 main()方法啟動:

ApplicationContext app = new ClassPathXmlApplicationContext("application.xml");

先看其構(gòu)造函數(shù)的調(diào)用:

public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[]{configLocation}, true, (ApplicationContext)null); }

其實際調(diào)用的構(gòu)造函數(shù)為:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); this.setConfigLocations(configLocations); if(refresh) { this.refresh(); } }

還 有 像 AnnotationConfigApplicationContext 、 FileSystemXmlApplicationContext 、 XmlWebApplicationContext 等都繼承自父容器 AbstractApplicationContext主要用到了裝飾器模式 和策略模式,最終都是調(diào)用 refresh()方法。

2、獲得配置路徑

通 過 分 析 ClassPathXmlApplicationContext 的 源 代 碼 可 以 知 道 , 在 創(chuàng) 建 ClassPathXmlApplicationContext 容器時,構(gòu)造方法做以下兩項重要工作:

首先,調(diào)用父類容器的構(gòu)造方法(super(parent)方法)為容器設(shè)置好 Bean 資源加載器。

然后,再調(diào)用父類AbstractRefreshableConfigApplicationContext 的 setConfigLocations(configLocations)方法設(shè)置 Bean 配置信息的定位路徑。

通 過 追 蹤 ClassPathXmlApplicationContext 的 繼 承 體 系 , 發(fā) 現(xiàn) 其 父 類 的 父 類 AbstractApplicationContext 中初始化 IOC 容器所做的主要源碼如下:

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { //靜態(tài)初始化塊,在整個容器創(chuàng)建過程中只執(zhí)行一次 static { //為了避免應(yīng)用程序在 Weblogic8.1 關(guān)閉時出現(xiàn)類加載異常加載問題,加載 IOC 容 //器關(guān)閉事件(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 的默認(rèn)構(gòu)造方法中有調(diào)用 PathMatchingResourcePatternResolver 的 構(gòu)造方法創(chuàng)建 Spring 資源加載器:

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); //設(shè)置 Spring 的資源加載器 this.resourceLoader = resourceLoader; }

在設(shè)置容器的資源加載器之后,接下來 ClassPathXmlApplicationContext 執(zhí)行setConfigLocations()方法通過調(diào)用其父類AbstractRefreshableConfigApplicationContext的方法進(jìn)行對Bean配置信息的定位,該方法的源碼如下:

//處理單個資源文件路徑為一個字符串的情況 public void setConfigLocation(String location) { //String CONFIG_LOCATION_DELIMITERS = ",; /t/n"; //即多個資源文件路徑之間用” ,; \t\n”分隔,解析成數(shù)組形式 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 定義資源文件的路徑,處理多個資源文件字符串?dāng)?shù)組 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 配置信息,也可以使用字符串?dāng)?shù)組,即下面兩種方式都是可以的:

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()函數(shù)開始的,refresh()是一個模板方法,規(guī)定了IOC 容 器 的 啟 動 流 程 , 有 些 邏 輯 要 交 給 其 子 類 去 實 現(xiàn) 。

它 對 Bean 配 置 資 源 進(jìn) 行 載 入 ClassPathXmlApplicationContext 通過調(diào)用其父類 AbstractApplicationContext 的 refresh()函數(shù)啟動整個 IOC 容器對 Bean 定義的載入過程,現(xiàn)在我們來詳細(xì)看看 refresh()中的邏輯處理:

@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //1、調(diào)用容器準(zhǔn)備刷新的方法,獲取容器的當(dāng)時時間,同時給容器設(shè)置同步標(biāo)識 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、調(diào)用所有注冊的 BeanFactoryPostProcessor 的 Bean invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //6、為 BeanFactory 注冊 BeanPost 事件處理器. //BeanPostProcessor 是 Bean 后置處理器,用于監(jiān)聽容器觸發(fā)的事件 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. //7、初始化信息源,和國際化相關(guān). initMessageSource(); // Initialize event multicaster for this context. //8、初始化容器事件傳播器. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //9、調(diào)用子類的某些特殊 Bean 初始化方法 onRefresh(); // Check for listener beans and register them. //10、為事件傳播器注冊事件監(jiān)聽器.registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //11、初始化所有剩余的單例 Bean finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. //12、初始化容器的生命周期事件處理器,并發(fā)布容器的生命周期事件 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、銷毀已創(chuàng)建的 Bean destroyBeans(); // Reset 'active' flag. //14、取消 refresh 操作,重置容器的同步標(biāo)識. 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、重設(shè)公共緩存 resetCommonCaches(); } } }

refresh()方法主要為 IOC 容器 Bean 的生命周期管理提供條件,Spring IOC 容器載入 Bean 配置信息從 其 子 類 容 器 的 refreshBeanFactory() 方 法 啟 動 。

所 以 整 個 refresh() 中 “ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();”

這句以后代碼的 都是注冊容器的信息源和生命周期事件,我們前面說的載入就是從這句代碼開始啟動。

refresh()方法的主要作用是:在創(chuàng)建 IOC 容器前,如果已經(jīng)有容器存在,則需要把已有的容器銷毀和關(guān)閉,以保證在 refresh 之后使用的是新建立起來的 IOC 容器。

它類似于對 IOC 容器的重啟,在新建立好的容器中對容器進(jìn)行初始化,對 Bean 配置資源進(jìn)行載入。

4、創(chuàng)建容器

obtainFreshBeanFactory()方法調(diào)用子類容器的 refreshBeanFactory()方法,啟動容器載入 Bean 配置

信息的過程,代碼如下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //這里使用了委派設(shè)計模式,父類定義了抽象的 refreshBeanFactory()方法,具體實現(xiàn)調(diào)用子類容器的 refreshBeanFactory()方 法 refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); }return beanFactory; }

AbstractApplicationContext 類中只抽象定義了 refreshBeanFactory()方法,容器真正調(diào)用的是其子類 AbstractRefreshableApplicationContext 實現(xiàn)的 refreshBeanFactory()方法,方法的源碼如下:

protected final void refreshBeanFactory() throws BeansException { //如果已經(jīng)有容器,銷毀容器中的 bean,關(guān)閉容器 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); }try { //創(chuàng)建 IOC 容器 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); //對 IOC 容器進(jìn)行定制化,如設(shè)置啟動參數(shù),開啟注解的自動裝配等 customizeBeanFactory(beanFactory); //調(diào)用載入 Bean 定義的方法,主要這里又使用了一個委派模式,在當(dāng)前類中只定義了抽象的 loadBeanDefinitions 方法,具體的實現(xiàn)調(diào)用子類容器 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 并關(guān)閉beanFactory,接著 創(chuàng)建 DefaultListableBeanFactory,并調(diào)用loadBeanDefinitions(beanFactory)裝載 bean 定義。

5、載入配置路徑

AbstractRefreshableApplicationContext 中只定義了抽象的 loadBeanDefinitions 方法,容器真正調(diào)用的是其子類 AbstractXmlApplicationContext 對該方法的實現(xiàn),AbstractXmlApplicationContext 的主要源碼如下:

loadBeanDefinitions() 方 法 同 樣 是 抽 象 方 法 , 是 由 其 子 類 實 現(xiàn) 的 , 也 即 在 AbstractXmlApplicationContext 中。

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { ... //實現(xiàn)父類抽象的載入 Bean 定義方法 @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //創(chuàng)建 XmlBeanDefinitionReader,即創(chuàng)建 Bean 讀取器,并通過回調(diào)設(shè)置到容器中去,容器使用該讀取器讀取 Bean 配置資源 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //為 Bean 讀取器設(shè)置 Spring 資源加載器,AbstractXmlApplicationContext 的 //祖先父類 AbstractApplicationContext 繼承 DefaultResourceLoader,因此,容器本身也是一個資源加載器 beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); //為 Bean 讀取器設(shè)置 SAX xml 解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); //當(dāng) Bean 讀取器讀取 Bean 定義的 Xml 資源文件時,啟用 Xml 的校驗機制 initBeanDefinitionReader(beanDefinitionReader); //Bean 讀取器真正實現(xiàn)加載的方法 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 讀取器調(diào)用其父類 AbstractBeanDefinitionReader 讀取定位的 Bean 配置資源 reader.loadBeanDefinitions(configResources); }// 如果子類中獲取的 Bean 配置資源定位為空,則獲取 ClassPathXmlApplicationContext // 構(gòu)造方法中 setConfigLocations 方法設(shè)置的資源 String[] configLocations = getConfigLocations(); if (configLocations != null) { //Xml Bean 讀取器調(diào)用其父類 AbstractBeanDefinitionReader 讀取定位 //的 Bean 配置資源 reader.loadBeanDefinitions(configLocations); } }//這里又使用了一個委托模式,調(diào)用子類的獲取 Bean 配置資源定位的方法 //該方法在 ClassPathXmlApplicationContext 中進(jìn)行實現(xiàn),對于我們 //舉例分析源碼的 ClassPathXmlApplicationContext 沒有使用該方法 @Nullable protected Resource[] getConfigResources() { return null; } }

以 XmlBean 讀取器的其中一種策略 XmlBeanDefinitionReader 為例。

XmlBeanDefinitionReader 調(diào)用其父類AbstractBeanDefinitionReader的 reader.loadBeanDefinitions()方法讀取Bean配置資源。

由于我們使用 ClassPathXmlApplicationContext 作為例子分析,因此 getConfigResources 的返回值為 null,因此程序執(zhí)行 reader.loadBeanDefinitions(configLocations)分支。

6、分配路徑處理策略

在 XmlBeanDefinitionReader 的抽象父類 AbstractBeanDefinitionReader 中定義了載入過程。

AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源碼如下:

//重載方法,調(diào)用下面的 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 容器初始化過程中設(shè)置的資源加載器 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); //委派調(diào)用其子類 XmlBeanDefinitionReader 的方法,實現(xiàn)加載功能 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); //委派調(diào)用其子類 XmlBeanDefinitionReader 的方法,實現(xiàn)加載功能 int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); }if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); }return loadCount; } }//重載方法,調(diào)用 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) 方法實際上是調(diào)用 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法。

從對 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源碼分析可以看出該方法就做了兩件事:

首先,調(diào)用資源加載器的獲取資源方法 resourceLoader.getResource(location),獲取到要加載的資源。

其次,真正執(zhí)行加載功能是其子類 XmlBeanDefinitionReader 的 loadBeanDefinitions()方法。

在loadBeanDefinitions()方法中調(diào)用了 AbstractApplicationContext 的 getResources()方法,跟進(jìn)去之后發(fā)現(xiàn) getResources()方法其實定義在 ResourcePatternResolver 中,此時,我們有必要來看一下ResourcePatternResolver 的全類圖:

從上面可以看到 ResourceLoader 與 ApplicationContext 的繼承關(guān)系,可以看出其實際調(diào)用的是DefaultResourceLoader中的getSource() 方法 定位Resource。

因 為 ClassPathXmlApplicationContext 本身就是 DefaultResourceLoader 的實現(xiàn)類,所以此時又回到了 ClassPathXmlApplicationContext 中來。

7、解析配置文件路徑

XmlBeanDefinitionReader通 過 調(diào) 用ClassPathXmlApplicationContext的父類 DefaultResourceLoader 的 getResource()方法獲取要加載的資源,其源碼如下 :

//獲取 Resource 的具體實現(xiàn)方法 @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 標(biāo)識,又不是 URL 標(biāo)識的 Resource 定位,則調(diào)用 //容器本身的 getResourceByPath 方法獲取 Resource return getResourceByPath(location); } } }

DefaultResourceLoader 提供了 getResourceByPath()方法的實現(xiàn),就是為了處理既不是 classpath標(biāo)識,又不是 URL 標(biāo)識的 Resource 定位這種情況。

protected Resource getResourceByPath(String path) { return new ClassPathContextResource(path, getClassLoader()); }

在 ClassPathResource 中完成了對整個路徑的解析。這樣,就可以從類路徑上對 IOC 配置文件進(jìn)行加載,當(dāng)然我們可以按照這個邏輯從任何地方加載,在 Spring 中我們看到它提供的各種資源抽象,比如 ClassPathResource、URLResource、FileSystemResource 等來供我們使用。

上面我們看到的是定位Resource 的一個過程,而這只是加載過程的一部分。例如 FileSystemXmlApplication 容器就重寫了getResourceByPath()方法:

@Override protected Resource getResourceByPath(String path) { if (path.startsWith("/")) { path = path.substring(1); }//這里使用文件系統(tǒng)資源對象來定義 bean 文件 return new FileSystemResource(path); }

通過子類的覆蓋,巧妙地完成了將類路徑變?yōu)槲募窂降霓D(zhuǎn)換。

8、開始讀取配置內(nèi)容

繼續(xù)回到 XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …)方法看到代表 bean 文件的資源定義以后的載入過程。

//XmlBeanDefinitionReader 加載資源的入口方法 @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { //將讀入的 XML 資源進(jìn)行特殊編碼處理 return loadBeanDefinitions(new EncodedResource(resource)); } //這里是載入 XML 形式 Bean 配置信息方法 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { ... try { //將資源文件轉(zhuǎn)為 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 { //關(guān)閉從 Resource 中得到的 IO 流 inputStream.close(); } }... } //從特定 XML 文件中實際載入 Bean 配置資源的方法 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //將 XML 文件轉(zhuǎn)換為 DOM 對象,解析過程由 documentLoader 實現(xiàn) Document doc = doLoadDocument(inputSource, resource); //這里是啟動對 Bean 定義解析的詳細(xì)過程,該解析過程會用到 Spring 的 Bean 配置規(guī)則 return registerBeanDefinitions(doc, resource); }... }

通過源碼分析,載入 Bean 配置信息的最后一步是將 Bean 配置信息轉(zhuǎn)換為 Document 對象,該過程由documentLoader()方法實現(xiàn)。

9、準(zhǔn)備文檔對象

DocumentLoader 將 Bean 配置資源轉(zhuǎn)換成 Document 對象的源碼如下:

//使用標(biāo)準(zhǔn)的 JAXP 將載入的 Bean 配置資源轉(zhuǎn)換成 document 對象 @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //創(chuàng)建文件解析器工廠 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); }//創(chuàng)建文檔解析器 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); //解析 Spring 的 Bean 配置資源 return builder.parse(inputSource); }protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { //創(chuàng)建文檔解析工廠 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); //設(shè)置解析 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; }

上面的解析過程是調(diào)用 JavaEE 標(biāo)準(zhǔn)的 JAXP 標(biāo)準(zhǔn)進(jìn)行處理。

至此 Spring IOC 容器根據(jù)定位的 Bean 配置信息,將其加載讀入并轉(zhuǎn)換成為 Document 對象過程完成。

接下來我們要繼續(xù)分析 Spring IOC 容器將載入的 Bean 配置信息轉(zhuǎn)換為 Document 對象之后,是如何將其解析為 Spring IOC 管理的 Bean 對象并將其注冊到容器中的。

10、分配解析策略

XmlBeanDefinitionReader 類中的 doLoadBeanDefinition()方法是從特定 XML 文件中實際載入Bean 配置資源的方法,該方法在載入 Bean 配置資源之后將其轉(zhuǎn)換為 Document 對象。

接下來調(diào)用registerBeanDefinitions()啟動Spring IOC容器對Bean定 的解析過程 , registerBeanDefinitions()方法源碼如下:

//按照 Spring 的 Bean 語義要求將 Bean 配置資源解析并轉(zhuǎn)換為容器內(nèi)部數(shù)據(jù)結(jié)構(gòu) public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //得到 BeanDefinitionDocumentReader 來對 xml 格式的 BeanDefinition 解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //獲得容器中注冊的 Bean 數(shù)量 int countBefore = getRegistry().getBeanDefinitionCount(); //解析過程入口,這里使用了委派模式,BeanDefinitionDocumentReader 只是個接口, //具體的解析實現(xiàn)過程有實現(xiàn)類 DefaultBeanDefinitionDocumentReader 完成 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //統(tǒng)計解析的 Bean 數(shù)量 return getRegistry().getBeanDefinitionCount() - countBefore; }

Bean 配置資源的載入解析分為以下兩個過程:

首先,通過調(diào)用 XML 解析器將 Bean 配置信息轉(zhuǎn)換得到 Document 對象,但是這些 Document 對象并沒有按照 Spring 的 Bean 規(guī)則進(jìn)行解析。

這一步是載入的過程 。

其次,在完成通用的 XML 解析之后,按照 Spring Bean 的定義規(guī)則對 Document 對象進(jìn)行解析,其解析過程是在接口BeanDefinitionDocumentReader的實現(xiàn)類DefaultBeanDefinitionDocumentReader 中實現(xiàn)。

11、將配置載入內(nèi)存

BeanDefinitionDocumentReader 接口通過 registerBeanDefinitions()方法調(diào)用其實現(xiàn)類DefaultBeanDefinitionDocumentReader 對 Document 對象進(jìn)行解析,解析的代碼如下:

通過上述 Spring IOC 容器對載入的 Bean 定義 Document 解析可以看出,我們使用 Spring 時,在Spring 配置文件中可以使用<import>元素來導(dǎo)入 IOC 容器所需要的其他資源,Spring IOC 容器在解析時會首先將指定導(dǎo)入的資源加載進(jìn)容器中。

使用<ailas>別名時,Spring IOC 容器首先將別名元素所定義的別名注冊到容器中。

對于既不是<import>元素,又不是<alias>元素的元素,即 Spring 配置文件中普通的<bean>元素的解析由 BeanDefinitionParserDelegate 類的parseBeanDefinitionElement()方法來實現(xiàn)。

這個解析的過程非常復(fù)雜。

12、載入<bean>元素

Bean 配置信息中的<import>和<alias>元素解析在DefaultBeanDefinitionDocumentReader 中已經(jīng)完成,對 Bean 配置信息中使用最多的<bean>元素交由 BeanDefinitionParserDelegate來解析, 其解析實現(xiàn)的源碼如下:

只要使用過 Spring,對 Spring 配置文件比較熟悉的人,通過對上述源碼的分析,就會明白我們在 Spring配置文件中<Bean>元素的中配置的屬性就是通過該方法解析和設(shè)置到 Bean 中去的。

注意:在解析<Bean>元素過程中沒有創(chuàng)建和實例化 Bean 對象,只是創(chuàng)建了 Bean 對象的定義類BeanDefinition,將<Bean>元素中的配置信息設(shè)置到 BeanDefinition 中作為記錄,當(dāng)依賴注入時才使用這些記錄信息創(chuàng)建和實例化具體的 Bean 對象。

上面方法中一些對一些配置如元信息(meta)、qualifier 等的解析,我們在 Spring 中配置時使用的也不多,我們在使用 Spring 的<Bean>元素時,配置最多的是<property>屬性,因此我們下面繼續(xù)分析源碼,了解 Bean 的屬性在解析時是如何設(shè)置的。

13、載入<property>元素

BeanDefinitionParserDelegate 在解析<Bean>調(diào)用 parsePropertyElements()方法解析<Bean>元素中的<property>屬性子元素,解析源碼如下:

通過對上述源碼的分析,我們可以了解在 Spring 配置文件中,<Bean>元素中<property>元素的相關(guān)配置是如何處理的:

1、ref 被封裝為指向依賴對象一個引用。

2、value 配置都會封裝成一個字符串類型的對象。

3、ref 和 value 都通過“解析的數(shù)據(jù)類型屬性值.setSource(extractSource(ele));”方法將屬性值/引用與所引用的屬性關(guān)聯(lián)起來。

在方法的最后對于<property>元素的子元素通過 parsePropertySubElement ()方法解析,我們繼續(xù)分析該方法的源碼,了解其解析過程。

14、載入<property>的子元素

在 BeanDefinitionParserDelegate 類中的 parsePropertySubElement()方法對<property>中的子元素解析,源碼如下:

通過上述源碼分析,我們明白了在 Spring 配置文件中,對<property>元素中配置的 array、list、set、map、prop 等各種集合子元素的都通過上述方法解析,生成對應(yīng)的數(shù)據(jù)對象,比如 ManagedList、 ManagedArray、ManagedSet 等,這些 Managed 類是 Spring 對象 BeanDefiniton 的數(shù)據(jù)封裝,對集合數(shù)據(jù)類型的具體解析有各自的解析方法實現(xiàn),解析方法的命名非常規(guī)范,一目了然,我們對<list>集合元素的解析方法進(jìn)行源碼分析,了解其實現(xiàn)過程。

15、載入<list>的子元素

在 BeanDefinitionParserDelegate 類中的 parseListElement()方法就是具體實現(xiàn)解析<property>元素中的<list>集合子元素,源碼如下:

經(jīng)過對 Spring Bean 配置信息轉(zhuǎn)換的 Document 對象中的元素層層解析,Spring IOC 現(xiàn)在已經(jīng)將 XML形式定義的 Bean 配置信息轉(zhuǎn)換為 Spring IOC 所識別的數(shù)據(jù)結(jié)構(gòu)——BeanDefinition,它是 Bean 配置信息中配置的 POJO 對象在 Spring IOC 容器中的映射,我們可以通過 AbstractBeanDefinition 為入口,看到了 IOC 容器進(jìn)行索引、查詢和操作。

通過 Spring IOC 容器對 Bean 配置資源的解析后,IOC 容器大致完成了管理 Bean 對象的準(zhǔn)備工作,即初始化過程,但是最為重要的依賴注入還沒有發(fā)生,現(xiàn)在在 IOC 容器中 BeanDefinition 存儲的只是一些靜態(tài)信息,接下來需要向容器注冊 Bean 定義信息才能全部完成 IOC 容器的初始化過程

16、分配注冊策略

讓我們繼續(xù)跟蹤程序的執(zhí)行順序,接下來我們來分析DefaultBeanDefinitionDocumentReader 對Bean 定 義轉(zhuǎn) 換的 Document 對 象解 析的 流程 中, 在其 parseDefaultElement() 方法中完成對Document 對 象 的 解 析 后 得 到 封 裝 BeanDefinition的BeanDefinitionHold 對象 , 然后調(diào)用BeanDefinitionReaderUtils 的 registerBeanDefinition()方向IOC容器注冊解析的Bean , BeanDefinitionReaderUtils 的注冊的源碼如下:

當(dāng)調(diào)用 BeanDefinitionReaderUtils 向 IOC 容器注冊解析的 BeanDefinition 時,真正完成注冊功能的是DefaultListableBeanFactory。

17、向容器注冊

DefaultListableBeanFactory 中 使 用 一 個 HashMap 的 集 合 對 象 存 放 IOC 容 器 中 注 冊解析的 BeanDefinition,向 IOC 容器注冊的主要源碼如下:

至此,Bean 配置信息中配置的 Bean 被解析過后,已經(jīng)注冊到 IOC 容器中,被容器管理起來,真正完成了 IOC 容器初始化所做的全部工作。現(xiàn)在 IOC 容器中已經(jīng)建立了整個 Bean 的配置信息,這些BeanDefinition 信息已經(jīng)可以使用,并且可以被檢索,IOC 容器的作用就是對這些注冊的 Bean 定義信息進(jìn)行處理和維護(hù)。這些的注冊的 Bean 定義信息是 IOC 容器控制反轉(zhuǎn)的基礎(chǔ),正是有了這些注冊的數(shù)據(jù),容器才可以進(jìn)行依賴注入。

?

總結(jié)

以上是生活随笔為你收集整理的从源码深处体验Spring核心技术--基于Xml的IOC容器的初始化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。