spring源码分析 ioc标签的解析

目录

bean标签的解析及注册

解析BeanDefinition

创建用于属性承载的BeanDefinition

解析各种属性

解析子元素meta

解析子元素lookup-method

解析子元素replaced-method

解析子元素constructor-arg

解析子元素property

解析子元素qualifier

AbstractBeanDefinition属性

解析默认标签中的自定义标签元素

注册解析的Bean Definition

通过BeanName注册BeanDefinition

通过别名注册Bean Definition

通知监听器解析及注册完成

alias标签的解析

import标签的解析

嵌入式beans标签的解析

自定义标签的解析

自定义标签使用

自定义标签解析

获取标签的命名空间

提取自定义标签处理器

标签解析


注意:本文摘自spring源码深度解析

之前提到过Spring中的标签包括默认标签和自定义标签两种,而两种标签的用法以及解析方式存在着很大的不同,本章节重点带领读者详细分析默认标签的解析过程。

默认标签的解析是在parseDefaultElement函数中进行的,函数中的功能逻辑一目了然,分别对4种不同标签(import、alias、bean和beans)做了不同的处理。

bean标签的解析及注册

在4种标签的解析中,对bean标签的解析最为复杂也最为重要,所以我们从此标签开始深入分析,如果能理解此标签的解析过程,其他标签的解析自然会迎刃而解。首先我们进入函数processBeanDefinition(ele,delegate)。

乍一看,似乎一头雾水,没有以前的函数那样清晰的逻辑。大致的逻辑总结如下。

1 首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdholder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如class、name、id、alias之类的属性。

2 当返回的bdHolder不为空的清况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。

3 解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了Bean­DefinitionReaderUtils的registerBeanDefinition方法。

4 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。配合时序图(见图),可能会更容易理解。

解析BeanDefinition

下面我们就针对各个操作做具体分析。首先我们从元素解析及信息提取开始,也就是BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),进入BeanDefinition­Delegate类的parseBeanDefinitionElement方法。

以上便是对默认标签解析的全过程了。当然,对Spring的解析犹如洋葱剥皮一样,一层一层地进行,尽管现在只能看到对属性过以及name的解析,但是很庆幸,思路我们已经了解了。在开始对属性展开全面解析前,Spring在外层又做了一个当前层的功能架构,在当前层完成的主要工作包括如下内容。

I. 提取元素中的id以及name属性。

2 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。

3 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。

4 将获取到的信息封装到BeanDefinitionHolder的实例中。

我们进一步地查看步骤2中对标签其他属性的解析过程。

终于,bean标签的所有属性,不论常用的还是不常用的我们都看到了,尽管有些复杂的属性还需要进一步的解析,不过丝毫不会影响我们兴奋的心情。接下来,我们继续一些复杂标签属性的解析。

创建用于属性承载的BeanDefinition

BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBean­Definition以及GenericBeanDefinition。三种实现均继承了AbstractBeanDefiniton,其中BeanDefinition是配置文件元素标签在容器中的内部表示形式。元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazy-init属性,BeanDefinition中的属性是一一对应的。其中RootBeanDefinition是最常用的实现类,它对应一般性的元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务类。

在配置文件中可以定义父和子,父用RootBeanDefinition表示,而子用ChildBeanDefiniton表示,而没有父的就使用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。

Spring通过BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefiniton注册到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinition­Registry中读取配置信息。它们之间的关系如图

由此可知,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。而代码createBeanDefinition(className,parent)的作用就是实现此功能。

解析各种属性

当我们创建了bean信息的承载实例后,便可以进行bean信息的各种属性解析了,首先我们进入parseBeanDefinitionAttributes方法。parseBeanDefinitionAttributes方法是对element所有元素属性进行解析:

我们可以清楚地看到Spring完成了对所有bean属性的解析,这些属性中有很多是我们经常使用的,同时我相信也一定会有或多或少的属性是读者不熟悉或者是没有使用过的,有兴趣的读者可以查阅相关资料进一步了解每个属性。

解析子元素meta

在开始解析元数据的分析前,我们先回顾一下元数据meta属性的使用。

这段代码并不会体现在MyTestBean的属性当中,而是一个额外的声明,当需要使用里面的信息的时候可以通过BeanDefinition的getAttribute(key)方法进行获取。

