6.1 EntityResolver 介绍
对于解析一个 XML,SAX 首先读取该 XML 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实际上就是生命的 DTD 的 URI 地址)来下载相应的 DTD 声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或者不可用时,这里会报错,就是因为相应的 DTD 声明没有被找到。
EntityResolver 的作用是项目本身就可以提供一个如何寻找 DTD 声明的方法,即由程序来实现寻找 DTD 声明的过程,比如我们将 DTD 文件放到项目中某个地方,在实现时直接将此文档读取并返回给 SAX 即可。这样就避免了通过网络寻找 DTD 声明。
首先看 EntityResolver 的接口方法声明:
接受两个参数 publicId 和 systemId,并返回一个 inputSource 对象。这里以特定的配置文件来进行讲解。
-
如果解析的配置文件的验证模式是 XSD ,如下:
读取到以下两个参数:- publicId: null
- systemId: http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd
-
如果解析的配置文件的验证模式是 DTD ,如下:
读取到以下两个参数:- publicId: -//SPRING//DTD BEAN 2.0//EN
- systemId: https://www.springframework.org/dtd/spring-beans-2.0.dtd
一般的做法是将验证文件放到自己的工程里。以加载 DTD 文件为例看看 Spring 中是如何实现的。根据之前 Spring 中通过 getEntityResolver() 方法对 EntityResolver 的获取,可以知道 Spring 中使用 DelegatingEntityResolver 类为 EntityResolver 的实现类,resolveEntity 实现方法如下:
可以看到,对不同的验证模式,Spring 使用了不同的解析器解析。比如加载 DTD 类型的 BeansDtdResolver 的 resolveEntity 是直接截取 systemId 最后的 xx.dtd 然后去当前路径下寻找,而加载 XSD 类型的 PluggableSchemaResolver 类的 resolveEntity 是默认到 META-INF/Spring.schemas 文件中找到 systemid 所对应的 XSD 文件并加载。
BeansDtdResolver.java
7. 解析及注册 BeanDefinitions
当把文件转换为 Document 后,接下来提取及注册 bean 是最重要的。继续上面的分析,当程序已经拥有 XML 文档文件的 Document 实例对象时,会被传入下面的方法参数中:
在这个方法中很好地应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是 BeanDefinitionDocumentReader。BeanDefinitionDocumentReader 是一个接口,而实例化的工作是在 createBeanDefinitionDocumentReader() 方法中完成的,而通过此方法,BeanDefinitionDocumentReader 真正的类型其实已经是 DefaultBeanDefinitionDocumentReader 了:
int countBefore = getRegistry().getBeanDefinitionCount();中的getRegistry()得到的其实就是 DefaultListableBeanFactory 实例:
DefaultBeanDefinitionDocumentReader 的 registerBeanDefinitions() 方法如下:
doRegisterBeanDefinitions() 代码如下:
7.1 profile 属性的使用
可以看到在注册 bean 的最开始是对 PROFILE_ATTRIBUTE 属性的解析,首先看下 profile 的用法:
继承到 Web 环境中时,在 web.xml 中加入以下代码:
<context-param>
<param-name>Spring.profiles.active</param-name>
<param-name>dev</param-name>
</context-param>
有了这个特性就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署甚至是测试环境,最常用的就是更换不同的数据库。
现在主流的 springboot 就包含了配置项 spring.profiles.active,可以直接指定开发环境还是部署环境等。
了解了 profile 的使用再分析代码会清晰得多,首先程序会获取 beans 节点是否定义了 profile 属性,如果定义了则需要到环境变量中去找,所以这里首先断言 environment 不可能为空,因为 profile 是可以同时指定多个的,需要程序对其拆分,并解析每个 profile 是都符合环境变量中所定义的,不定义则不会浪费性能去解析。
7.2 解析并注册 BeanDefinition
处理了 profile 后就可以进行 XML 的读取了,跟踪方法 parseBeanDefinitions(root, this.delegate):
对于默认标签解析与自定义标签解析会在后面的文章中进行总结。