本文檔針對spring4.2.x版本 java
Spring IOC容器初始化的過程,分爲定位,載入解析以及註冊,接下來本文主要分析的是spring如何去解析Bean爲BeanDefinition對象的,這個只對xml聲明的Bean進行分析,對於經過spring註解掃描的方式之後再作分析 node
當spring定位到spring的xml文件之後,將xml讀做爲文件流的形式,做爲InputSource和Resource對象傳遞給文檔解析器進行解析,文檔解析的開始是XmlBeanDefinitionReader的doLoadBeanDefinitions方法。 spring
inputSource是SAX的InputSource, resource對象是對xml文件描述的一個對象 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { /** 傳遞inputSource對象進入doLoadDocument方法,此方法中設置了xml的版本,xml解析的模式等相關信息,而後input Source文件流讀做爲Document對象返回,把Document對象傳遞給registerBeanDefinitions方法,這個方法纔是真正的把Document對象解析爲BeanDefinition對象的具體實現 **/ Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch … }
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
先實例化出來一個BeanDefinitionDocumentReader,這個對象是Java經過本身封裝工具類BeanUtils.instantiateClass 經過反射的方式實例化出來的,而後記錄一下在註冊以前BeanDefinition中對象的個數,接着開始去解析document,spring自己設計很是靈活,BeanDefinitionDocumentReader 的registerBeanDefinitions方法是一個抽象方法,spring自身實現一個默認的BeanDefinitionDocumentReader的一個註冊器,DefaultBeanDefinitionDocumentReader,在這個子類中去實現具體的解析工做。 ide
@Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); } protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate對象描述了spring中bean節點中定義的所全部屬性和子節點 //全部的內嵌<beans>節點,都會被遞歸這個方法中經過遞歸的方式找到 //<beans>節點的默認屬性都會在此方法中設置, //保存當前對象(或者父對象)BeanDefinitionParserDelegate軌跡,可能爲空,可是會建立 //子類的屬性描述(BeanDefinitionParserDelegate)會持有父類的一個引用 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); 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; } } } //在xml解析的預處理,客戶端能夠本身定義一些本身的節點屬性,用以特殊的做用,或者加強,此方法spring默認實現爲空 preProcessXml(root); //把Document對象解析爲BeanDefinition對象 parseBeanDefinitions(root, this.delegate); //在xml解析爲BeanDefinition以後作一些後置處理,客戶端能夠在解析完xml以後,作一些本身的業務邏輯,目前spring的默認實現爲空 postProcessXml(root); this.delegate = parent; }
接下來分析parseBeanDefinition方法 工具
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //校驗是否是spring的默認命名空間,默認命名空間爲http://www.springframework.org/schema/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; //循環xml節點,看delegate對象是不是默認命名空間,若是是則按照默認命名空間來解析節點,不然則按照客戶自定義來解析 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
下面對parseDefaultElement 方法進行分析 post
此方法會查詢文檔中的import 標記,alias標記,以及bean和beans標記,根據這些標記進行分別匹配作相應的解析工做 this
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
一、 Import節點的解析 spa
首先要求import節點中必須有resource屬性,而後根據resource的值判斷是相對路徑仍是絕對路徑。 debug
而且對於resource中的佔位符進行解析,例如"${user.dir}" 解析爲真正的路徑 設計
而後調用XmlBeanDefinitionReader的loadBeanDefinitions方法,把import節點映射爲BeanDefinition對象,初始化了一個4個元素大小的LinkHashSet對象,準備好相關容器和資源,而後調用本類中的doLoadBeanDefinitions方法,作真正的映射工做 ,這是一個遞歸的過程,它須要把全部的import節點中的xml文件遞歸的找出來,而後作的均是bean節點的解析
二、 Alias節點解析
三、 Bean節點解析
使用list存儲bean信息
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
/** List of bean definition names, in registration order */
private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);
/** List of names of manually registered singletons, in registration order */
private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16);
把以上信息進行存儲完之後逐個轉化爲BeanDefinition的屬性
四、 Beans節點解析
最終轉化爲bean節點的解析