对meta属性的解析代码如下:

解析子元素lookup-method

同样,子元素lookup-method似乎并不是很常用,但是在某些时候它的确是非常有用的属性,通常我们称它为获取器注入。引用Spring in Action中的一句话:获取器注入是一种特殊的方法注入,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。我们看看具体的应用。

到现在为止,除了配置文件外,整个测试方法就完成了,如果之前没有接触过获取器注入的读者们可能会有疑间:抽象方法还没有被实现,怎么可以直接调用呢?答案就在Spring为我们提供的获取器中,我们看看配置文件是怎么配置的。

在配置文件中,我们看到了源码解析中提到的lookup-method子元素,这个配置完成的功能是动态地将teacher所代表的bean作为getBean的返回值,运行测试方法我们会看到控制台上的输出:

lam Teacher

当我们的业务变更或者在其他情况下,teacher里面的业务逻辑已经不再符合我们的业务要求,需要进行替换怎么办呢?这是我们需要增加新的逻辑类:

同时修改配置文件:

至此,我们已经初步了解了lookup-method子元素所提供的大致功能,相信这时再次去看它的属性提取源码会觉得更有针对性。

上面的代码很眼熟,似乎与parseMetaElements的代码大同小异,最大的区别就是在if判断中的节点名称在这里被修改为LOOKUP_METHOD_ELEMENT。还有,在数据存储上面通过使用LookupOverride类型的实体类来进行数据承载并记录在AbstractBeanDefinition中的methodOverrides属性中。

解析子元素replaced-method

这个方法主要是对bean中replaced-method子元素的提取,在开始提取分析之前我们还是预先介绍下这个元素的用法。

方法替换:可以在运行时用新的方法替换现有的方法。与之前的look-up不同的是,

replaced-method不但可以动态地替换返回实体bean,而且还能动态地更改原有方法的逻辑。我们来看看使用示例。

好了,运行测试类就可以看到预期的结果了,控制台成功打印出“我替换了原有的方法"也就是说我们做到了动态替换原有方法,知道了这个元素的用法,我们再次来看元素的提取过程:

我们可以看到无论是look-up还是replaced-method都是构造了一个MethodOverride,并最终记录在了AbstractBeanDefinition中的methodOverrides属性中。而这个属性如何使用以完成它所提供的功能我们会在后续的章节进行详细的介绍,

解析子元素constructor-arg

对构造函数的解析是非常常用的,同时也是非常复杂的,也相信大家对构造函数的配置都不陌生,举个简单的小例子:

上面的配置是Spring构造函数配置中最基础的配置,实现的功能就是对HelloBean自动寻找对应的构造函数,并在初始化的时候将设置的参数传入进去。那么让我们来看看具体的XML解析过程。

对于constructor-arg子元素的解析,Spring是通过parseConstructorArgElements函数来实现的,具体的代码如下:

这个结构似乎我们可以想象得到,遍历所有子元素,也就是提取所有constructor-arg,然后进行解析,但是具体的解析却被放置在了另一个函数parseConstructorArgElement中,具体代码如下:

上面一段看似复杂的代码让很多人失去了耐心,但是,涉及的逻辑其实并不复杂,首先是提取constructor-arg上必要的属性(index、type、name)。

如果配置中指定了index属性,那么操作步骤如下。

1 解析Constructor-arg的子元素。

2 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。

3 将type、name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的indexedArgumentValues属性中。

如果没有指定index属性,那么操作步骤如下。

1. 解析constructor-arg的子元素。

2 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。

3 将type、name和index属性一并封装在ConstructorArgurnentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的genericArgumentValues属性中。

可以看到,对于是否制定index属性来讲,Spring的处理流程是不同的,关键在于属性信息被保存的位置。

那么了解了整个流程后,我们尝试着进一步了解解析构造函数配置中子元素的过程,进入parsePropertyValue:

从代码上来看,对构造函数中属性元素的解析,经历了以下几个过程。

1 略过description或者meta

2 提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则为在 constructor-arg上不存在以下情况。

同时既有ref属性又有value属性。

存在ref属性或者value属性且又有子元素。

3. ref属性的处理。使用RuntimeBeanReference封装对应的ref名称,如:

<constructor-arg ref= " a " >

4. value属性的处理。使用TypedStringValue封装,如:

<constructor-arg value=” a ” >

5 子元素的处理,如:

<constructor arg>
<map>
<entry key=” key ” value=” value” />
</map>
</constructor- arg>

而对于子元素的处理,例如这里提到的在构造函数中又嵌入了子元素map是怎么实现的呢?parseProrertySubElement中实现了对各种子元素的分类处理。

可以看到,在上面的函数中实现了所有可支持的子类的分类处理,到这里,我们已经大致理清构造函数的解析流程,至于更深入的解析读者有兴趣可以自己去探索。

解析子元素property

parsePropertyElement函数完成了对property属性的提取,property使用方式如下:

而具体的解析过程如下:

有了之前分析构造函数的经验,这个函数我们并不难理解,无非是提取所有property的子元素,然后调用parsePropertyElement处理,parsePropertyElement代码如下:

可以看到上面函数与构造函数注入方式不同的是将返回值使用PropertyValue进行封装,并记录在了BeanDefinition中的propertyValues属性中。

解析子元素qualifier

对于qualifier元素的获取,我们接触更多的是注解的形式,在使用Spring框架中进行自动注入时,Spring容器中匹配的候选Bean数目必须有且仅有一个。当找不到一个匹配的Bean时,Spring容器将抛出BeanCreationException异常,并指出必须至少拥有一个匹配的Bean。

Spring允许我们通过Qualifier指定注入Bean的名称,这样歧义就消除了,而对于配置方式使用如:

其解析过程与之前大同小异,这里不再重复叙述。

AbstractBeanDefinition属性

至此我们便完成了对XML文档到GenericBeanDefinition的转换,也就是说到这里,XML中所有的配置都可以在GenericBeanDefinition的实例类中找到对应的配置。

GenericBeanDefinition只是子类实现,而大部分的通用属性都保存在了AbstractBeanDefinition中,那么我们再次通过AbstractBeanDefinition的属性来回顾一下我们都解析了哪些对应的配置。

解析默认标签中的自定义标签元素

到这里我们已经完成了分析默认标签的解析与提取过程,或许涉及的内容太多,我们已经忘了我们从哪个函数开始的了,我们再次回顾下默认标签解析函数的起始函数:

我们已经用了大量的篇幅分析了BeanDefinitionHolder bdHolder = delegate.parseBean­DefinitionElement(ele)这句代码,接下来,我们要进行bdHolder = delegate.decorateBean­DefinitionlfRequired(ele,bdHolder)代码的分析,首先大致了解下这句代码的作用,其实我们可以从语义上分析:如果需要的话就对beanDefinition进行装饰,那这句代码到底是什么功能呢?其实这句代码适用于这样的场景,如:

当Spring中的bean使用的是默认的标签配置,但是其中的子元素却使用了自定义的配置时,这句代码便会起作用了。可能有人会有疑问,之前讲过,对bean的解析分为两种类型,一种是默认类型的解析,另一种是自定义类型的解析,这不正是自定义类型的解析吗?为什么会在默认类型解析中单独添加一个方法处理呢?确实,这个问题很让入迷惑,但是,不知道聪明的读者是否有发现,这个自定义类型并不是以Bean的形式出现的呢?我们之前讲过的两种类型的不同处理只是针对Bean的,这里我们看到,这个自定义类型其实是属性。好了,我们继续分析下这段代码的逻辑。

这里将函数中第三个参数设置为空,那么第三个参数是做什么用的呢?什么情况下不为空呢?其实这第三个参数是父类bean,当对某个嵌套配置进行分析时,这里需要传递父类beanDefinition。分析源码得知这里传递的参数其实是为了使用父类的scope属性,以备子类若没有设置scope时默认使用父类的属性,这里分析的是顶层配置,所以传递null。将第三个参数设置为空后进一步跟踪函数:

程序走到这里,条理其实已经非常清楚了,首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所对应的NamespaceHandler并进行进一步解析。在自定义标签解析的章节我们会重点讲解,这里暂时先略过。

我们总结下decorateBeanDefinitionlfRequired方法的作用,在decorateBeanDefinitionlfRequired 中我们可以看到对于程序默认的标签的处理其实是直接略过的,因为默认的标签到这里已经被处理完了,这里只对自定义的标签或者说对bean的自定义属性感兴趣。在方法中实现了寻找自定义标签并根据自定义标签寻找命名空间处理器,并进行进一步的解析。

