上一节分析了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 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等等。