IOC容器就是具備依賴注入功能的容器,IOC容器負責實例化、定位、配置應用程序中的對象及創建這些對象之間的依賴。應用程序無需直接在代碼中new 相關的對象,應用程序由IOC容器進行組裝。在Spring中BeanFactory是IOC容器的實際表明者。node
由IOC容器管理的那些組成你應用程序的對象咱們就叫它Bean,Bean就是由Spring容器初始化、裝配及管理的對象。框架
Spring提供了兩種容器:BeanFactory和ApplicationContext。ide
BeanFactory:基礎類型IOC容器,提供完整的IoC服務,默認採用延遲初始化策略,也就是隻有客戶端對象須要訪問容器中的某個受管理對象的時候,纔對該受管理對象進行初始化以及依賴注入操做。工具
ApplicationContext:是在BeanFactory的基礎上構建,除了擁有BeanFactory的全部支持,還提供了其餘高級特性,例如事件發佈、國際化支持等。ApplicationContext所管理的對象,在容器啓動後默認所有初始化並綁定完成,因此相對於BeanFactory,它要求更多的資源。post
XML配置格式是Spring支持的最完整、功能最強大的配置方式。ui
Spring對其內部使用到的資源實現了本身的抽象結構:Resource接口來封裝底層資源。this
public interface InputStreamSource { InputStream getInputStream() throws IOException; }
InputStreamResource封裝任何返回InputStream的類,好比File、ClassPath下的資源和Byte Array等。編碼
public interface Resource extends InputStreamSource { boolean exists(); boolean isReadable(); boolean isOpen(); URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); }
Resource接口抽象了全部Spring內部使用到的底層資源:File、URL、classPath等。對不一樣來源的資源文件都有相應的Resource實現:文件(FileSystemResource)、ClassPath資源(ClassPathResource)、URL資源(URLResource)等。相關類圖以下:url
在平常的開發中,咱們能夠直接使用Spring提供的類來進行資源的加載,好比:spa
public class Test { public static void main(String[] args) throws IOException { BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("xmlBeanFactory.xml")); MyTestBean myTestBean=(MyTestBean) beanFactory.getBean("myTestBean"); myTestBean.fun(); //加載ClassPath資源 Resource resource=new ClassPathResource("test.txt"); InputStream inputStream=resource.getInputStream(); BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream)); System.out.println(reader.readLine()); //加載File文件資源 Resource resource=new FileSystemResource("D:\\fileResourceTest.txt"); InputStream inputStream=resource.getInputStream(); BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream)); String str; while((str=reader.readLine())!=null) System.out.println(str); } }
當經過Resource相關類完成了對配置文件封裝後,配置文件的讀取工做就全權交給XmlBeanDifinitionReader來處理了。XmlBeanDifinitionReader負責讀取Spring指定格式的XML配置文件並解析,以後將解析後的文件內容映射到相應的BeanDefinition,並加載到相應的BeanDefinitionRegistry中。這時,整個BeanFactory就能夠放給客戶端使用了。
整個資源的加載過程很複雜,參考下面的時序圖
整個流程大體要通過如下幾個步驟:
首先對傳入的Resource資源作封裝,目的是考慮到Resource可能存在編碼要求的狀況,其次,經過SAX讀取XML文件的方式來準備InputSource對象,最後將準備的數據經過參數傳入真正的核心處理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { /* * 加載XML文件,並獲得相應的、Document * 根據返回的Document註冊Bean信息 */ Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
在獲取Document的代碼中,執行下列代碼
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception
{ return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
DocumentLoader只是一個接口,這裏真正調用的是DefaultDocumentLoader。
@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); return builder.parse(inputSource); }
與SAX解析XML文檔的思路一致,這裏首先建立DocumentBuilderFactory,再經過DocumentBuilderFactory建立DocumentBuilder,進而解析InputSource來返回Document對象。DocumentLoader還涉及到驗證模式的讀取。
備註:
驗證模式,XML文件的驗證模式保證了XML文件的正確性,比較經常使用的有兩種,DTD和XSD。
DTD:文檔定義類型,屬於XML文件組成的一部分,是一種保證XML文檔格式正確的有效方法,能夠經過比較XML文檔和DTD文件來看文檔是否符合規範,元素和標籤是否正確。一個DTD文件包含:元素的定義規則,元素間關係的定義規則,元素可以使用的屬性,可以使用的實體或符號規則。
XSD:即XML schema。描述了XML文檔的結構,可使用一個指定的XML schema來驗證某個XML文檔,以堅持該XML文檔是否符合其要求。
Spring經過getValidationModeForResource方法來獲取對對應資源的驗證模式:
protected int getValidationModeForResource(Resource resource) { int validationModeToUse = getValidationMode(); //若是手動指定了驗證模式則使用指定的驗證模式 if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } //若是未指定,則使用自動檢測 int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } return VALIDATION_XSD; }
當把文件轉換爲Document後,接下來就是提取並註冊bean了,也就是執行registerBeanDefinitions(doc, resource)方法。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //將環境變量設置其中 documentReader.setEnvironment(getEnvironment()); //記錄統計前BeanDefinition的加載個數 int countBefore = getRegistry().getBeanDefinitionCount(); //加載和註冊bean documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //記錄本次加載的BeanDefinition的個數 return getRegistry().getBeanDefinitionCount() - countBefore; }
進入registerBeanDefinitions方法:
@Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
doRegisterBeanDefinitions()方法就是真正開始解析了。
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); // 處理profile屬性 if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //解析前處理,留給子類實現 preProcessXml(root); parseBeanDefinitions(root, this.delegate); //解析後處理,留給子類處理 postProcessXml(root); this.delegate = parent; }
處理了profile後就進行XML讀取了,跟蹤代碼進入parseBeanDefinitions方法。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //對beans的處理 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { //對bean的處理 parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
Spring爲基本的BeanFactory類型容器提供了XMLBeanFactory實現,相應地,它也爲ApplicationContext類型容器提供瞭如下幾個實現:
Spring框架內部使用Resource接口做爲全部資源的抽象和訪問接口,上面已有接觸,其中ClassPathResource就是Resource的一個特定類型的實現,表明位於Classpath中的資源。有了資源,還須要ResourceLoader去查找和定位這些資源。
ResourceLoader有一個默認的實現類DefaultResourceLoader,實現代碼以下:
@Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); 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 { // 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); } } }
處理邏輯以下:
ResourcePatternResolver是ResourceLoader的擴展,ResourceLoader每次只能根據資源路徑返回肯定的單個Resource實例,而ResourcePatternResolver則能夠根據指定的資源路徑匹配模式,每次返回多個Resource實例。
ApplicationContext繼承了ResourcePatternResolver,也就間接實現了ResourceLoader接口,這就是ApplicationContext支持Spring內統一資源加載策略的真相。
綜上,IOC容器會以某種方式加載Configuration Metadata(一般是XMl格式的配置信息),而後根據這些信息綁定整個系統的對象,最終組裝成一個可用的基於輕量級容器的應用系統。
基本上能夠分爲兩個階段,容器的啓動階段和Bean實例化階段。
一、容器啓動階段
容器啓動伊始,首先會經過某種途徑加載Configuration Metadata,大部分狀況下須要依賴某些工具類(BeanDefinitionReader)對加載的Configuration Metadata進行解析和分析,並將分析後的信息編組爲相應的BeanDefinition,最後把這些保存了bean定義必要信息的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器的啓動工做就完成了。
二、Bean實例化階段
通過第一階段,如今全部的bean定義信息都經過BeanDefinition的方式註冊到BeanDefinitionRegistry中,當某個請求方經過容器的getBean方法明確的請求某個對象,或者因依賴關係容器須要隱式的調用getBean方法時,就會觸發第二個階段的活動。
該階段,容器會首先檢查所請求的對象以前是否已經初始化,若是沒有,則會根據註冊的BeanDefinition所提供的信息實例化請求對象,併爲其注入依賴,若是該對象實現了某些回調接口,也會根據回調接口的要求來裝配它。當該對象裝配完成以後,容器就會當即將其返回請求方使用。