注册解析的BeanDefinition

对于配置文件,解析也解析完了,装饰也装饰完了,对于得到的beanDinition已经可以满足后续的使用要求了,唯一还剩下的工作就是注册了,也就是processBeanDefinition函数中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry())代码 的解析了。

从上面的代码可以看出,解析的beanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中,而对于beanDefinition的注册分成了两部分:通过beanName的注册以及通过别名的注册。

通过BeanName注册BeanDefinition

对于beanDefinition的注册,或许很多人认为的方式就是将beanDefinition直接放入map中就好了,使用beanName作为key。确实,Spring就是这么做的,只不过除此之外,它还做了点别的事情。

上面的代码中我们看到,在对于bean的注册处理方式上,主要进行了几个步骤。

1. 对AbstractBeanDefinition的校验。在解析XML文件的时候我们提过校验,但是此校验非彼校验,之前的校验时针对于XML格式的校验,而此时的校验时针是对于AbstractBean­Definition的methodOverrides属性的。

2 对beanName已经注册的清况的处理。如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖。

3 加入map缓存。

4 清除解析之前留下的对应beanName的缓存。

通过别名注册BeanDefinition

在理解了注册bean的原理后,理解注册别名的原理就容易多了。

由以上代码中可以得知,注册alias的步骤如下。

I. alias与beanName相同情况处理。若alias与beanName并名称相同则不祔要处理并删除掉原有alias。

2. alias覆盖处理。若aliasName已经使用并已经指向了另一beanName则需要用户的设置进行处理。

3. alias循环检查。当A->B存在时,若再次出现A->C->B时候则会抛出异常。

4 注册alias。

通知监听器解析及注册完成

通过代码getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此工作,这里的实现只为扩展,当程序开发人员需要对注册BeanDefinition事件进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前在Spring中并没有对此事件做任何逻辑处理。

alias标签的解析

通过上面较长的篇幅我们终于分析完了默认标签中对bean标签的处理,那么我们之前提到过,对配置文件的解析包括对import标签、alias标签、bean标签、beans标签的处理,现在我们已经完成了最正要也是最核心的功能,其他的解析步骤也都是围绕第3个解析而进行的。在分析了第3个解析步骤后,再回过头来看看对alias标签的解析。

在对bean进行定义时,除了使用id属性来指定名称之外,为了提供多个名称,可以使用alias标签来指定。而所有的这些名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用的每一个组件能更容易地对公共组件进行引用。

然而,在定义bean时就指定所有的别名并不是总是恰当的。有时我们期望能在当前位置为那些在别处定义的bean引入别名。在XML配置文件中,可用单独的元素来完成bean别名的定义。如配置文件中定义了一个JavaBean:

 

考虑一个更为具体的例子,组件A在XML配置文件中定义了一个名为componentA的DataSource类型的bean,但组件B却想在其XML文件中以componentB命名来引用此bean。而且在主程序MyApp的XML配置文件中,希望以myApp的名字来引用此bean。最后容器加载3个XML文件来生成最终的ApplicationContext。在此情形下,可通过在配置文件中添加下列alias元素来实现:

可以发现,跟之前讲过的bean中的alias解析大同小异,都是将别名与beanName组成一对注册至registry中。这里不再提述。

import标签的解析

对于Spring配置文件的编写,我想,经历过庞大项目的人,都有那种恐惧的心理,太多的配置文件了。不过,分模块是大多数人能想到的方法,但是,怎么分模块,那就仁者见仁,智者见智了。使用import是个好办法,例如我们可以构造这样的Spring配置文件:

applicationContext.xml文件中使用import的方式导入有模块配置文件,以后若有新模块的加入,那就可以简单修改这个文件了。这样大大简化了配置后期维护的复杂度,并使配置模块化,易于管理。我们来看看Spring是如何解析import配置文件的呢?

上面的代码不难,相信配合注释会很好理解,我们总结一下大致流程便于读者更好地梳理, 在解析标签时,Spring进行解析的步骤大致如下。

1 获取resource属性所表示的路径。

