Spring源码-ICO-配置文件解析和注册过程分析


本篇主要学习和分析整个Spring-ICO容器初始化中的第一步,对XML配置文件进行加载和解析为BeanDefinition对象,并注册到内部的BeanFactory中。


 
重点包括:
(1).使用JAXP加载,验证和解析XML配置文件。
(2).学习Spring使用JAR文件中的META-INF中的配置文件(处理器映射文件),通过定义解析框架完成各个XML配置文件的解析。

  1. 通过 AbstractApplicationContext 类的模板方法refresh()开始整个加载和初始化过程。
  2. prepareRefresh();方法是容器的初始化,并记录启动时间。
  3. obtainFreshBeanFactory()内调用模板方法refreshBeanFactory()的子类的实现开始XML配置文件的解析和加载为BeanDefinitions(这个类是解析加载和ICO容器初始化的桥梁),这是整个ICO容器初始化的第一步。
  4. AbstractRefreshableApplicationContext.refreshBeanFactory()是模板方法的实现,如下:
	@Override
	protected final void refreshBeanFactory() throws BeansException {
		//同步方式判断是否有其他线程已经初始化了BeanFactory,如果有,则销毁。
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			// 生成一个内部的新的DefaultListableBeanFactory类
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			// 客户化参数设置:allowBeanDefinitionOverriding:允许覆写;allowCircularReferences:允许循环引用。
			customizeBeanFactory(beanFactory);
			// 重点:调用子类的实现,解析XML配置文件为BeanDefinition,并加入到beanFactory的集合参数中。
			loadBeanDefinitions(beanFactory);
			//同步方式把新加载的beanFactory赋值给容器的内部beanFactory
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

5. 对于模板方法loadBeanDefinitions(beanFactory)的实现,一般由具体的特性实现类实现,比较常用的实现是 AbstractXmlApplicationContext(基于XML配置文件的程序,包括常用的基于Classpath和Filesystem加载XML的实现:ClassPathXmlApplicationContext和FileSystemXmlApplicationContext),XmlWebApplicationContext(WEB项目直接配置)和AnnotationConfigWebApplicationContext(包扫描方式),这里以AbstractXmlApplicationContext为例进行分析

AbstractXmlApplicationContext:

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		// resourceLoader同于加载配置文件为Resource
		beanDefinitionReader.setResourceLoader(this);
		// 设置用于XML配置文件验证的实体分解器,后续或介绍
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		// 调用XmlBeanDefinitionReader的loadBeanDefinitions方法开始加载配置文件
		loadBeanDefinitions(beanDefinitionReader);
	}

根据代码可以看出,配置文件的加载是托管给 XmlBeanDefinitionReader 来实现的。

6. XmlBeanDefinitionReader是 BeanDefinitionReader接口的实现,该接口定义使用ResourceLoader加载配置文件,然后使用解析文件,并通过BeanDefinitionRegistry注册到beanFactory中。在XmlBeanDefinitionReader实现中,loadBeanDefinitions(EncodedResource encodedResource)实现真正的加载处理。代码如下:

		public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
			……
			try {
				// 获取配置文件输入流
				InputStream inputStream = encodedResource.getResource().getInputStream();
				try {
					// 构建JAXP的解析输入
					InputSource inputSource = new InputSource(inputStream);
					if (encodedResource.getEncoding() != null) {
						inputSource.setEncoding(encodedResource.getEncoding());
					}
					// 调用加载处理
					return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
				}
				finally {
					inputStream.close();
				}
			}
			……
		}
		
		protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
		  // 判断XML文档内定义的验证类型(DTD和XSD),其实就是逐行判断文档里面是否有“DOCTYPE”,有则是DTD,否则XSD
			int validationMode = getValidationModeForResource(resource);
			// 通过DefaultDocumentLoader,使用JAXP加载XML为w3c的Document
			Document doc = this.documentLoader.loadDocument(
					inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
			return registerBeanDefinitions(doc, resource);
		}
		……
	 }	

7. XML的加载是通过JAXP实现,并根据XML文档定义的验证方式对XML文档的语法进行合法性验证。这里使用EntityResolver(DelegatingEntityResolver)实现对文档验证实体的转换,可以自动实现转换http..形式的DTD和XSD文件到本地,对无互联网环境的进行支持。转换的mapping映射关系保存在各个Jar包的META-INF\spring.schema文件中;使用ErrorHandler(SimpleSaxErrorHandler)进行错误回调处理。关于JAXP的具体介绍请参考:JAXP使用及理解

http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
……
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		//调用内部方法创建JAXP的DocumentBuilderFactory,并设置打开名字空间支持和验证
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isDebugEnabled()) {
			logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		// 创建JAXP-DOM方式解析的DocumentBuilder
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		// JAXP 解析并返回解析结果Document
		return builder.parse(inputSource);
	}


	protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
			throws ParserConfigurationException {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(namespaceAware);

		if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
			factory.setValidating(true);

			if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
				// Enforce namespace aware for XSD...
				factory.setNamespaceAware(true);
				try {
					//注意:这里是设置XSD的样子采用W3C标准验证方式:http://www.w3.org/2001/XMLSchema
					factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
				}
				catch (IllegalArgumentException ex) {
					ParserConfigurationException pcex = new ParserConfigurationException(
							"Unable to validate using XSD: Your JAXP provider [" + factory +
							"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
							"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
					pcex.initCause(ex);
					throw pcex;
				}
			}
		}

		return factory;
	}
	
 
