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()); } //通过属性值来记录已经加载的资源 SetcurrentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet (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文件,并得到对应的Document13 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 2
获取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 *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 errors10 * @see #loadBeanDefinitions11 * @see #setDocumentReaderClass12 * @see BeanDefinitionDocumentReader#registerBeanDefinitions13 */14 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {15 //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //记录统计前BeanDefinition的加载个数16 int countBefore = getRegistry().getBeanDefinitionCount(); //加载及注册bean17 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //记录本次加载的BeanDefinition个数18 return getRegistry().getBeanDefinitionCount() - countBefore;19 }
1 /** 2 * Register each bean definition within the given root { @code} element. 3 */ 4 protected void doRegisterBeanDefinitions(Element root) { 5 // Any nested elements will cause recursion in this method. In 6 // order to propagate and preserve 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
另一类就是自定义的,如:
1
这两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该如何做,但是如果是自定义的,那么久需要用户实现一些接口及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCutomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的方法其实是使用node.getNamespaceURI()获取命名空间,并与Spring的命名空间进行对比,如果一致则是默认,否则是自定义。