2 解析路径中的系统属性,格式如"${user.dir}"。

3 判定location是绝对路径还是相对路径。

4. 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析。

5 如果是相对路径则计算出绝对路径并进行解析。

6 通知监听器,解析完成。

嵌入式beans标签的解析

对于嵌入式的beans标签,相信大家使用过或者至少接触过,非常类似于import标签所提供的功能,使用如下:

对于嵌入式beans标签来讲,并没有太多可讲,与单独的配置文件并没有太大的差别,无非是递归调用beans的解析过程,相信读者根据之前讲解过的内容已经有能力理解其中的奥秘了。

自定义标签的解析

在之前的章节中,我们提到了在Spring中存在默认标签与自定义标签两种,而在上一章中我们分析了Spring中对默认标签的解析过程,相信大家一定已经有所感悟。那么,现在将开始新的里程,分析Spring中自定义标签的加载过程。同样,我们还是先再次回顾一下,当完成从配置文件到Document的转换并提取对应的root后,将开始了所有元素的解析,而在这一过程中便开始了默认标签与自定义标签两种格式的区分,函数如下:

在本章中,所有的功能都是刚绕其中的一句代码delegate.parseCustomElement(root)开展的。

从上面的函数我们可以看出,当Spring拿到一个元素时首先要做的是根据命名空间进行解析,如果是默认的命名空间,则使用parseDefaultElement方法进行元素解析,否则使用parseCustom­Element方法进行解析。在分析自定义标签的解析过程前,我们先了解一下自定义标签的使用过程。

自定义标签使用

在很多情况下,我们需要为系统提供可配置化支持,简单的做法可以直接基于Spring的标准bean来配置,但配置较为复杂或者需要更多丰富控制的时候,会显得非常笨拙。一般的做法会用原生态的方式去解析定义好的XML文件,然后转化为配置对象。这种方式当然可以解决所有问题,但实现起来比较烦琐,特别是在配置非常复杂的时候,解析工作是一个不得不考虑的负担。Spring提供了可扩展Schema的支持,这是一个不错的折中方案,扩展Spring自定义标签配置大致需要以下几个步骤(前提是要把Spring的Core包加入项目中)。

创建一个需要扩展的组件。

定义一个XSD文件描述组件内容

创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。

创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。

编写Spring.handlers和Spring.schemas文件。

现在我们就按照上面的步骤带领读者一步步地体验自定义标签的过程。

1 首先我们创建一个普通的POJO,这个POJO没有任何特别之处,只是用来接收配置文件。

在上面的XSD文件中描述了一个新的targetNamespace,并在这个空间中定义了一个name为user的element,user有3个属性id,userName和email,其中email的类型为string。这3个类主要用于验证Spring配置文件中自定义格式。XSD文件是XML DTD的替代者,使用XML Schema语言进行编写,这里对XSD Schema不做太多解释,有兴趣的读者可以参考相关的资料。

3 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。

4 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。

以上代码很简单,无非是当遇到自定义标签

5 编写Spring.handlers和Spring.schemas文件,默认位置是在工程的/META-INF/文件夹下,当然,你可以通过Spring的扩展或者修改源码的方式改变路径。

到这里,自定义的配置就结束了,而Spring加载自定义的大致流程是遇到自定义标签然后就去Spring.handlers和Spring.schemas中去找对应的handler和XSD,默认位置是/META-INF/下,进而有找到对应的handler以及解析元素的Parser,从而完成了整个自定义元素的解析,也就是说自定义与Spring中默认的标准配置不同在于Spring将自定义标签解析的工作委托给了用户去实现。

6 创建测试配置文件,在配置文件中引入对应的命名空间以及XSD后,便可以直接使用自定义标签了。

在上面的例子中,我们实现了通过自定义标签实现了通过属性的方式将user类型的Bean赋值,在Spring中自定义标签非常常用,例如我们熟知的事务标签:tx()。

自定义标签解析

了解了自定义标签的使用后,我们带着强烈的好奇心来探究一下自定义标签的解析过程。

相信了解了自定义标签的使用方法后,或多或少会对自定义标签的实现过程有一个自己的想法。其实思路非常的简单,无非是根据对应的bean获取对应的命名空间,根据命名空间解析对应的处理器,然后根据用户自定义的处理器进行解析。可是有些事情说起来简单做起来难,我们先看看如何获取命名空间吧。

