获取Domcument
在 XmlBeanDefinitionReader.doLoadDocument() 方法中做了两件事情,一是调用 getValidationModeForResource() 获取 XML 的验证模式,二是调用 DocumentLoader.loadDocument() 获取 Document 对象,上文我们获取了xml文件的验证方式 ( 传送门 ),接下来我们看一下获取domcument
XmlBeanDefinitionReader.java
/
* 使用配置好的DocumentLoader文档加载器加载指定的文档
* documentLoader 默认实现DefaultDocumentLoader
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
其中documentLoader 默认实现DefaultDocumentLoader,点击进查看他的代码
DefaultDocumentLoader.java
/**
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
* 使用标准JAXP配置XML解析器加载InputSource的Document对象
* namespaceAware 默认命名为false
* validationMode 上面getValidationModeForResource返回过来的类型
*/
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//创建文档构建器工厂对象,并初始化一些属性
//如果验证模式为XSD,那么强制支持XML名称空间,并加上schema属性
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
// 判断记录器Trace跟踪是否激活。Trace跟踪激活后会打印比较详细的信息。有助于提高系统性能
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 创建一个JAXP文档构建器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 按照XML文档解析给定inputSource的内容,然后返回一个新的DOM对象
return builder.parse(inputSource);
}
由上可以看出Spring在这里执行步骤是
1,建立DocumentBuildFactory**(属于javax.xml.parsers)**
2,通过DocumentBuildFactory创建DocumentBuilder
3,解析inputSource来返回Document对象
我们看一下createDocumentBuilderFactory
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
// 如果没有指令应用禁止验证, VALIDATION_NONE=0
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
// VALIDATION_XSD=3
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// 使用xsd命名空间
factory.setNamespaceAware(true);
try {
// 设置schema属性
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;
}
这里其实使用SAX解析XML的,使用SAX我们就不得不说一下EntityResolver,EntityResolve是org.xml.sax下面的一个接口,官方定义如果SAX应用实现自定义处理外部实体,则必须实现这个接口,并且用setEntityResolver向SAX驱动器注册一个实例,EntityResolver 的作用就是项目本身就可以提供一个如何寻找DTD 的声明方法,在Spring中的参数entityResolver是这样获取的
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
EntityResolver 用法
EntityResolver接受两个参数,var1(pubilcId),var2(ststemId),返回一个inputSource对象
假如我们读取验证模式为XSD的配置文件
读取到两个参数
- pubilcId:null
- ststemId:http://www.springframework.org/schema/beans/spring-beans.xsd
如果解析DTD的文件的话,读取到的参数为 - pubilcId:-//Spring//DTD BEAN 2.0//EN
- ststemId:http://www.springframework.org/dtd/spring-beans-2.0.dtd
由于验证文件的默认加载方式是通过url解析网络下载获取,这样会造成延迟,用户体验也不好,一般我们都会放到我们自己的工程里,那么怎么才可以可以将url转为从我们的工程中的对应文件了,我们这里来看spring是怎么实现的
spring中一般使用DelegatingEntityResolver类为EntityResolver的实现类
DelegatingEntityResolver实现了EntityResolver,DelegatingEntityResolver实现的resolveEntity方法是只有这样的
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
throws SAXException, IOException {
if (systemId != null) {
// 如果是dtd DTD_SUFFIX=.dtd
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
// XSD_SUFFIX = .xsd
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
// Fall back to the parser's default behavior.
return null;
}
对应不同的验证模式,spring使用了不同的解析器解析,比如:
- 加载DTD类型的BeanDtdResolver一般是直接截取systemId最后的xx.dtd然后去当前路径下面找
- 加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/spring.schemas我家中找到systemid所对应的xsd文件并进行加载
解析注册BeanDefinitions
继续上面的代码,解析注册BeanDefinitions其实是在**documentReader.registerBeanDefinitions(doc,createReaderContext(resource))**方法里面的,也就是图中红线部分点击进去我们看一下他的源码
XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader,
// 目的是为了对xml格式的BeanDefinitions进行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// getRegistry()方法拿的是bean工厂对象,beanDefinition注册在工厂中
//这个方法就是返回已经被注册在工厂中的beanDefinitions数量
int countBefore = getRegistry().getBeanDefinitionCount();
// 具体的解析过程由DefaultBeanDefinitionDocumentReader完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 返回上个方法真正注册在工厂中的beanDefinition数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
注意这里documentReader.registerBeanDefinitions(doc,
createReaderContext(resource));这使用了委派模式,关于设计模式可以关注我的java设计模式系列,一直不断更新中,
我们先来看一下createBeanDefinitionDocumentReader()方法以及documentReaderClass
也就是说BeanDefinitionDocumentReader是一个接口,registerBeanDefinitions(doc, createReaderContext(resource)),实现的其实是他的子类,点击进去registerBeanDefinitions进去
DefaultBeanDefinitionDocumentReader.java
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//入参时创建的XmlReaderContext对象
this.readerContext = readerContext;
//传进xml文档对象的根元素
doRegisterBeanDefinitions(doc.getDocumentElement());
}
由上面可以知道这段代码的重要目的之一就是提取root,以便于再次将xml的文档根元素作为参数传递,具体的注册其实是通过**doRegisterBeanDefinitions(doc.getDocumentElement());**实现的
我们的XML加载解析的准备阶段已经全部完成,下面就是开始进行解析了
还是老规矩点击进doRegisterBeanDefinitions这个方法
protected void doRegisterBeanDefinitions(Element root) {
// 任何被嵌套的<beans>元素都会导致此方法的递归。为了正确的传播和保存<beans>的默认属性、
// 保持当前(父)代理的跟踪,它可能为null
// 为了能够回退,新的(子)代理具有父的引用,最终会重置this.delegate回到它的初始(父)引用。
// 这个行为模拟了一堆代理,但实际上并不需要一个代理
// BeanDefinitionParserDelegate中定义了一系列具体的spring xml文件定义的各种元素
// 这里实现的是DefaultBeanDefinitionDocumentReader
BeanDefinitionParserDelegate parent = this.delegate;
// 创建一个代理 初始化
this.delegate = createDelegate(getReaderContext(), root, parent);
//如果默认名称空间是"http://www.springframework.org/schema/beans"
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
//存在xml文件设置"profile" 运行时环境
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//xml预处理,子类没有重写里面就是空实现
preProcessXml(root);
//2.2生成BeanDefinition,并注册在工厂中
parseBeanDefinitions(root, this.delegate);
//xml后处理,子类没有重写里面就是空实现
postProcessXml(root);
this.delegate = parent;
}
我们按照代码执行顺序进入方法一个个的查看,先是createDelegate,这主要是对delegate(DefaultBeanDefinitionDocumentReader进行一个初始化)
在继续往下走,进入if判断,这里我们可以看到spring从xml根元素中获取了一个名字叫PROFILE_ATTRIBUTE,值是profile的属性值,我们不着急往下走,先看看这个profile是什么
Profile属性
Spring中的Profile功能其实早在Spring 3.1的版本就已经出来,它可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中,就好像我们的运行环境差不多把,通常我们的开发会部署多套环境,如测试环境,开发环境,生产环境,每一套环境对应不同的配置文件,Profile使我们更加灵活的在不同的配置环境之间进行切换,最常用的应该就是切换不同的数据库把
具体使用
web.xml 加入以下代码
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>development</param-value>
</context-param>
回到主题,继续查看doRegisterBeanDefinitions的代码,我们看到preProcessXml(root)和postProcessXml(root)这两个方法,这两个方法都是同一个类(DefaultBeanDefinitionDocumentReader.jva)里面的
不难看出这两个都是空方法,具体的实现是留给子类去实现的,这里使用到了设计模式里的模板方法,我们直接去看parseBeanDefinitions,这个方法里面主要使用了Spring的Bean规则从文档根元素对文档进行解析
DefaultBeanDefinitionDocumentReader
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//spring定义了元素默认名称空间是"http://www.springframework.org/schema/beans"
//进入条件
if (delegate.isDefaultNamespace(root)) {
//获取根元素下的子Node,注意,Node不一定是子标签,可能是回车,可能是注释
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
//拿到了<beans>下的子标签
Element ele = (Element) node;
//3.如果该标签属于beans的名称空间,则进入这个方法
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
//4.如果该标签属于其他的名称空间比如:context,aop等
//xmlns:aop="http://www.springframework.org/schema/aop"
//xmlns:context="http://www.springframework.org/schema/context"
// 即如果没有使用默认命名空间,则使用用户自定义的配置规则
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 文档的根节点没有使用Spring的默认命名空间
// 使用自定义的解析规则解析文档根目录
delegate.parseCustomElement(root);
}
}
对于上述代码,我们可以知道spring解析俩种不同类型的bean,即自定义bean(使用delegate.parseCustomElement)和默认的bean(使用parseDefaultElement),主要通过判断node.getNamespaceURI()获取命名空间,并与spring给定的命名空间进行对比
下 面 一 章 我 们 分 别 看 看 这 俩 种 标 签 对 于 s p r i n g 来 说 是 怎 么 解 析 并 实 现 的 {下面一章我们分别看看这俩种标签对于spring来说是怎么解析并实现的} 下面一章我们分别看看这俩种标签对于spring来说是怎么解析并实现的