Java代码  
  1. protected DocumentBuilder createDocumentBuilder(  
  2.         DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)  
  3.         throws ParserConfigurationException {  
  4.   
  5.     DocumentBuilder docBuilder = factory.newDocumentBuilder();  
  6.     // 设置XML验证文件XSD的本地映射转换的实体分解器(http://www.springframework.org/schema/context/spring-context-3.0.xsd 转换为 包路径下org/springframework/context/config/spring-context-3.0.xsd)  
  7.     if (entityResolver != null) {  
  8.         docBuilder.setEntityResolver(entityResolver);  
  9.     }  
  10.     // 设置JAXP解析的错误处理回调  
  11.     if (errorHandler != null) {  
  12.         docBuilder.setErrorHandler(errorHandler);  
  13.     }  
  14.     return docBuilder;  
  15. }  

8. 回到第6步,我们在第7步使用JAXP解析并验证完成XML配置文件后得到的Document对象,现在可以用于注册BeanDefinition。在第6步的最后调用registerBeanDefinitions(doc, resource)完成注册。

整个解析过程托管给BeanDefinitionDocumentReader完成注册,代码如下:

 

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		// Read document based on new BeanDefinitionDocumentReader SPI.
		// 实际的实现类是:DefaultBeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		// 获取BeanFactory已经注册的BeanDefinition数量
		int countBefore = getRegistry().getBeanDefinitionCount();
		// 核心方法:通过BeanDefinitionDocumentReader注册doc中定义的bean到BeanFactory中
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		// 返回本次注册的BeanDefinition的数量=目前BeanFactory中的总数-countBefore 
		return getRegistry().getBeanDefinitionCount() - countBefore;
}
  

ReaderContext接口及XmlReaderContext

 代码中createReaderContext(resource)返回一个XmlReaderContext类,是解析和注册过程的会话类,该类中持有本次解析会话的实例包括:

 

  • Resrouce,错误处理ProblemReporterFailFastProblemReporter
  • 事件处理ReaderEventListenerEmptyReaderEventListener使用的是空实现),
  • 源抽取器SourceExtractorNullSourceExtractor空实现)
  • XmlBeanDefinitionReader(里面保存了BeanFactoryRegiester的实现DefaultListableBeanFactory,也就是ICO容器的内部beanFactory,注册的BeanDefenition就放在里面)
  • NamespaceHandlerResolverXML配置文件中的各种名字空间(如:context)定义的节点(如: context:property-placeholder)的对应解析器的分解器。实现通过Namespace SystemId找到对应的解析器的类路径。主要通过读取各个JAR文件的META-INF/spring.handlers文件实现。

Spring-context-3.x.x.jarMETA-INF/spring.handlers文件内容:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler

http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler

http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler

 

9. 可以开始真正的解析了。对Document的解析分为两种情况,一种是默认的名字空间beanshttp://www.springframework.org/schema/beans 无前缀的配置如:bean)和其他名字空间的节点的解析(有前缀,如:context:property-placeholder)。

 

  • 无前缀的beans默认名字空间节点:采用BeanDefinitionParserDelegate(解析的工具类)完成节点的解析。
  • 有前缀的其他名字空间节点:使用解析框架完成解析,具体逻辑为首先使用NamespaceSystemId(就是URL全路径)通过NamespaceHandlerResolver找到对应NamespaceHandler,然后通过具体的NamespaceHandlerparse方法解析节点。在NamespaceHandler内部通节点名称找到对应BeanDefinitionParser解析器完成节点的解析并返回BeanDefinition
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        // 创建解析的委托处理工具类
        BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
        // 解析前置处理,这里是空实现
        preProcessXml(root);
        // 解析整个文档,轮训各个子节点分别解析,下面有代码分析
        parseBeanDefinitions(root, delegate);
        //解析后置处理,也是空实现
        postProcessXml(root);
    }
 
         /**
          * Parse the elements at the root level in the document:
          * "import", "alias", "bean".
          * @param root the DOM root element of the document
          */
         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;
                                               // 如果是默认名字空间(beans),则直接使用解析
                                               if (delegate.isDefaultNamespace(ele)) {
                                                        parseDefaultElement(ele, delegate);
                                               }
                                               else {
                                                        // 如果是非默认空间,这使用解析框架完成。
                                                        delegate.parseCustomElement(ele);
                                               }
                                     }
                            }
                   }
                   else {
                            delegate.parseCustomElement(root);
                   }
         }
 
 
//BeanDefinitionParserDelegate. parseCustomElement(…)实现对非默认名字空间节点的通用解析框架
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
                   String namespaceUri = getNamespaceURI(ele);
                   NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
                   if (handler == null) {
                            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
                            return null;
                   }
                   return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
                  }
 

 

context:property-placeholder节点在解析过程中使用的NamespaceHandlerBeanDefinitionParser.

通过前面查看Spring-context-3.x.x.jarMETA-INF/spring.handlers文件内容(http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

),可以知道context名字空间使用的是ContextNamespaceHandler,代码如下:

 

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
 
    public void init() {
        // context:property-placeholder节点使用PropertyPlaceholderBeanDefinitionParser
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
 
}
  

可以看出在初始化方法里面定义了各个节点对应的解析器。其中context:property-placeholder节点使用PropertyPlaceholderBeanDefinitionParser

 

以上基本是ICO中对XML配置文件的整个解析和注册过程,到此只是完成了SPRING容器初始化的第一步:创建内部的beanFactory,加载解析XML文件并注册BeanDefinition到内部的BeanFactory中。后续继续学习通过BeanDefinition实例化Bean,设置依赖关系等。

猜你喜欢

转载自acooly.iteye.com/blog/1707354