Spring源碼分析--Ioc容器定位解析資源文件並註冊BeanDefinition

一、資源文件定位和解析流程

加載和解析資源文件是在ApplicationContext子類調用refresh()方法時執行的,整個過程就是將資源文件讀入到內存中並且解析成Spring Bean對應的數據結構(BeanDefinition)。以ClassPathXmlApplicatinContext爲例整個調用流程如下圖:


二、解析流程詳細說明

1AbstractApplicationContext 調用refresh()方法(前面已經有對refresh方法的分析),方法內通過

ConfigurableListableBeanFactorybeanFactory =obtainFreshBeanFactory();方法進入到BeanFactory的刷新和創建。

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
			//這個方法會進入到loadBeanDefinitions的調用
			refreshBeanFactory();
			ConfigurableListableBeanFactory beanFactory = getBeanFactory();
			if (logger.isDebugEnabled()) {
				logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
			}
			return beanFactory;
		}

2AbstractApplicationContext子類實現refreshBeanFactory()方法。如AbstractRefreshableApplicationContext類的實現:

	protected final void refreshBeanFactory() throws BeansException {
			if (hasBeanFactory()) {
				destroyBeans();
				closeBeanFactory();
			}
			try {
				//創建BeanFactory
				DefaultListableBeanFactory beanFactory = createBeanFactory();
				beanFactory.setSerializationId(getId());
				//個性BeanFactory
				customizeBeanFactory(beanFactory);
				//這次分析的正主來了,加載BeanDefinitions
				loadBeanDefinitions(beanFactory);
				synchronized (this.beanFactoryMonitor) {
					this.beanFactory = beanFactory;
				}
			}
			catch (IOException ex) {
				throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
			}

}

3AbstractApplicationContext調用loadBeanDefinitions(DefaultListableBeanFactory beanFactory) ,此方法根據首先創建XmlBeanDefinitionReader對象,然後配置該對象的上下文和資源加載環境,同時調用子類實現的initBeanDefinitionReaderXmlBeanDefinitionReader進行個性化配置,最近後入到initBeanDefinitionReader(beanDefinitionReader)的調用

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
			//根據給定的BeanFactory創建XmlBeanDefinitionReader 對象
			XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
	
			// 配置beanDefinitionReader的上下文和資源加載環境
			beanDefinitionReader.setEnvironment(this.getEnvironment());
			beanDefinitionReader.setResourceLoader(this);
			beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
	
			// 調用子類實現的initBeanDefinitionReader對XmlBeanDefinitionReader進行個性化配置initBeanDefinitionReader(beanDefinitionReader);
			
			//調用載入Bean定義的方法,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器
			loadBeanDefinitions(beanDefinitionReader);
		}

4AbstractApplicationContext調用loadBeanDefinitions(beanDefinitionReader),這個方法是取得資源或資源路徑然後通過傳入的reader去加載BeanDefinitions

	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);
			}
		}

5BeanDefinitionReader loadBeanDefinitions()方法調用,BeanDefinitionReader子類重載了loadBeanDefinitions()方法,其最終的目的就是通過Resource或者Location讀取輸入流轉換成xml Document進行解析。


6BeanDefinitionReader 讀取到輸入流後就要開始解析了,看看XmlBeanDefinitionReader.doLoadBeanDefinitions()都幹了些啥

	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
				throws BeanDefinitionStoreException {
			try {
				//文件解析成Doc
				Document doc = doLoadDocument(inputSource, resource);
				//啓動資源解析的詳細過程
				return registerBeanDefinitions(doc, resource);
			}
			……//省略的都是異常捕獲了
	}

7XmlBeanDefinitionReader.registerBeanDefinitions()代碼可以看出,XmlBeanDefinitionsReader說我把Xml解析成Doc了,Doc解析我就委託給BeanDefinitionDocumentReader來進行解析了。
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
			BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
			documentReader.setEnvironment(getEnvironment());
			int countBefore = getRegistry().getBeanDefinitionCount();
			documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
			return getRegistry().getBeanDefinitionCount() - countBefore;
		}

8BeanDefinitionDocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));一看DocReader竟然方法和XmlReader方法一樣,其實這是委派模式的一種體現(Spring Ioc容器資源文件解析和BeanDefinition加載是到處可見委派模式的使用啊),其實真正做事的DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)方法

	protected void doRegisterBeanDefinitions(Element root) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
					return;
				}
			}
			//創建委派類,DefaultBeanDefinitionDocumentReader表示他只能解析Spring Beans 模塊xsd文件說明的元素,要解析其他的還得委派給其他對象
			BeanDefinitionParserDelegate parent = this.delegate;
			this.delegate = createDelegate(this.readerContext, root, parent);
			
			//留個子類實現的方法,個性化用
			preProcessXml(root);
			//解析資源
			parseBeanDefinitions(root, this.delegate);
			postProcessXml(root);
	
			this.delegate = parent;
		}

9parseBeanDefinitions(Elementroot, BeanDefinitionParserDelegate delegate)實現具體的解析,到這就是真刀真槍要把一個個Doc元素給解析成BeanDefinition

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
			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)) {
							parseDefaultElement(ele, delegate);
						}
						else {
							delegate.parseCustomElement(ele);
						}
					}
				}
			}
			else {
				delegate.parseCustomElement(root);
			}
		}
	

三、解析具體實現

1BeanDefinition解析流程已經到了解析每個元素的時候,元素解析首先是判斷命名空間,如果是默認命名空間的元素(即:http://www.springframework.org/schema/beans)則直接解析,默認命名空間能解析的是Beanimportaliasbeans代碼如下:
	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);
			}

}

2、默認命名空間只能解析默認的元素,那麼如果配置了事務,配置了註解掃描這些該如何處理,平時用Spring這些照樣是得加載出來的,BeanDefinitionParserDelegate.parseCustomElement()就是來幹這些的

	public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
			//取得命名空間
			String namespaceUri = getNamespaceURI(ele);
			//取得命名空間對應的handler
			NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
			if (handler == null) {
				error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
				return null;
			}
			//handler解析對應命名空間的元素,讓handler飛吧
			return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
		}

handler我用context對應的命名空間(http://www.springframework.org/schema/context)來分析,其他的肯定原理是一樣的,只是內部做的具體事不一樣。對應的handlerorg.springframework.context.config.ContextNamespaceHandler

3ContextNamespaceHandler.parse()方法,原來它也是找對應的Parser幫手來解析

	public BeanDefinition parse(Element element, ParserContext parserContext) {
			//找到對應的Parser然後解析
			return findParserForElement(element, parserContext).parse(element, parserContext);
		}
	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		String localName = parserContext.getDelegate().getLocalName(element);
		//這個命名空間可以看看都用那些解析器
		BeanDefinitionParser parser = this.parsers.get(localName);
		if (parser == null) {
			parserContext.getReaderContext().fatal(
					"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
		}
		return parser;
	}

context的解析器是這些,

{property-override=org.springframework.context.config.PropertyOverrideBeanDefinitionParser,//屬性重載

 annotation-config=org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser,//註解解析

 mbean-server=org.springframework.context.config.MBeanServerBeanDefinitionParser,//元數據解析

 component-scan=org.springframework.context.annotation.ComponentScanBeanDefinitionParser,//組建掃描

 load-time-weaver=org.springframework.context.config.LoadTimeWeaverBeanDefinitionParser,

 property-placeholder=org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser,

 spring-configured=org.springframework.context.config.SpringConfiguredBeanDefinitionParser,

 mbean-export=org.springframework.context.config.MBeanExportBeanDefinitionParser}

這裏看一個通常配置比較多的component-scan 上面所有的都在Spring Beans模塊下,AnnotationConfigBeanDefinitionParser就進入了Spring context模塊了,AnnotationConfigBeanDefinitionParser就是context 模塊對應的Annotation包底下支持註解解析的類,它的parse()方法的代碼如下:

	public BeanDefinition parse(Element element, ParserContext parserContext) {
			//找到要掃描的包
			String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
	
			// 真正進行bean掃描並且註冊beanDefinitions
			ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
			Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
			registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
			
			//返回是空,這個可能是Spring Bean設計的遺留問題導致的,Spring Bean解析一個元素的方法都是返回單個BeanDefinition ,掃描是能夠掃描到多個的(具體看配置),但這除了使得AbstractBeanDefinitionReader. loadBeanDefinitions(String... locations)的返回值跟真實解析到的BeanDefinition個數不一致外沒其他影響。BeanDefinition 在調用registerComponents()方法就註冊到傳到的改方法的上下文了
			return null;
		}






發佈了25 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章