上一节分析了XmlBeanDefinitionReader以及系统环境的初始化,本小节分析Spring解析xml的过程中的将Xml文件解析为Document对象。
先来回顾一下Java解析xml的方式。包括DOM解析、SAX解析XML、JDOM解析XML、DOM4J解析XML等,每种解析方式各有优缺点。Spring使用的是第一种解析方式DOM解析,先通过一个例子来看一下Java是如何将xml文件解析为Document对象的。这将有助于接下来对Spring源码的分析。
1. Java DOM解析xml文件
- DOM解析
@Test public void test14() throws ParserConfigurationException, IOException, SAXException { // 解析xml文件 // 1、获取InputStream输入流 InputStream in = new ClassPathResource("v2/day01.xml").getInputStream(); // 2、获取DocumentBuilderFactory实例 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 3、获取DocumentBuilder实例 DocumentBuilder docBuilder = factory.newDocumentBuilder(); // 4、将docBuilder转换为Document Document doc = docBuilder.parse(in); // 5、获取节点并循环输出节点值 Element element = doc.getDocumentElement(); NodeList childNodes = element.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); //System.out.println(node.getNodeName()); NamedNodeMap attributes = node.getAttributes(); if (null != attributes) { System.out.println(attributes.getNamedItem("id")); System.out.println(attributes.getNamedItem("class")); } } }
- 输出
========测试方法开始======= id="dog1" class="com.lyc.cn.v2.day01.Dog" id="dog2" class="com.lyc.cn.v2.day01.Dog" id="dog3" class="com.lyc.cn.v2.day01.DogStaticFactory" id="dogFactory" class="com.lyc.cn.v2.day01.DogFactory" id="dog4" null id="outer" class="com.lyc.cn.v2.day01.inner.Outer" id="father" class="com.lyc.cn.v2.day01.parent.Father" id="sun" class="com.lyc.cn.v2.day01.parent.Sun" id="cat" class="com.lyc.cn.v2.day01.collection.Cat" id="car" class="com.lyc.cn.v2.day01.method.lookupMethod.Car" id="taxi" class="com.lyc.cn.v2.day01.method.lookupMethod.Taxi" id="dogReplaceMethod" class="com.lyc.cn.v2.day01.method.replaceMethod.ReplaceDog" id="originalDogReplaceMethod" class="com.lyc.cn.v2.day01.method.replaceMethod.OriginalDog" id="student" class="com.lyc.cn.v2.day01.factoryBean.StudentFactoryBean" id="furniture" class="com.lyc.cn.v2.day01.factoryBean.FurnitureFactoryBean" id="myLifeCycleBean" class="com.lyc.cn.v2.day01.lifecycle.LifeCycleBean" id="myBeanPostProcessor" class="com.lyc.cn.v2.day01.lifecycle.LifeCycleBeanPostProcessor" id="dog" class="com.lyc.cn.v2.day01.Dog" id="myBeanFactoryPostProcessor" class="com.lyc.cn.v2.day01.lifecycle.MyBeanFactoryPostProcessor" ========测试方法结束=======
非常简单,不再做过的分析。
2. Spring将xml转换为Document对象分析
打开XmlBeanFactory类
/** * 通过指定Resource对象和父BeanFactory创建XmlBeanFactory实例 * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { // 依次向上实例化父类构造器 super(parentBeanFactory); // 解析xml配置文件,将其转换为IoC容器的内部表示 this.reader.loadBeanDefinitions(resource); }
this.reader.loadBeanDefinitions(resource);
该代码的作用就是解析xml配置文件,将其转换为IoC容器的内部表示。我们先分析其第一步操作:解析xml配置文件。
跟踪代码,依次打开
- 方法入口
/** * 加载BeanDefinition * Load bean definitions from the specified XML file. * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
- 获取InputStream对象
/** * 加载BeanDefinition * 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 { // 1、使用ThreadLocal防止资源文件循环加载 Set<EncodedResource> currentResources = 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 { // 2、加载BeanDefinition InputStream inputStream = encodedResource.getResource().getInputStream(); try { 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(); } } }
- 将xml转换为Document对象并执行BeanDefinition注册
/** * 真正开始执行BeanDefinition的注册 * Actually load bean definitions from the specified XML file. * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #doLoadDocument * @see #registerBeanDefinitions */ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 资源文件解析为Document对象 Document doc = doLoadDocument(inputSource, resource); // 注册BeanDefinitions return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(),"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(),"XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(),"Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(),"IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, ex); } }
通过上面代码的分析,已经接触到了将xml文件转换为Document的核心Document doc = doLoadDocument(inputSource, resource);
,其实并没有我们想象中那么神秘,跟我们之前分析的DOM解析是一样的。但是其中有一些细节还是值得我们去分析的。
- 执行转换
@Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { // 1、创建DocumentBuilderFactory对象 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); // 2、创建DocumentBuilder对象 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); // 3、将inputSource解析为Document对象 return builder.parse(inputSource); }
转换过程一共分为了三步,这与DOM解析的流程差不多,来具体分析一下其中的一些细节。
1.创建DocumentBuilderFactory对象
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { // 1、获取DocumentBuilderFactory实例 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); // 2、如果开启xml验证的话,则验证xml if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { factory.setValidating(true); // 如果xml验证模式为XSD则需要强制指定由此代码生成的解析器将提供对XML名称空间的支持 if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { // Enforce namespace aware for XSD... factory.setNamespaceAware(true); try { 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; }
- 2.创建DocumentBuilder对象
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory, @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler) throws ParserConfigurationException { // 1、创建DocumentBuilder对象 DocumentBuilder docBuilder = factory.newDocumentBuilder(); // 2、尝试设置entityResolver if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); } // 3、尝试设置errorHandler if (errorHandler != null) { docBuilder.setErrorHandler(errorHandler); } return docBuilder; }
这里有一个EntityResolver类,该类的作用是避免从网络上寻找DTD声明。至于转换方法本节不在分析,因为涉及到了jdk的源码,且不是我们分析的重点。
总之Spring将Xml文件解析为Document对象的过程就是使用了Java的DOM解析,只不过在解析之上做了一些额外的操作,例如防止文件重复加载、xml验证模式、
设置EntityResolver、设置errorHandler等等。