之前我們大概了解 Spring 中關(guān)于 IoC 容器的設(shè)計與應(yīng)用。接下來我們就要從源代碼出發(fā)机隙,詳細了解 Spring IoC 容器的實現(xiàn)枝冀。
IoC 容器的初始化左冬。
簡單來說招狸,IoC 容器的初始化是由前面的 refersh()方法來啟動的京腥,這個方法標(biāo)志是 IoC 容器的正式啟動被冒。具體來說古今, IoC 容器的啟動包括 BeanDefinition 的 Resource 資源定位惶楼、載入和注冊三個基本過程砌创,spring 中將這三個步驟完全解耦,便于我們可能的對著三個過程進行裁剪或擴展鲫懒,定制自己的 IoC 容器初始化過程嫩实。
上一篇文章我們已經(jīng)介紹過編程式使用 IoC 容器,里面就涉及到了 Resource 定位和載入過程接口的調(diào)用窥岩。在下面的內(nèi)容中甲献,我們將以 ApplicationContext 為例,詳細分析這三個過程的實現(xiàn)颂翼。
-
Resource 資源定位
我們在上一文提到的編程式使用 DefaultListableBeanFactory晃洒,首先定義一個 Resouce 來定位容器使用的 BeanDefinition慨灭,這里使用的 ClassPathResouce,意味著 Spring 會在類路徑中去尋找以文件形式存在的 BeanDefinition 信息球及。這里的 Resouce 并不能直接由 DefaultListableBeanFactory 使用氧骤,而是交由 BeanDefinitionReader 對這些信息進行處理。
下面我們繼續(xù)回到 FileSystemXMLApplication吃引。- 首先來看一下該類的類結(jié)構(gòu)圖:
從圖中可以看到筹陵,通過繼承 AbstractXmlApplicationContext,其已經(jīng)具備了 ResourceLoader 讀入 Resource 定義的 BeanDefinition 的能力镊尺。 - 接著回到 FileSystemXMLApplication 源碼中朦佩,其中最主要關(guān)注的就跟我們上一篇文章將的兩個地方:
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } @Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
- 首先我們可以看到構(gòu)造方法中調(diào)用了 setConfiLocations(configLocations) 這個方法,不難猜想出其應(yīng)該是在父類中實現(xiàn)了的設(shè)置 xml 路徑的方法庐氮,那么既然有設(shè)置语稠,必然就會有獲取,我們可以到實現(xiàn)這個方法的父類 AbstractRefreshableConfigApplicationContext 里面看一下弄砍。
- 果然如我們所猜想仙畦,里面有 getConfigLocations() 方法。該方法必然會在 IoC 容器初始化的過程中被調(diào)用音婶,我們查找一下該方法被調(diào)用的地方议泵。在 AbstractXmlApplicationContext 的 loadBeanDefinitions 方法里面:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
- 到了這里我們發(fā)現(xiàn)了一點端倪,該方法調(diào)用之后的步驟桃熄,跟我們編程式使用 DefaultListableBeanFactory 一樣先口,都是首先定義了一個 BeanDefinitionReader,這里使用的是 XmlBeanDefinitionReader瞳收,然后調(diào)用其 loadBeanDefinitions 方法進行處理碉京。接下來我們就進入該方法里面看一下。其最終追蹤到的實現(xiàn)代碼如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { 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 { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 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. Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }
- 就跟我們前面說過的螟深,ApplicationContext 擴展了 ResourcePatternResolver 接口谐宙,所以我們在上面的代碼可以看到兩種不同的 getResource 的方法,我們這里先忽略 ResourcePatternResolver 的實現(xiàn)方式界弧,看一下通過 DefaulResouceLoader 的實現(xiàn)過程:
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
- 前面我們看到凡蜻,getResourceByPath 方法會被我們的例子子類 FileSystemXmlApplicationContext 實現(xiàn),這份方法返回的是一個 FileSystemResource 對象垢箕,通過這個對象划栓,Spring 可以進行 I/O 的操作,完成 BeanDefinition 的定位条获。分析到這里已經(jīng)一目了然了忠荞。它實現(xiàn)的就是對 path 進行解析,然后生成一個 FileSystemResource 對象并返回。
- 首先我們可以看到構(gòu)造方法中調(diào)用了 setConfiLocations(configLocations) 這個方法,不難猜想出其應(yīng)該是在父類中實現(xiàn)了的設(shè)置 xml 路徑的方法庐氮,那么既然有設(shè)置语稠,必然就會有獲取,我們可以到實現(xiàn)這個方法的父類 AbstractRefreshableConfigApplicationContext 里面看一下弄砍。
- 如果是其他的 ApplicationContext委煤,那么對生成其他種類的 Resource堂油,比如 ClassPathResource、ServletContextResource 等碧绞。作為接口的 Resource 定義了許多與 I/O 操作府框,這些操作對后面的 BeanDefinition 提供了服務(wù)。關(guān)于 Resource 的種類讥邻,我們可以從其繼承關(guān)系中看到:
我們上面通過 FileSystemXmlApplicationContext 為例子了解了 Resource 定位過程迫靖。但是具體的 BeanDefinition 的數(shù)據(jù)還沒有開始讀入,這些數(shù)據(jù)的讀入我們將在下一篇文章介紹的 BeanDefinition 的載入和解析來完成计维。
- 首先來看一下該類的類結(jié)構(gòu)圖: