1 /** 2 * Create a new XmlBeanFactory with the given input stream, 3 * which must be parsable using DOM. 4 * @param resource XML resource to load bean definitions from 5 * @param parentBeanFactory parent bean factory 6 * @throws BeansException in case of loading or parsing errors 7 */ 8 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { 9 super(parentBeanFactory); 10 this.reader.loadBeanDefinitions(resource); 11 }
整个处理过程:
1)封装资源文件
当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装
2)获取输入流
从Resource中获取对应的InputSteam并构造InputSource
3)通过构造的InputSource实例和Resource实例继续调用doLoadBeanDefinitions.
loadBeanDefinition的实现:
1 /** 2 * Load bean definitions from the specified XML file. 3 * @param resource the resource descriptor for the XML file 4 * @return the number of bean definitions found 5 * @throws BeanDefinitionStoreException in case of loading or parsing errors 6 */ 7 @Override 8 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { 9 return loadBeanDefinitions(new EncodedResource(resource)); 10 }
new EncodeResource(resource)实现:
1 private EncodedResource(Resource resource, String encoding, Charset charset) { 2 super(); 3 Assert.notNull(resource, "Resource must not be null"); 4 this.resource = resource; 5 this.encoding = encoding; 6 this.charset = charset; 7 }
1 /** 2 * Open a {@code java.io.Reader} for the specified resource, using the specified 3 * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding} 4 * (if any). 5 * @throws IOException if opening the Reader failed 6 * @see #requiresReader() 7 * @see #getInputStream() 8 */ 9 public Reader getReader() throws IOException { 10 if (this.charset != null) { 11 return new InputStreamReader(this.resource.getInputStream(), this.charset); 12 } 13 else if (this.encoding != null) { 14 return new InputStreamReader(this.resource.getInputStream(), this.encoding); 15 } 16 else { 17 return new InputStreamReader(this.resource.getInputStream()); 18 } 19 }
真正的数据准备阶段:
/** * Load bean definitions from the specified XML file. * @param encodedResource the resource descriptor for the XML file, * allowing to specify an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ 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()); } //通过属性值来记录已经加载的资源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try {
//从encodeResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try {
//InputResource这个类不来自与Spring 全类路径是org.xml.sax.InputSource InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); }
//真正的逻辑核心部分 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
首先对传入的resource参数做封装,目的是考虑到Resource可能存在的编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource,encodeResource.getResource()).
1 /** 2 * Actually load bean definitions from the specified XML file. 3 * @param inputSource the SAX InputSource to read from 4 * @param resource the resource descriptor for the XML file 5 * @return the number of bean definitions found 6 * @throws BeanDefinitionStoreException in case of loading or parsing errors 7 * @see #doLoadDocument 8 * @see #registerBeanDefinitions 9 */ 10 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 11 throws BeanDefinitionStoreException { 12 try {
//加载XML文件,并得到对应的Document 13 Document doc = doLoadDocument(inputSource, resource);
//根据返回的Document注册Bean信息 14 return registerBeanDefinitions(doc, resource); 15 } 16 catch (BeanDefinitionStoreException ex) { 17 throw ex; 18 } 19 catch (SAXParseException ex) { 20 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 21 "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); 22 } 23 catch (SAXException ex) { 24 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 25 "XML document from " + resource + " is invalid", ex); 26 } 27 catch (ParserConfigurationException ex) { 28 throw new BeanDefinitionStoreException(resource.getDescription(), 29 "Parser configuration exception parsing XML from " + resource, ex); 30 } 31 catch (IOException ex) { 32 throw new BeanDefinitionStoreException(resource.getDescription(), 33 "IOException parsing XML document from " + resource, ex); 34 } 35 catch (Throwable ex) { 36 throw new BeanDefinitionStoreException(resource.getDescription(), 37 "Unexpected exception parsing XML document from " + resource, ex); 38 } 39 }
获取XML的验证模式
DTD和XSD区别
DTD即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
要使用DTD验证模式的时候需要在XML文件的头部声明,以下是在Spring中使用DTD声明方式的代码:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
获取Document
1 /** 2 * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured 3 * XML parser. 4 */ 5 @Override 6 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, 7 ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { 8 9 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); 10 if (logger.isDebugEnabled()) { 11 logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); 12 } 13 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); 14 return builder.parse(inputSource); 15 }
首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。
在上述方法中涉及一个参数EntityResolver何为EntityResolver?如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例,也就是说,对于解析一个XML,SAX首先读取该XML文档的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个严重。默认的寻找规则,即通过网络来下载相应的DTD声明,并进行认证。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。
解析及注册BeanDefinitions
当把文件转换为Document后,接下来的提取及注册Bean就是我们的重头戏,当程序已经拥有XML文档文件的Document实例对象时,就会被引入如下的方法:
1 /** 2 * Register the bean definitions contained in the given DOM document. 3 * Called by {@code loadBeanDefinitions}. 4 * <p>Creates a new instance of the parser class and invokes 5 * {@code registerBeanDefinitions} on it. 6 * @param doc the DOM document 7 * @param resource the resource descriptor (for context information) 8 * @return the number of bean definitions found 9 * @throws BeanDefinitionStoreException in case of parsing errors 10 * @see #loadBeanDefinitions 11 * @see #setDocumentReaderClass 12 * @see BeanDefinitionDocumentReader#registerBeanDefinitions 13 */ 14 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 15 //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//记录统计前BeanDefinition的加载个数 16 int countBefore = getRegistry().getBeanDefinitionCount();
//加载及注册bean 17 documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//记录本次加载的BeanDefinition个数 18 return getRegistry().getBeanDefinitionCount() - countBefore; 19 }
1 /** 2 * Register each bean definition within the given root {@code <beans/>} element. 3 */ 4 protected void doRegisterBeanDefinitions(Element root) { 5 // Any nested <beans> elements will cause recursion in this method. In 6 // order to propagate and preserve <beans> default-* attributes correctly, 7 // keep track of the current (parent) delegate, which may be null. Create 8 // the new (child) delegate with a reference to the parent for fallback purposes, 9 // then ultimately reset this.delegate back to its original (parent) reference. 10 // this behavior emulates a stack of delegates without actually necessitating one. 11 BeanDefinitionParserDelegate parent = this.delegate; 12 this.delegate = createDelegate(getReaderContext(), root, parent); 13 14 if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性 15 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); 16 if (StringUtils.hasText(profileSpec)) { 17 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( 18 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); 19 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { 20 if (logger.isInfoEnabled()) { 21 logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + 22 "] not matching: " + getReaderContext().getResource()); 23 } 24 return; 25 } 26 } 27 } 28 //解析前处理,留给子类实现 29 preProcessXml(root); 30 parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类实现 31 postProcessXml(root); 32 33 this.delegate = parent; 34 }
preProcessXml和postProcessXml两个方法是空的,这是模板方法模式,在面向对象设计方法中常说:一个雷要么面向集成的设计,要么就用final修饰,这两个方法正是为子类设计的,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法就可以了。
解析并注册BeanDefinition:
1 /** 2 * Parse the elements at the root level in the document: 3 * "import", "alias", "bean". 4 * @param root the DOM root element of the document 5 */ 6 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans的处理 7 if (delegate.isDefaultNamespace(root)) { 8 NodeList nl = root.getChildNodes(); 9 for (int i = 0; i < nl.getLength(); i++) { 10 Node node = nl.item(i); 11 if (node instanceof Element) { 12 Element ele = (Element) node; 13 if (delegate.isDefaultNamespace(ele)) {
//对bean的处理 14 parseDefaultElement(ele, delegate); 15 } 16 else {
//对bean的处理 17 delegate.parseCustomElement(ele); 18 } 19 } 20 } 21 } 22 else { 23 delegate.parseCustomElement(root); 24 } 25 }
在Spring的XML配置里面有两大类Bean声明,一个是默认的,如:
1 <bean id="test" class="com.slp.Test"/>
另一类就是自定义的,如:
1 <tx:annotation-driven/>
这两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该如何做,但是如果是自定义的,那么久需要用户实现一些接口及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCutomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的方法其实是使用node.getNamespaceURI()获取命名空间,并与Spring的命名空间进行对比,如果一致则是默认,否则是自定义。