IoC容器是什麼?
IoC文英全稱Inversion of Control,即控制反轉,我麼能夠這麼理解IoC容器:
「把某些業務對象的的控制權交給一個平臺或者框架來同一管理,這個同一管理的平臺能夠稱爲IoC容器。」java
咱們剛開始學習spring的時候會常常看到的相似下面的這代碼:node
ApplicationContext appContext = new ClassPathXmlApplicationContext("cjj/models/beans.xml"); Person p = (Person)appContext.getBean("person");
上面代碼中,在建立ApplicationContext實例對象過程當中會建立一個spring容器,該容器會讀取配置文件"cjj/models/beans.xml",並統一管理由該文件中定義好的全部bean實例對象,若是要獲取某個bean實例,使用getBean方法就好了。例如咱們只須要將Person提早配置在beans.xml文件中(能夠理解爲注入),以後咱們能夠不需使用new Person()的方式建立實例,而是經過容器來獲取Person實例,這就至關於將Person的控制權交由spring容器了,差很少這就是控制反轉的概念。web
那在建立IoC容器時經歷了哪些呢?爲此,先來了解下Spring中IoC容器分類,繼而根據一個具體的容器來說解IoC容器初始化的過程。
Spring中有兩個主要的容器系列:spring
ApplicationContext比較複雜,它不但繼承了BeanFactory的大部分屬性,還繼承其它可擴展接口,擴展的了許多高級的屬性,其接口定義以下:數組
public interface ApplicationContext extends EnvironmentCapable,
ListableBeanFactory, //繼承於BeanFactory
HierarchicalBeanFactory,//繼承於BeanFactory MessageSource, //
ApplicationEventPublisher,//
ResourcePatternResolver //繼承ResourceLoader,用於獲取resource對象
在BeanFactory子類中有一個DefaultListableBeanFactory類,它包含了基本Spirng IoC容器所具備的重要功能,開發時不管是使用BeanFactory系列仍是ApplicationContext系列來建立容器基本都會使用到DefaultListableBeanFactory類,能夠這麼說,在spring中實際上把它當成默認的IoC容器來使用。下文在源碼實例分析時你將會看到這個類。網絡
(注:文章有點長,須要細心閱讀,不一樣版本的spring中源碼可能不一樣,但邏輯幾乎是同樣的,若是能夠建議仍是看源碼 ^_^)數據結構
回到本文正題上來,關於Spirng IoC容器的初始化過程在《Spirng技術內幕:深刻解析Spring架構與設計原理》一書中有明確的指出,IoC容器的初始化過程能夠分爲三步:架構
Resource是Sping中用於封裝I/O操做的接口。正如前面所見,在建立spring容器時,一般要訪問XML配置文件,除此以外還能夠經過訪問文件類型、二進制流等方式訪問資源,還有當須要網絡上的資源時能夠經過訪問URL,Spring把這些文件統稱爲Resource,Resource的體系結構以下:app
經常使用的resource資源類型以下:
FileSystemResource:以文件的絕對路徑方式進行訪問資源,效果相似於Java中的File;
ClassPathResourcee:以類路徑的方式訪問資源,效果相似於this.getClass().getResource("/").getPath();
ServletContextResource:web應用根目錄的方式訪問資源,效果相似於request.getServletContext().getRealPath("");
UrlResource:訪問網絡資源的實現類。例如file: http: ftp:等前綴的資源對象;
ByteArrayResource: 訪問字節數組資源的實現類。框架
那如何獲取上圖中對應的各類Resource對象呢?
Spring提供了ResourceLoader接口用於實現不一樣的Resource加載策略,該接口的實例對象中能夠獲取一個resource對象,也就是說將不一樣Resource實例的建立交給ResourceLoader的實現類來處理。ResourceLoader接口中只定義了兩個方法:
Resource getResource(String location); //經過提供的資源location參數獲取Resource實例
ClassLoader getClassLoader(); // 獲取ClassLoader,經過ClassLoader可將資源載入JVM
注:ApplicationContext的全部實現類都實現RecourceLoader接口,所以能夠直接調用getResource(參數)獲取Resoure對象。不一樣的ApplicatonContext實現類使用getResource方法取得的資源類型不一樣,例如:FileSystemXmlApplicationContext.getResource獲取的就是FileSystemResource實例;ClassPathXmlApplicationContext.gerResource獲取的就是ClassPathResource實例;XmlWebApplicationContext.getResource獲取的就是ServletContextResource實例,另外像不須要經過xml直接使用註解@Configuation方式加載資源的AnnotationConfigApplicationContext等等。
在資源定位過程完成之後,就爲資源文件中的bean的載入創造了I/O操做的條件,如何讀取資源中的數據將會在下一步介紹的BeanDefinition的載入過程當中描述。
1.什麼是BeanDefinition? BeanDefinition與Resource的聯繫呢?
官方文檔中對BeanDefinition的解釋以下:
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
它們之間的聯繫從官方文檔描述的一句話:Load bean definitions from the specified resource中可見一斑。
/** * Load bean definitions from the specified resource. * @param resource the resource descriptor * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
總之,BeanDefinition至關於一個數據結構,這個數據結構的生成過程是根據定位的resource資源對象中的bean而來的,這些bean在Spirng IoC容器內部表示成了的BeanDefintion這樣的數據結構,IoC容器對bean的管理和依賴注入的實現都是經過操做BeanDefinition來進行的。
2.如何將BeanDefinition載入到容器?
在Spring中配置文件主要格式是XML,對於用來讀取XML型資源文件來進行初始化的IoC 容器而言,該類容器會使用到AbstractXmlApplicationContext類,該類定義了一個名爲loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用於獲取BeanDefinition:
// 該方法屬於AbstractXmlApplicationContect類 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); this.initBeanDefinitionReader(beanDefinitionReader); // 用於獲取BeanDefinition this.loadBeanDefinitions(beanDefinitionReader); }
此方法在具體執行過程當中首先會new一個與容器對應的BeanDefinitionReader型實例對象,而後將生成的BeanDefintionReader實例做爲參數傳入loadBeanDefintions(XmlBeanDefinitionReader),繼續往下執行載入BeanDefintion的過程。例如AbstractXmlApplicationContext有兩個實現類:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext,這些容器在調用此方法時會建立一個XmlBeanDefinitionReader類對象專門用來載入全部的BeanDefinition。
下面以XmlBeanDefinitionReader對象載入BeanDefinition爲例,使用源碼說明載入BeanDefinition的過程:
// 該方法屬於AbstractXmlApplicationContect類
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources();//獲取全部定位到的resource資源位置(用戶定義) if (configResources != null) { reader.loadBeanDefinitions(configResources);//載入resources } String[] configLocations = getConfigLocations();//獲取全部本地配置文件的位置(容器自身) if (configLocations != null) { reader.loadBeanDefinitions(configLocations);//載入resources } }
經過上面代碼將用戶定義的資源以及容器自己須要的資源所有加載到reader中,reader.loadBeanDefinitions方法的源碼以下:
// 該方法屬於AbstractBeanDefinitionReader類, 父接口BeanDefinitionReader @Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { // 將全部資源所有加載,交給AbstractBeanDefinitionReader的實現子類處理這些resource counter += loadBeanDefinitions(resource); } return counter; }
BeanDefinitionReader接口定義了 int loadBeanDefinitions(Resource resource)方法:
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
XmlBeanDefinitionReader 類實現了BeanDefinitionReader接口中的loadBeanDefinitions(Resource)方法,其繼承關係如上圖所示。XmlBeanDefinitionReader類中幾主要對加載的全部resource開始進行處理,大體過程是,先將resource包裝爲EncodeResource類型,而後處理,爲生成BeanDefinition對象作準備,其主要幾個方法的源碼以下:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { // 包裝resource爲EncodeResource類型 return loadBeanDefinitions(new EncodedResource(resource)); } // 加載包裝後的EncodeResource資源 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } try { // 經過resource對象獲得XML文件內容輸入流,併爲IO的InputSource作準備 InputStream inputStream = encodedResource.getResource().getInputStream(); try { // Create a new input source with a byte stream. InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 開始準備 load bean definitions from the specified XML file return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } } protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 獲取指定資源的驗證模式 int validationMode = getValidationModeForResource(resource); // 從資源對象中加載DocumentL對象,大體過程爲:將resource資源文件的內容讀入到document中 // DocumentLoader在容器讀取XML文件過程當中有着舉足輕重的做用! // XmlBeanDefinitionReader實例化時會建立一個DefaultDocumentLoader型的私有屬性,繼而調用loadDocument方法 // inputSource--要加載的文檔的輸入源 Document doc = this.documentLoader.loadDocument( inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware); // 將document文件的bean封裝成BeanDefinition,並註冊到容器 return registerBeanDefinitions(doc, resource); } catch ...(略) }
DefaultDocumentLoader大體瞭解便可,感興趣可繼續深究,其源碼以下:(看完收起,便於閱讀下文)
/* Simply loads documents using the standard JAXP-configured XML parser. */ public class DefaultDocumentLoader implements DocumentLoader { //JAXP解析XML的三種基本接口爲:文檔型的DOM接口,簡單型的SAX接口以及XML流接口 private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; protected final Log logger = LogFactory.getLog(getClass()); public Document loadDocument( InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //建立DocumentBuilder工場 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } // DOM解析器被稱做DocumentBuilder,經過工場的方式獲取DocumentBuilder實例對象 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource);//把文件或解析流轉化成Document對象 } protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); if (validationMode != XmlBeanDefinitionReader.VALIDATION_NONE) { factory.setValidating(true); //是否使用jaxp接口提供的XML Schema驗證 if (validationMode == XmlBeanDefinitionReader.VALIDATION_XSD) { factory.setNamespaceAware(true); try { factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); } catch (IllegalArgumentException ex) { throw new BeanDefinitionStoreException( "Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.4 or below with " + "Apache Crimson? Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); } } } return factory; } protected DocumentBuilder createDocumentBuilder( DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler) throws ParserConfigurationException { DocumentBuilder docBuilder = factory.newDocumentBuilder(); if (errorHandler != null) { docBuilder.setErrorHandler(errorHandler); } if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); } return docBuilder; } }
上面代碼分析到了registerBeanDefinitions(doc, resource)這一步,也就是準備將Document中的Bean按照Spring bean語義進行解析並轉化爲BeanDefinition類型,這個方法的具體過程以下:
/** * 屬於XmlBeanDefinitionReader類 * Register the bean definitions contained in the given DOM document. * @param doc the DOM document * @param resource * @return the number of bean definitions found * @throws BeanDefinitionStoreException */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 獲取到DefaultBeanDefinitionDocumentReader實例 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 獲取容器中bean的數量 int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
經過 XmlBeanDefinitionReader 類中的私有屬性 documentReaderClass 能夠得到一個 DefaultBeanDefinitionDocumentReader 實例對象:
private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
DefaultBeanDefinitionDocumentReader實現了BeanDefinitionDocumentReader接口,它的registerBeanDefinitions方法定義以下:
// DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); // 獲取doc的root節點,經過該節點可以訪問全部的子節點 Element root = doc.getDocumentElement(); // 處理beanDefinition的過程委託給BeanDefinitionParserDelegate實例對象來完成 BeanDefinitionParserDelegate delegate = createHelper(readerContext, root); // Default implementation is empty. // Subclasses can override this method to convert custom elements into standard Spring bean definitions preProcessXml(root); // 核心方法,代理 parseBeanDefinitions(root, delegate); postProcessXml(root); }
上面出現的BeanDefinitionParserDelegate類很是很是重要(須要瞭解代理技術,如JDK動態代理、cglib動態代理等)。Spirng BeanDefinition的解析就是在這個代理類下完成的,此類包含了各類對符合Spring Bean語義規則的處理,好比<bean></bean>、<import></import>、<alias><alias/>等的檢測。
parseBeanDefinitions(root, delegate)方法以下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); // 遍歷全部節點,作對應解析工做 // 如遍歷到<import>標籤節點就調用importBeanDefinitionResource(ele)方法對應處理 // 遍歷到<bean>標籤就調用processBeanDefinition(ele,delegate)方法對應處理 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)) { parseDefaultElement(ele, delegate); } else { //對應用戶自定義節點處理方法 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // 解析<import>標籤 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // 解析<alias>標籤 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // 解析<bean>標籤,最經常使用,過程最複雜 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // 解析<beans>標籤 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
這裏針對經常使用的<bean>標籤中的方法作簡單介紹,其餘標籤的加載方式相似:
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 該對象持有beanDefinition的name和alias,可使用該對象完成beanDefinition向容器的註冊 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 註冊最終被修飾的bean實例,下文註冊beanDefinition到容器會講解該方法 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
parseBeanDefinitionElement(Element ele)方法會調用parseBeanDefinitionElement(ele, null)方法,並將值返回BeanDefinitionHolder類對象,這個方法將會對給定的<bean>標籤進行解析,若是在解析<bean>標籤的過程當中出現錯誤則返回null。
須要強調一下的是parseBeanDefinitionElement(ele, null)方法中產生了一個抽象類型的BeanDefinition實例,這也是咱們首次看到直接定義BeanDefinition的地方,這個方法裏面會將<bean>標籤中的內容解析到BeanDefinition中,以後再對BeanDefinition進行包裝,將它與beanName,Alias等封裝到BeanDefinitionHolder 對象中,該部分源碼以下:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); } public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); ...(略) String beanName = id; ...(略) // 從上面按過程走來,首次看到直接定義BeanDefinition !!! // 該方法會對<bean>節點以及其全部子節點如<property>、<List>、<Set>等作出解析,具體過程本文不作分析(太多太長) AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { ...(略) } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
最終Bean配置會被解析成BeanDefinition並與beanName,Alias一同封裝到BeanDefinitionHolder類中, 以後beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),註冊到DefaultListableBeanFactory.beanDefinitionMap中。以後客戶端若是要獲取Bean對象,Spring容器會根據註冊的BeanDefinition信息進行實例化。
BeanDefinitionReaderUtils類:
public static void registerBeanDefinition( BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException { // Register bean definition under primary name. String beanName = bdHolder.getBeanName();
// 註冊beanDefinition!!! beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()); // 若是有別名的話也註冊進去,Register aliases for bean name, if any. String[] aliases = bdHolder.getAliases(); if (aliases != null) { for (int i = 0; i < aliases.length; i++) { beanFactory.registerAlias(beanName, aliases[i]); } } }
DefaultListableBeanFactory實現了上面調用BeanDefinitionRegistry接口的 registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,這一部分的主要邏輯是向DefaultListableBeanFactory對象的beanDefinitionMap中存放beanDefinition,當初始化容器進行bean初始化時,在bean的生命週期分析裏必然會在這個beanDefinitionMap中獲取beanDefition實例,有機會成文分析一下bean的生命週期,到時能夠分析一下如何使用這個beanDefinitionMap。
registerBeanDefinition( beanName, bdHolder.getBeanDefinition() )方法具體方法以下:
/** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "Bean definition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } }
// beanDefinitionMap是個ConcurrentHashMap類型數據,用於存放beanDefinition,它的key值是beanName Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { if (!this.allowBeanDefinitionOverriding) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': there's already [" + oldBeanDefinition + "] bound"); } else { if (logger.isInfoEnabled()) { logger.info("Overriding bean definition for bean '" + beanName + "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } } else { this.beanDefinitionNames.add(beanName); }
// 將獲取到的BeanDefinition放入Map中,容器操做使用bean時經過這個HashMap找到具體的BeanDefinition this.beanDefinitionMap.put(beanName, beanDefinition);
// Remove corresponding bean from singleton cache, if any. // Shouldn't usually be necessary, rather just meant for overriding // a context's default beans (e.g. the default StaticMessageSource // in a StaticApplicationContext). removeSingleton(beanName); }
本文可看作本人看書筆記學習,其中大部分參考《Spirng技術內幕:深刻解析Spring架構與設計原理》以及結合網上博客等倉促而做,做此文只但願本身的技術不斷提升,同時記錄本身學習過程當中的點點滴滴,其中確定有許多不足之處,望諒解與指出。