《Spring源码深度解析》学习笔记
阅读源码的一个好方法:通过示例跟踪代码,如果调用层次深,可配以UML。文中源码源自Spring 4.
从一个最基本的示例开始。假设有下面的Cat类:
public class Cat {
private String name = "Tom";
public void sayHello(){
System.out.println("Hello,I am " + name);
}
}
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cat" class="com.tony.test4spring.Cat" />
</beans>
BeanTest.java
public class BeanTest {
@Test
public void testXmlBeanFactory(){
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));
Cat cat = (Cat) bf.getBean("cat");
cat.sayHello();
}
}
输出结果: Hello,I am Tom
本文就从这个例子开始,分析下面这行代码究竟发生了什么。
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));
1.配置文件的封装
Spring配置文件beans.xml的读取是通过ClassPathResource进行封装的,就像上面的new ClassPathResource(“beans.xml”) 。Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口。Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。该接口定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen),还提供了不同资源到URL(getURL)、URI(getURI)、File(getFile)类型的转换方法。对不同来源的资源文件有相应的实现类:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)等。
通过Resource相关实现类完成配置文件的封装后,进入了XmlBeanFactory使用Resource实例作为构造函数参数的方法。如下所示:
XmlBeanFactory.java
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
构造函数再次调用内部构造函数:
XmlBeanFactory.java
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
this.reader.loadBeanDefinitions(resource)是资源加载的真正实现。在此之前,有一个调用父类构造函数初始化的过程,跟踪到AbstractAutowireCapableBeanFactory.java
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
这里的ignoreDependencyInterface方法的功能是忽略给定接口的自动装配功能。举例来说,当类A中有属性类B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B。但是某些情况下B不会被初始化,比如B实现了BeanNameAware接口。所以,这里的三行ignore方法就是告诉Spring,上例中的B属性如果实现了这三个接口中的任意一个时,那么在获取A的Bean的时候,就忽略B的初始化。
2.加载Bean
回到XmlBeanDefinitionReader类型的reader属性提供的方法:this.reader.loadBeanDefinitions(resource),这是整个资源加载的切入点。
1)封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
XmlBeanDefinitionReader.java
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
这里EncodedResource的作用是什么呢?通过名称,我们大致可以推断这个类主要是用于对资源文件的编码进行处理。其中主要逻辑天现在gerReader()方法中,当设置了编码属性的时候,Spring会使用相应的编码作为输入流的编码。
EncodedResource.java
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());
}
}
当构造好了EncodedResource实例后,再次转入了可复用方法:
2)获取输入流。从Resource中获取对应的InputStream并构造InputSource.
3)通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
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();
}
}
}
resourcesCurrentlyBeingLoaded是类XmlBeanDefinitionReader的ThreadLocal类型的成员变量,存放着和当前线程有关的数据:已经被加载的xml resource。把EncodedResource放到HashSet中,那么EncodedResource就必须覆盖Object的equals()方法和hashCode()方法。判断HashSet中元素是否重复的依据是 i)先看hashCode是否相同,如果不相同,那么equals方法的执行结果必然不相同;ii)如果hashCode相同,只能通过进一步执行equals来判定元素是否重复。
接着从encodedResource中获取已经封装的Resource对象,并再次从Resource中获取InputStream,通过SAX读取xml文件的方式准备InputSource对象。
说到这不得不提一下xml的两种解析模型,dom和sax。Dom解析(Document Object Mode)是把XML解析为一棵与XML文档结构对应的树,存储在内存中。取到XML里的每个元素、属性、文本、注释都在Document里有所体现。对于小的xml文件来说,这样处理还是很方便的。但是当遇到比较大的xml文件的时候,dom解析的缺点就显现出来了,内存占用比较大,而且查找速度比较慢。
sax解析就是为了解决这个问题出现的。SAX(Simple API for XML),对xml文档解析会从xml文档开始位置起进行解析,根据提前定义好的事件处理器来决定当前所解析的部分(元素、元素属性或者是内容)是否有必要记录并存储。
接下来把准备好的数据传入真正的核心处理部分doLoadBeanDefinitions:
XmlBeanDefinitionReader.java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//加载xml文件,得到对应的Document
Document doc = doLoadDocument(inputSource, resource);
//根据返回的Document注册Bean信息
return registerBeanDefinitions(doc, resource);
} ...N个catch...
}
doLoadDocument方法具体实现:
XmlBeanDefinitionReader.java
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
其中getValidationModeForResource(resource)是为了获取xml文件的验证模式。
3.获取xml的验证模式
xml的验证模式保证了xml文件的正确性,比较常用的验证模式有两种:DTD和XSD。
1)DTD与XSD区别
DTD(Document Type Definition),是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。使用DTD验证模式的时候,需要在XML文件头部声明,以下是在Spring中使用DTD声明方式的代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
...
</beans>
XSD(Xml Schema Definition)描述了XML文档的结构,可以用一个指定的Xml Schema来验证某个Xml文档,以检查该XML文档是否符合其要求。Xml Schema本身是一个XML,符合XML语法结构,可以用通用的xml解析器解析它。
使用xml schema检验xml时,往往需要声明namespace和对应的schema的URL地址,如下:
2)验证模式的读取
弄清楚了DTD和XSD的区别后,继续看getValidationModeForResource(resource)的具体实现:
XmlBeanDefinitionReader.java
protected int getValidationModeForResource(Resource resource) {
/** * 验证模式可以通过调用XmlBeanDefinitionReader中的setValidationMode方法进行设定, * 如果没设定,getValidationMode()方法返回值就是VALIDATION_AUTO, * 接下来就是通过detectValidationMode方法侦测验证模式 */
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
detectValidationMode(resource)的具体实现:
XmlBeanDefinitionReader.java
protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}
InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}
try {
return this.validationModeDetector.detectValidationMode(inputStream);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}
该方法把侦测验证模式的工作委托给了专门处理类XmlValidationModeDetector,调用了其detectValidationMode方法:
XmlValidationModeDetector.java
public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
/** * 去除当前行的注释内容,比如当前行内容为<!-- a comment --> ,经过consumeCommentTokens方法后返回空字符串; */
content = consumeCommentTokens(content);
/** * 如果当前行在注释范围内,或者当前行不包含文本内容,就直接判断下一行 */
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
/** * 当前行是否含有"DOCTYPE"字符串,如果有,则使用的是DTD验证模式; */
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
/** * 因为验证模式的定义一定在"<"这个字符之前,所以如果当前行包含"<",则表示获取验证模式的区域已经过去了,不再继续读下一行了 */
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
/** * 如果xml中含有"DOCTYPE",则表明验证模式就是DTD,否则就是XSD */
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
从上面的代码可以看出来,Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。
4.获取Document
获得xml验证模式以后,重新回到XmlBeanDefinitionReader的doLoadDocument方法,
XmlBeanDefinitionReader.java
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
接下来进入DefaultDocumentLoader的loadDocument方法:
DefaultDocumentLoader.java
public Document loadDocument(InputSource inputSource,EntityResolver entityResolver ,ErrorHandler errorHandler, int validationMode, boolean namespaceAware ) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode , namespaceAware);
if (logger .isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]" );
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
这部分的代码其实并没有太多需要描述的,因为通过SAX解析XML文档的套路大致都差不多,Spring在这里并没有特殊的地方,同样首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。
XmlBeanDefinitionReader的loadDocument方法的参数中的EntityResolver类型的值是通过getEntityResolver()获取的。EntityResolver的作用是什么?
1)EntityResolver的作用
解析xml时,SAX首先读取xml首部的声明部分,默认情况下,会按照声明中XSD或DTD的URI地址来下载相应的XSD或DTD文件,以此来验证xml是否正确。下载会有时间延迟,如果网络中断或者不可用导致找不到相应的XSD或DTD文件,就会报错。
EntityResolver是org.xml.sax package下面的一个接口,作用是项目本身就提供了一个如何用程序寻找XSD或DTD文件的方法,比如我们把XSD/DTD文件放在项目的某处,实现EntityResolver接口时可以直接读取并返回给SAX,这样就避免了通过网络来下载文件的问题。
EntityResolver接口包含如下方法:
public abstract InputSource resolveEntity(String publicId,String systemId ) throws SAXException, IOException;
这个方法针对不同的xml验证模式,参数publicId和systemId的值是不同的。如果是XSD,比如:
参数值为:
publicId:null
systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
如果是DTD,比如:
参数值为:
publicId:-//Spring//DTD BEAN 2.0//EN
systemId:http:www.springframework.org/dtd/spring-beans-2.0dtd
现在回过头来看XmlBeanDefinitionReader的getEntityResolver()方法,
XmlBeanDefinitionReader.java
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
getResourceLoader()返回的是一个PathMatchingResourcePatternResolver类型的实例,所以返回的EntityResolver是ResourceEntityResolver类型的实例。
在执行builder.parse(inputSource)方法时,会调用ResourceEntityResolver(即EntityResolver类型的入参)的resolveEntity方法,该方法继续调用父类DelegatingEntityResolver的resolveEntity方法:
DelegatingEntityResolver.java
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
可以看到针对不同的xml验证模式,Spring使用了不同的解析器解析:
1)如果systemId以.dtd结尾,即DTD验证模式,使用的是BeansDtdResolver的resolveEntity方法。
2)如果systemId以.xsd结尾,即XSD验证模式,使用的是PluggableSchemaResolver的resolveEntity方法。因为大多数情况下使用xsd验证模式,所以看一下这个方法的源码:
public InputSource resolveEntity(String publicId, String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Trying to resolve XML entity with public id [" + publicId +
"] and system id [" + systemId + "]");
}
if (systemId != null) {
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}
在运行测试用例时,systemId传进来的值为http://www.springframework.org/schema/beans/spring-beans.xsd ,以此为key到getSchemaMappings()返回的Map中寻找匹配值,看一下getSchemaMappings()方法的源码:
private Map<String, String> getSchemaMappings() {
if (this.schemaMappings == null) {
synchronized (this) {
if (this.schemaMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
}
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded schema mappings: " + mappings);
}
Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
}
}
}
}
return this.schemaMappings;
}
先把META-INF/spring.schemas中的内容加载进Properties实例中,然后再把它装进Map中,此时返回的Map内容为(一种可能的情形):
{
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans-4.0.xsd,
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd,
http://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd,
http://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd,
http://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd,
http://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.2.xsd,
http://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd,
http://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd,
http://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd,
http://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-4.2.xsd,
http://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd,
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd,
http://www.springframework.org/schema/util/spring-util-4.2.xsd=org/springframework/beans/factory/xml/spring-util-4.2.xsd,
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd,
http://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd,
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd,
http://www.springframework.org/schema/util/spring-util-4.1.xsd=org/springframework/beans/factory/xml/spring-util-4.1.xsd,
http://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-4.2.xsd,
http://www.springframework.org/schema/tool/spring-tool-4.0.xsd=org/springframework/beans/factory/xml/spring-tool-4.0.xsd,
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans-4.2.xsd,
http://www.springframework.org/schema/tool/spring-tool-4.1.xsd=org/springframework/beans/factory/xml/spring-tool-4.1.xsd,
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd,
http://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd,
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans-4.1.xsd,
http://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd,
http://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd,
http://www.springframework.org/schema/tool/spring-tool-4.2.xsd=org/springframework/beans/factory/xml/spring-tool-4.2.xsd
}
根据systemId http://www.springframework.org/schema/beans/spring-beans.xsd取到的value是: org/springframework/beans/factory/xml/spring-beans-4.2.xsd,回过头来看PluggableSchemaResolver的resolveEntity方法,则是去classpath下的对应的org/springframework/beans/factory/xml/spring-beans-4.2.xsd路径下寻找spring-beans-4.2.xsd,并加载。 这样就完成了从本地项目中读取xsd而不是去网络上下载。
5.解析及注册BeanDefinitions
当把文件转换为Document后,接下来就是提取和注册Bean,程序进入XmlBeanDefinitionReader的registerBeanDefinitions方法。
XmlBeanDefinitionReader.java
public int registerBeanDefinitions (Document doc , Resource resource ) throws BeanDefinitionStoreException {
/** * 返回的是类DefaultBeanDefinitionDocumentReader的实例 */
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
/** * 记录统计前BeanDefinition的加载个数 */
int countBefore = getRegistry().getBeanDefinitionCount();
/** * 加载和注册Bean */
documentReader.registerBeanDefinitions(doc , createReaderContext(resource ));
/** * 记录本次加载的BeanDefinition的个数 */
return getRegistry().getBeanDefinitionCount() - countBefore ;
}
跟进DefaultBeanDefinitionDocumentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext )方法,发现这个方法的主要目的就是获取root结点,以便于再次将root作为参数继续BeanDefinition的注册:
DefaultBeanDefinitionDocumentReader.java
public void registerBeanDefinitions(Document doc , XmlReaderContext readerContext ) {
this .readerContext = readerContext;
logger .debug("Loading bean definitions" );
Element root = doc .getDocumentElement();
doRegisterBeanDefinitions( root);
}
看一下doRegisterBeanDefinitions(root)的具体实现:
DefaultBeanDefinitionDocumentReader.java
protected void doRegisterBeanDefinitions(Element root) {
/** * 任何一个嵌入的<beans>元素都会在这个方法中造成递归。为了正确的传递和保护<beans>的defalt-*这类的属性, * 需要跟踪当前的delegate(parent),delegate可能为null。创建一个引用指向parent新的delegate用于回退, * 最终把this.delegate设置为初始的引用(parent)。 */
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
/** * 根节点使用的是否是默认的namespace:返回true的情形:root.getNamespaceURI()要么为空, * 要么是http://www.springframework.org/schema/beans */
if (this.delegate.isDefaultNamespace(root)) {
/** * 如果root含有profile属性,则判断profile的值是否符合环境变量中定义的(web.xml中的context-param来定义); * 符合继续,否则return; */
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)) {
return;
}
}
}
/** * preProcessXml(root)和postProcessXml(root)方法体是空的,这里应用了设计模式里的 * 模板方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解 * 析前后做一些处理的话,只需要重写这两个方法就可以了 */
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
接下来进入parseBeanDefinitions(root,this.delegate)方法:
DefaultBeanDefinitionDocumentReader.java
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在Spring xml配置文件里有两大类Bean声明,一类是使用默认的命名空间,比如:
<bean id="cat" class="test.Cat" />
另一类是使用自定义的命名空间,比如
<tx:annotation-driven />
判断是否是默认命名空间,取决于root.getNamespaceURI()的值是否为
http://www.springframework.org/schema/beans,或者是否为空(如下代码)。
public boolean isDefaultNamespace(Node node) {
return isDefaultNamespace(getNamespaceURI(node));
}
public boolean isDefaultNamespace(String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
这两种方式的读取及解析差别很大,对于默认的namespace,使用parseDefaultElement方法进行解析;如果是自定义的namespace,则使用delegate.parseCustomElement方法解析。各自的解析细节在下一节中作讨论。