您的位置:首页 > 编程语言 > Java开发

简单看Spring源码--对xml文件解析

2017-04-05 21:51 603 查看

Spring如何解析xml配置文件?

xml配置文件是Spring中极其重要的一部分,让我们一起看一下spring解析xml文件的。

以下是一段简单的通过类路径下的test.xml文件加载bean获得BeanFactory的代码:

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("test.xml"));


一行代码,spring做的事情极其的复杂,主要分为以下几步:

1, 把资源文件进行封装,封装为Resource,有了Resource就可以对所有的资源文件进行统一处理

ClassPathResource的核心逻辑: (其实是简单的用class或classLoader读取类路径的资源文件)

InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;


2,在XmlBeanFactory构造方法中调用XmlBeanDefinitionReader开始Bean的加载

this.reader.loadBeanDefinitions(resource);


这句代码是整个资源加载的切入点。

3, EncodedResource对Resoure进行编码的处理,设置了编码属性的时候Spring会使用相应的编码作为输入流的编码

public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}


这段代码设置了输入流的编码,但是我在看源码的过程中并没有发现其有被调用的情况,

在注释上@see getInputStream()

@Override
public InputStream getInputStream() throws IOException {
return this.resource.getInputStream();
}


发现直接就是调用resource方法。

到底怎么回事???我看的时候一脸蒙蔽0。0

4,构造InputSource,InputSource并不来自于Spring,全路径是org.xml.sax,SAX解析xml的时候会使用InputSource来决定如何读取xml文件

InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}


原来encode在这里实现了,那么上面的EncodeResource只是起到了简单的提供encode或者charset的作用,

这样的话那么设计的时候是不是可以把EncodeResource去掉呢,直接在InputSource.setEncoding即可。

5,现在到达了核心处理部分

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}


首先
getValidationModeForResource
获取xml文件的验证模式(DTD或者XSD),可以自己设置验证方式,否则默认是开启
VALIDATION_AUTO
即自动获取验证模式的,底层实现是InputStream读取xml文件看xml文件是否包含
DOCTYPE
单词,包含的话就是DTD,否则返回XSD。看一下关键逻辑:

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);


接着获取Document,Spring并没有进行特殊的对xml文档的处理,使用了SAX解析xml文档,三步走:先创建DocumentBuilderFactory,接着获取DocumentBuilder,最后解析InputStream返回Document对象。

6, 可以看一下
EntityResolver
类,EntityResolver是什么呢,对于一个xml的验证,XSD或者DTD文件默认是从网上下载的,可以的话一般都是把DTD文件放在工程之中,而EntityResolver就是提供了一个如何寻找DTD文件的声明。

对于xsd模式:

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">[/code] 
读取到以下的两个参数:
publicId:
null ,
systemId:
http://www.springframework.org/schema/beans/spring-beans.xsd

对于DTD模式验证:

<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">


读取到两个参数:
publicId:
-//Spring//DTD BEAN 2.0//EN,

systemId:
http://www.springframework.org/dtd/spring-beans-2.0.dtd

默认spring对于两种验证方式提供了不同的解析器。

this.dtdResolver = new BeansDtdResolver();
this.schemaResolver = new PluggableSchemaResolver(classLoader);


XSD解析:

默认的XSD解析PluggableSchemaResolver使用了
private volatile Map<String, String> schemaMappings;
来保存schemaURL->local schema path的映射,默认的schemaMappingLocation位于META-INF/spring.schemas ,通过getSchemaMapping来获取这个映射,其中使用双重校验锁的方式来实现单例模式:

if (this.schemaMappings == null) {
synchronized (this) {
if (this.schemaMappings == null) {
...
}
}
}


有了map,接下来获取InputSource就是使用systemId获取resourceLocation,实现比较简单。

DTD解析:

DTD解析是直接截取systemId的最后的xx.dtd去当前路径下面寻找。

7,快了,快了再坚持一下,最后解析及注册bean

通过DocumentBuilder获取Document之后,就剩下

return registerBeanDefinitions(doc, resource);


方法的返回值是发现的定义的bean的数目,方法主要内容:

//创建DocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//注册beanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//获得已有的beanDefinition数目
int countBefore = getRegistry().getBeanDefinitionCount();

return getRegistry().getBeanDefinitionCount() - countBefore;


注册bean的时候首先使用一个
BeanDefinitionParserDelegate
类来判断是否是默认命名空间,实现是通过判断namespace uri 是否和默认的uri相等:

public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";


public boolean isDefaultNamespace(String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}


对于默认的命名空间,首先开始的是对profile属性解析,profile用得最多的是DataSource在不同环境下使用不同的bean,spring使用StringTokenizer来进行字符串的分割,但是jdk为了兼容性是推荐使用String.split()方法的:

String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}


preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);


接下来的解析使用了模板模式,preProcessXml和postProcessXml都是空方法,是为了方便之后的子类在解析之前之后进行一些处理。只需要覆写这两个方法即可。

在parseBeanDefinitions方法中spring对不同命名空间的元素的解析使用不同的方法:

Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}


对于不同的bean声明,spring的处理方法我先看一下,下次再写了。。

为了首尾呼应,回到开始的

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("test.xml"));


我们已经进入到解析bean的比较深入的步骤了,接下来

<bean id="myTestBean" class="MyTestBean.class"/>


经过默认命名空间的解析,接下来就是对bean标签的解析以及注册。

下次再写了。。。

以上只是自己的一些浅薄见解,是为了做一下总结,想努力地提升自己。如果有不对的地方,或者是哪里出错的地方,希望大佬可以指点一下小弟。。。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring xml 源码