简单看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源码分析2 — XML配置文件的解析流程
- Spring源码学习--Spring配置解析文件ApplicationContext.xml(一)
- Spring源码浅析 -- XML配置文件的载入与解析
- spring源码解析-从xml配置文件中获取bean
- 使用XPath解析xml实现简单的Spring IOC完成bean的依赖注入
- Spring3.2 中 Bean 定义之基于 XML 配置方式的源码解析
- Spring依赖注入的XML配置文件的实现思想(2)——简单的实现
- spring源码解读-加载解析配置文件
- Spring源码解析配置文件装载与解析
- spring MVC学习笔记(二) springMVC.xml配置文件解析
- Spring源码学习-容器初始化之FileSystemXmlApplicationContext(二)路径格式及解析方式(上) 推荐
- Spring3.2 中 Bean 定义之基于 XML 配置方式的源码解析
- 简单用xslt样式表解析xml文件。
- Spring源码追踪2——xml解析入口
- spring框架的XML扩展特性:让spring加载和解析你自定义的XML文件
- 关于Dom4j解析XML文件,外带上一个简单工具
- Spring源码分析-配置文件的解析(二)
- Spring的Ioc简单使用实例(含XML配置文件路径知识)
- Spring的Autowired自动装配(XML版本+Annotation版本+源码+解析)
- 通过Spring工具类获取classpath下的文件资源,解析xml