1.
DefaultListableBeanFactory类是整个bean加载的核心部分,是spring注册及加载bean的默认实现,
而对于XMLBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口.以下是ConfigurableListableBeanFactory的层次结构图:
各个类的作用:
AliasRegistry:定义对alias的简单增删改等操作.
SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现.
SingletonBeanRegistry:定义对单列的注册及获取.
BeanFactory:定义获取bean及bean的各种属性.
DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现.
HierarchicalBeanFactory:继承BeanFactory, 也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持.
BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作.
FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能.
ConfigurableBeanFactory:提供配置Factory的各种方法.
ListableBeanFactory:根据各种条件获取bean的配置清单.
AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能.
AutowireCapableBeanFactory:提供创建bean,自动注入,初始化及应用bean的后处理器.
AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现.
ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等.
DefaultListableBeanFactory:综合上面所有功能, 主要是对Bean注册后的处理.
XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从xml文档中读取BeanDefinition,对于注册及获取Bean都是从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性.在XmlBeanFactory中主要使用Reader属性对资源文件进行读取和注册.
XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,那么我们可以从XmlBeanDefinitionReader中梳理一下资源文件读取,解析及注册的大致脉络,首先我们看看各个类的功能.
ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource.
BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能.
EnvironmentCapable:定义获取Environment方法. (注:Environment接口是表示当前应用程序咸亨在运行的环境的接口,模拟应用程序环境的两个关键方面:配置文件和属性.)
DocumentLoader:定义从资源文件加载到转换为Document的功能.
AbstractBeanDefinitionReader:对EnvironmentCapable,BeanDefinitionReader类定义的功能进行实现.
BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能.
BeanDefinitionParserDelegate:定义解析Element的各种方法.
(1).通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件.
(2).通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件.
(3)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析.
配置文件封装:
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource("beanFactoryTest.xml"),那么ClassPathResource完成了什么功能呢?
在java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同前缀(协议,Protocol)来识别,如"file"、“http”、“jar”等,然后URL没有默认定义相对ClassPath或ServletContext等资源的handler,虽然可以注册资金的URLStreamHandler来解析特定的URL前缀(协议),比如“classpath”,然而这需要了解URL的实现机制,而且URL也没有提供一些基本的方法,如检查当前资源是否存在、检查当前资源是否可读等方法。因而Spring对其内部使用到的资源实现了自己的抽象结构:Rsource接口来封装底层资源。
InputStreamSource封装任何能返回InputStream的类,比如File、ClassPath下的资源和Byte Array等。它只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象。
Resource接口抽象了所有的Spring内部使用到的底层资源:File、URL、Classpath等。首先,它定义了3个判断当前资源状态的方法:存在性、可读性、是否处于打开状态。另外,Resource接口还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性、文件名(不带路径信息的文件名,getFilename())的方法。为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法:createRelative()。在错误处理中需要详细的打印出错的资源文件,因而Resource还提供了getDescription()方法用于在错误处理中的打印信息。
对不同来源的资源维纳都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。关系如下:从下往上看,上为子类, 下为父类:
在日常的开发工作中,资源文件的加载也是经常用到的,可以直接使用Spring提供的类,比如在希望加载文件时可以使用以下代码:Resource resource = new ClassPathResource("beanFactoryTest.xml"); InputStream inputStream = resource.getInputStream(); 得到inputStream后,我们就可以按照以前的开发方式进行实现了,并且我们已经可以利用Resource及其子类为我们提供好的诸多特性。
有了Resource接口便可以对所有资源文件进行统一处理,至于实现,其实时非常简单的,以getInputStream()为例,ClassPathResource中的实现方式便是通过class或者classLoader提供的底层方法进行调研,而对于FileSystemResource的实现其实更简单,直接使用FileInputStream对文件进行实例化。
当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。
了解了Spring中将配置文件封装为Resource类型的实例方法后,我们就可以继续探寻XmlBeanFactory的初始化过程了,XmlBeanFactory的初始化有若干方法,Spring中提供了很多的构造函数,在这里分析的是使用Resource实例作为构造函数参数的方法,代码如下:
public XmlBeanFactory(Resource resource)throws BeanException{
// XmlBeanFactory(resource, beanFactory)构造方法,
this.(resource, null);
}
// 构造函数内部再次调用内部构造函数
// parentBeanFactory为父类BeanFactory用于factory合并,可以为空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)throws BeanException{
super.(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
上面函数中的代码this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现,也是我们分析的重点之一。我们可以看到时序图中提到的XmlBeanDefinitionReader加载数据就是在这里完成的,但是在XmlBeanDefinitionReader加载数据前还有一个调用父类构造函数初始化的过程:super(parentBeanFactory),跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中:
public AbstractAutowireCapableBeanFactory(){
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能,那么,这样做的目的是什么呢?会产生什么样的效果呢?
举列来说,当A中有属性B,那么当spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B,这也就是Spring中提供的一个重要特性。但是,某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。Spring中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入。
加载Bean
查看XMLBeanDefinitionReader类的方法loadBeanDefinitions(resource)方法,如下图:
(1)封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
(2)获取输入流,从Resource中获取对应的InputStream并构造InputStream。
(3)通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
看一下loadBeanDefinitions函数具体的实现过程:
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
那么EncodeResource的作用是什么呢?通过名称,我们可以大致推断这个类主要是用于对资源文件的编码进行处理的。其中的主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。
public Reader getReader() throws IOException{
if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
} else {
return new InputStreamReader(this.resource.getInputStream());
}
}
上面代码构造了一个有编码(encoding)的InputStreamReader。当构造好encodedResource对象后,再次转入了可服用方法loadBeanDefinitions(new EncodedResource(resource));
查看如下代码:
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
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// 从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// InputSource这个类并不来自于Spring,它的全路径org.xml.sax.InputSource
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();
}
}
}
我们再次整理一下数据准备阶段的逻辑,首先对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())。
看如下代码:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 解析指定的xml生成DOM对象
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
在上面冗长的代码中加入不考虑异常类的代码,其实只做了三件事,这三件事的每一件都必不可少。
(1)获取对xml文件的验证模式。
(2)加载xml文件,并得到对应的Document。
(3)根据返回的Document注册Bean信息。
这个3个步骤支撑着整个Spring容器部分的实现基础,尤其是第3步对配置文件的解析,逻辑非常的复杂,那么我们先从获取XML文件的验证模式开始讲起。
获取XMl的验证模式:
保证XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD。
区别:
DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符合规则。
要使用DTD模式验证的时候需要在XML的文件头部声明。
XML Schema 语言就是XSD(XML Schemas Definition)。 XML Schema 描述了XML文档的结构。可以用一个指定的XML Schema 来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML Schema指定一个XML文档所允许的结构和内容,并可据此检查一个XML文档是否是有效的。XML Schema 本身是一个XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。
在使用XML Schema 文档对XML实例文档进行校验,除了要声明名称空间外(xmlns=http://Springframework.rog/schema/beans),还必须指定该名称空间所对应的XML Schema 文档的存储位置。通过schemaLocation属性来指定名称空间所对应的XML Schema 文档的存储位置,它包含两个部分,一部分是名称空间的URL,另一部分就是该名称空间所标识的XML Schema 文件位置或URL地址。
验证模式的读取:
了解了DTD和XSD的区别后我们再去分析Spring中对于验证模式的提取就更容易理解了,通过之前的分析我们锁定了Spring通过getValidationModeForResource方法来获取对应资源的验证模式。
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
// 如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 如果未指定则使用自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
方法的实现其实还是很简单的,无非是如果设定了验证模式则使用设定的验证模式(可以通过对调用XmlBeanDefinitionReader中的setValidationMode方法进行设定),否则使用自动检测的方式。而自动检测验证模式的功能是在函数detectValidationMode方法中实现的,在detectValidationMode函数中又将自动检测验证模式的工作委托给了专门处理类XmlValidationModeDetector,调用了XmlValidationModeDetector的detectValidationMode方法,具体代码如下:
继续查看XmlValidationModeDetector的方法detectValidationMode
只要我们理解了XSD与DTD的使用方法,理解上面的代码应该不会太难,Spring用来检测验证模式的方法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。
获取Document
经过了验证模式准备的步骤就可以进行Document加载了,同样XmlBeanFactoryReader类对于文档读取并没有亲力亲为,而是委托给了DocumentLoader去执行,这里的DocumentLoader是个借口,而真正调用的是DefaultDocumentLoader,源码如下:
对于这部分代码其实并没有太多可以描述的,因为通过SAX解析XML文档的都是一样的,Spring在这里并没有什么特殊的地方,同样首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。对此感兴趣的读者可以在网上获取更多的资料。这里有必要提及一下EntityResolver,对于参数entityResolver,传入的是通过getEntityResolver()函数获取的返回值,看源码:
EntityResolver用法
在loadDocument方法中涉及一个参数EntityResolver,何为EntityResolver?官网解释:如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明的DTD的URL地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就死因为相应的DTD声明没有找到的原因。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD 文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找相应的声明。
首先看entityResolver的接口方法声明:
InputSource resolveEntity(String publicId, String systemId);
这里,它接收两个参数publicId和systemId, 并返回一个inputSource对象。这里我们以特定配置文件来进行讲解。
(1)如果我们在解析验证模式为XSD的配置文件,代码如下:
读取到以下两个参数。
publicId: null
systemId: http://www.Springframework.org/schema/beans/Spring-beans.xsd
(2)如果我们在解析验证模式为DTD的配置文件,代码如下:
........
读取到以下两个参数。
publicId:-//Spring//DTD BEAN 2.0//EN
systemId:http://www.SpringFramework.org/dtd/Spring-beans-2.0.dtd
之前已经提到过,验证文件默认的加载方式是通过URL进行网络下载获取,这样会造成延迟,用户体验也不好,一般的做法都是讲验证文件放置在自己工程里,那么怎么做才能将这个URL转换为自己工程里对应的地址文件呢?我们以加载DTD文件为例来看看Spring中是如何实现的。根据之前Spring中通过getEntityResolver()方法对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolverEntity实现方法如下:
我们可以看到,对不同的验证模式,Spring使用了不同的解析器解析。这里简单描述一下原理,比如加载DTD类型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolverEntity是默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载。
查看BeansDtdResolver类方法resolveEntity源码:
查看PluggableSchemaResolver类方法resolveEntity源码:
解析及注册BeanDefinitions
当把文件转换为Document后,接下来的提取及注册bean就是我们的重头戏。继续上面的分析,当程序已经拥有XML文档文件的Document实例对象时,就会被引入下面这个方法。
XmlBeanDefinitionReader类方法的registerBeanDefinitions实现了对xml解析、注册并统计加载的BeanDefinition个数。
其中的参数doc是通过上一节loadDocument加载转换出来的。在这个方法中很好地应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑类就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一个接口,而实例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader了,进入DefaultBeanDefinitionDocumentReader,发现这个方法的重要目的之一就是提取root,以便于再次将root作为参数继续BeanDefinition的注册。
public void registerBeanDefinitions(Document doc, XmlReaderContext xmlReaderContext){
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
经历艰难险阻,磕磕绊绊,我们终于到了核心逻辑的底部doRegisterBeanDefinitions(root),至少我们在这个方法中看到了希望。
如果说以前一直是XML加载解析的准备阶段,那么doRegisterBeanDefinitions算是真正地开始进行解析了,我们期待的核心部分真正开始了。
通过上面的代码我们看到了处理流程,首先是对profile的处理,然后开始进行解析,可是当我们跟进preProcessXml(root)或者postProcessXml(root)发现代码是空的,既然是空的写着还有什么用呢?就像面向对象设计方法学中常说的一句话,一个类要么是面向继承设计的,要么就用final修饰。在DefaultBeanDefinitionDocumentReader中并没有用final修饰,所以它是面向继承而设计的。这两个方法正式为子类而设计的,如果读者有了解过设计模式,可以很快速地反映出这事模板方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法就可以了。
profile属性的使用
在注册Bean的最开始是对PROFILE_ATTRIBUTE属性的解析,可能对于我们来说,profile属性并不是很常用。让我们先了解一下这个属性。
1.profile是什么呢?
Spring中的Profile功能其实早在Spring 3.1的版本就已经出来,它可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,我们以前所定义的Bean,当Spring容器一启动的时候,就会一股脑的全部加载这些信息完成对Bean的创建;而使用了Profile之后,它会将Bean的定义进行更细粒度的划分,将这些定义的Bean划分为几个不同的组,当Spring容器加载配置信息的时候,首先查找激活的Profile,然后只会去加载被激活的组中所定义的Bean信息,而不被激活的Profile中所定义的Bean定义信息是不会加载用于创建Bean的。
2.为什么要使用Profile
由于我们平时在开发中,通常会出现在开发的时候使用一个开发数据库,测试的时候使用一个测试的数据库,而实际部署的时候需要一个数据库。以前的做法是将这些信息写在一个配置文件中,当我把代码部署到测试的环境中,将配置文件改成测试环境;当测试完成,项目需要部署到现网了,又要将配置信息改成现网的,真的好烦。。。而使用了Profile之后,我们就可以分别定义3个配置文件,一个用于开发、一个用户测试、一个用户生产,其分别对应于3个Profile。当在实际运行的时候,只需给定一个参数来激活对应的Profile即可,那么容器就会只加载激活后的配置文件,这样就可以大大省去我们修改配置信息而带来的烦恼。
定义关于profile的spring配置文件
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
有了这个特性我们就可以同时在配置文件中部署两套配置来使用于生产环境和开发环境,这样可以方便的进项切换开发、部署环境,最常用的就是更换不同的数据库。
了解了profile的使用再来分析代码会清晰的多,首先程序会获取beans节点是否定义了profile属性,如果定义了则会需要到环境变量中去寻找,所以这里首先断言environment不可能为空,因为profile是可以同时指定多个的,需要程序对其拆分,并解析每个profile是都符合环境变量中所定义的,不定义则不会浪费性能去解析。
解析并注册BeanDefinition
在Spring的xml配置里面有两大类Bean声明,一个是默认的,如:
另一类就是自定义的,如:
这两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该怎么做,但是如果是自定义的,那么久需要用户实现一些接口及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的方法其实时使用node.getNamespaceURL()获取命名空间,并与Spring中固定的命名空间http://www.Springframework.org/schema/beans进行比对。如果一致则认为是默认,否则就认为是自定义。而对于默认标签解析与自定义标签解析我们将会在下一章中进行讨论。