获取标签的命名空间

标签的解析是从命名空间的提起开始的,无论是区分Spring中默认标签和自定义标签还是区分自定义标签中不同标签的处理器都是以标签所提供的命名空间为基础的,而至于如何提取对应元素的命名空间其实并不需要我们亲自去实现,在org.w3c.dom.Node中已经提供了方法供我们直接调用:

public String getNamespaceURI (Node node) { return node.getNamespaceURI();

提取自定义标签处理器

有了命名空间,就可以进行NamespaceHandler的提取了,继续之前的parseCustomElement函数的跟踪,分析NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver(). resolve(namespaceUri), 在readerContext初始化的时候其属性namespaceHandlerResolver已经被初始化为了DefaultNamespaceHandlerResolver的实例,所以,这里调用的resolve方法其实调用的是DefaultNamespaceHandlerResolver类中的方法。我们进入DefaultNamespaceHandlerResolver的resolve方法进行查看。

上面的函数清晰地阐述了解析自定义NamespaceHandler的过程,通过之前的示例程序我们了解到如果要使用自定义标签,那么其中一项必不可少的操作就是在Spring.Handlers文件中配置命名空间与命名空间处理器的映射关系。只有这样,Spring才能根据映射关系找到匹配的处理器,而寻找匹配的处理器就是在上面函数中实现的,当获取到自定义的NamespaceHandler之后就可以进行处理器初始化并解析了。我们不妨再次回忆一下示例中对于命名空间处理器的内容:

当得到自定义命名空间处理后会马上执行namespaceHandler.init()来进行自定义Bean­DefinitionParser的注册。在这里,你可以注册多个标签解析器,当前示例中只有支持注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器进行解析。那么,根据上面的函数与之前介绍过的例子,我们基本上可以推断getHandlerMappings的主要功能就是读取Spring.handlers配置文件并将配置文件缓存在map中。

同我们想象的一样,借助了工具类PropertiesLoaderUtils对属性handlerMappingsLocation进行了配置文件的读取,handlerMappingsLocation被默认初始化为"META-INF/Spring.handlers"。

标签解析

得到了解析器以及要分析的元素后,Spring就可以将解析工作委托给自定义解析器去解析了。在Spring中的代码为:

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))

以之前提到的示例进行分析,此时的handler已经被实例化成我们自定义的MyNamespace­Handler了,而MyNamespaceHandler也已经完成了初始化的工作,但是在我们实现的自定义命名空间处理器中并没有实现parse方法,所以推断,这个方法是父类中的实现,查看父类NamespaceHandlerSupport中的parse方法。

解析过程中首先是寻找元素对应的解析器,进而调用解析器中的parse方法,那么结合示例来讲,其实就是首先获取在MyNameSpaceHandler类中的init方法中注册的对应的UserBean­DefinitionParser实例,并调用其parse方法进行进一步解析。

虽说是对自定义配置文件的解析,但是,我们可以看到,在这个函数中大部分的代码是用来处理将解析后的AbstractBeanDefinition转化为BeanDefinitionHolder并注册的功能,而真正去做解析的事情委托给了函数parselnternal,正是这句代码调用了我们自定义的解析函数。

在parselnternal中并不是直接调用自定义的doParse函数,而是进行了一系列的数据准备,包括对beanClass、scope、lazylnit等属性的准备。

回顾一下全部的自定义标签处理过程,虽然在实例中我们定义UserBeanDefinitionParser,但是在其中我们只是做了与自己业务逻辑相关的部分。不过我们没做但是并不代表没有,在这个处理过程中同样也是按照Spring中默认标签的处理方式进行,包括创建BeanDefinition以及进行相应默认属性的设置,对于这些工作Spring都默默地帮我们实现了,只是暴露出一些接口来供用户实现个性化的业务。通过对本章的了m年,相信读者对Spring中自定义标签的使用以及在解析自定义标签过程中Spring为我们做了哪些工作会有一个全面的了解。到此为止我们已经完成了Spring中全部的解析工作,也就是说到现在为止我们已经理解了Spring将bean从配置文件到加载到内存中的全过程,而接下来的任务便是如何使用这些bean,下一章将介绍bean的加载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值