咱們知道Spring的IoC起到了一個容器的做用,其中裝得都是各類各樣的Bean。同時在咱們剛剛開始學習Spring的時候都是經過xml文件來定義Bean,Spring會某種方式加載這些xml文件,而後根據這些信息綁定整個系統的對象,最終組裝成一個可用的基於輕量級容器的應用系統。html
Spring IoC容器總體能夠劃分爲兩個階段,容器啓動階段,Bean實例化階段。其中容器啓動階段主要包括加載配置信息、解析配置信息,裝備到BeanDefinition中以及其餘後置處理,而Bean實例化階段主要包括實例化對象,裝配依賴,生命週期管理已經註冊回調。下面LZ先介紹容器的啓動階段的第一步,即定位配置文件。編程
咱們使用編程方式使用DefaultListableBeanFactory時,首先是須要定義一個Resource來定位容器使用的BeanDefinition。設計模式
ClassPathResource resource = new ClassPathResource("bean.xml");
經過這個代碼,就意味着Spring會在類路徑中去尋找bean.xml並解析爲BeanDefinition信息。固然這個Resource並不能直接被使用,他須要被BeanDefinitionReader進行解析處理(這是後面的內容了)。架構
對於各類applicationContext,如FileSystemXmlApplicationContext、ClassPathXmlApplicationContext等等,咱們從這些類名就能夠看到他們提供了那些Resource的讀入功能。下面咱們以FileSystemXmlApplicationContext爲例來闡述Spring IoC容器的Resource定位。app
先看FileSystemXmlApplicationContext繼承體系結構:函數
從圖中能夠看出FileSystemXMLApplicationContext繼承了DefaultResourceLoader,具有了Resource定義的BeanDefinition的能力,其源代碼以下:學習
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { /** * 默認構造函數 */ public FileSystemXmlApplicationContext() { } public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); } public FileSystemXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); } public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, null); } //核心構造器 public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } //經過構造一個FileSystemResource對象來獲得一個在文件系統中定位的BeanDefinition //採用模板方法設計模式,具體的實現用子類來完成 protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
AbstractApplicationContext中的refresh()方法是IoC容器初始化的入口,也就是說IoC容器的初始化是經過refresh()方法來完成整個調用過程的。在覈心構造器中就對refresh進行調用,經過它來啓動IoC容器的初始化工做。getResourceByPath爲一個模板方法,經過構造一個FileSystemResource對象來獲得一個在文件系統中定位的BeanDEfinition。getResourceByPath的調用關係以下(部分):this
refresh爲初始化IoC容器的入口,可是具體的資源定位仍是在XmlBeanDefinitionReader讀入BeanDefinition時完成,loadBeanDefinitions() 加載BeanDefinition的載入。url
protected final void refreshBeanFactory() throws BeansException { //判斷是否已經建立了BeanFactory,若是建立了則銷燬關閉該BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //建立DefaultListableBeanFactory實例對象 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); //加載BeanDefinition信息 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
DefaultListableBeanFactory做爲BeanFactory默認實現類,其重要性不言而喻,而createBeanFactory()則返回該實例對象。spa
protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); }
loadBeanDefinition方法加載BeanDefinition信息,BeanDefinition就是在這裏定義的。AbstractRefreshableApplicationContext對loadBeanDefinitions僅僅只是定義了一個抽象的方法,真正的實現類爲其子類AbstractXmlApplicationContext來實現:
AbstractRefreshableApplicationContext:
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;
AbstractXmlApplicationContext:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //建立bean的讀取器(Reader),即XmlBeanDefinitionReader,並經過回調設置到容器中 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // beanDefinitionReader.setEnvironment(getEnvironment()); //爲Bean讀取器設置Spring資源加載器 beanDefinitionReader.setResourceLoader(this); //爲Bean讀取器設置SAX xml解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // initBeanDefinitionReader(beanDefinitionReader); //Bean讀取器真正實現的地方 loadBeanDefinitions(beanDefinitionReader); }
程序首先首先建立一個Reader,在前面就提到過,每一類資源都對應着一個BeanDefinitionReader,BeanDefinitionReader提供統一的轉換規則;而後設置Reader,最後調用loadBeanDefinition,該loadBeanDefinition纔是讀取器真正實現的地方:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //獲取Bean定義資源的定位 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } //獲取Bean定義資源的路徑。在FileSystemXMLApplicationContext中經過setConfigLocations能夠配置Bean資源定位的路徑 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
首先經過getConfigResources()獲取Bean定義的資源定位,若是不爲null則調用loadBeanDefinitions方法來讀取Bean定義資源的定位。
loadBeanDefinitions是中的方法:
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; }
繼續:
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
再繼續:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { //獲取ResourceLoader資源加載器 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } // if (resourceLoader instanceof ResourcePatternResolver) { try { //調用DefaultResourceLoader的getResourceByPath完成具體的Resource定位 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 { //調用DefaultResourceLoader的getResourceByPath完成具體的Resource定位 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; } }
在這段源代碼中經過調用DefaultResourceLoader的getResource方法:
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith("/")) { return getResourceByPath(location); } //處理帶有classPath標識的Resource else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { //處理URL資源 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { return getResourceByPath(location); } } }
在getResource方法中咱們能夠清晰地看到Resource資源的定位。這裏能夠清晰地看到getResourceByPath方法的調用,getResourceByPath方法的具體實現有子類來完成,在FileSystemXmlApplicationContext實現以下:
protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
這樣代碼就回到了博客開初的FileSystemXmlApplicationContext 中來了,它提供了FileSystemResource 來完成從文件系統獲得配置文件的資源定義。固然這僅僅只是Spring IoC容器定位資源的一種邏輯,咱們能夠根據這個步驟來查看Spring提供的各類資源的定位,如ClassPathResource、URLResource等等。下圖是ResourceLoader的繼承關係:
這裏就差很少分析了Spring IoC容器初始化過程資源的定位,在BeanDefinition定位完成的基礎上,就能夠經過返回的Resource對象來進行BeanDefinition的載入、解析了。
下篇博客將探索Spring IoC容器初始化過程的解析,Spring Resource體系結構會在後面詳細講解。
參考文獻
一、《Spring技術內幕 深刻解析Spring架構與設計原理》--第二版