前言
在Spring的XML配置中有两大类bean声明,一个是默认的,一个是自定义。两种格式的读取及解析天差万别,自定义的需要用户实现一些接口及配置。其中对于根节点、子节点如果是默认命名空间则采用parseDefaultElement方法进行解析,否则采用delegate.parseCustomElement对自定义命名空间进行解析。而判断命名空间采用node.getNamespaceURI()来获取,并与Spring中固定的命名空间进行对比,一致是默认,否则是自定义。
格式如下:
1、默认 <bean id ="test" class="test.TestBean" />
2、自定义 <tx:annotation-driven />
一、默认标签的解析
默认标签的解析是在parseDefaultElement函数中进行,函数中的功能分别对4中标签(import、alias、bean、beans)做了不同的解析。其中bean标签的解析最为复杂也最为重要。
bean标签的解析及注册
对bean标签的解析函数为processBeanDefinition(ele,delegate).解析大概逻辑为:
1、 首先委托BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法进行元素解析。返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法之后,bdHolder实例已经包含配置文件中配置的各种属性了,比如name,id,alias之类的属性
2、当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签解析。
3、解析完成后,需要对解析后的bdHolder进行注册,注册委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法
4、最后发出响应事件,通知相关的监听器,这个bean已经加载完成了
默认的beanName初始为bean的id,若name存在为name,如果不存在beanName,那么根据Spring中提供的命名规则为当前bean生成对应的beanName.


用于属性承载的BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBean-Definition以及GenericBeanDefinition,三种实现均继承了AbstractBeanDefinition。
其中BeanDefinition是配置文件<bean>元素标签在容器中的内部表示形式。<bean>元素标签拥有class,scope,lazy-init等配置属性,BeanDefinition则提供了相应的beanClass,scope,lazyInit属性,BeanDefinition和<bean>中的属性一一对应。
RootBeanDefinition是最为常用的实现类,对应一般性的<bean>标签;GenericBeanDefinition是2.5版本加入的bean文件配置属性定义类,是一站式服务类。
在配置文件中可以定义父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefinition表示,而没有父<bean>的<bean>就用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。
GenericBeanDefinition只是子类实现,而大部分的通用属性都保存在了AbstractBeanDefinition。
总结
Spring通过BeanDefinition将配置文件中的bean标签配置信息转换成容器的内部表示,并将BeanDefinition注册到BeanDefinitionRegistry中。spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息。
其他标签的解析步骤:
1、解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。(createBeanDefinition)
2、对bean信息的各种属性解析。(parseBeanDefinitionAttributes)属性有scope、singleton、abstract、lazy-init、autowire、dependency-check、depends-on、autowire-candidate、primary、init-method、destory-method、factory-method、factory-bean属性。
3、解析子元素meta。是一个额外的说明,当需要使用meta信息,可通过BeanDefinition的getAttribute(key)方法获取。
4、解析子元素lookup-method属性。获取器注入。
5、解析replace-method属性。方法替换,可动态替换返回实体bean,还可以替换原有方法的逻辑。
6、解析子元素(构造函数)construstor-arg.
7、解析子元素property
8、解析qualifier子元素。qualifier元素的获取,更多是注解的形式。
解析默认标签中的自定义标签元素,首先是获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所对应的命名空间处理器并进行进一步的解析。自定义标签元素在默认标签中实际上就是属性。
注册解析的BeanDefinition
BeanDefinition的注册分为两部分:beanName、别名。注册完成之后通知监听器已经注册完成。
beanName注册的步骤:
1、对AbstractBeanDefinition的校验。对methodOverrides属性。
2、对beanName已经注册的情况处理。如果设置不允许覆盖,则抛出异常。
3、加入map缓存。
4、清除解析之前留下的对应beanName的缓存。
通过别名的注册步骤:
1、alias与beanName相同情况处理。相同不需要处理,删除掉原有alias。
2、alias覆盖处理。若是aliasName已经存在并指向另外一个beanName就需要用户设置处理。
3、alias循环检查。出现就抛出异常
4、注册alias。
alias标签的解析
在对bean进行定义时,除了使用id属性来指定名称,为了提供多个名称,可以使用alias标签来指定,所有名称都指向同一个bean。增加别名可以方便不同对象来调用。
import标签的解析
import标签主要用于导入其他配置文件。
解析过程步骤大致为:
1、获取resouce属性所标识的路径。
2、解析路径中的系统属性。
3、判断location是绝对路径还是相对路径。
4、如果是绝对路径则递归调用bean的解析过程,进行另一次的解析。
5、如果是相对路径则计算出绝对路径并进行解析。
6、通过监视器,解析完成。
嵌入式beans标签的解析
类似于import标签的解析,是递归调用beans的解析过程。
二、自定义标签的解析
前言提到Spring根据命名空间来区分是默认标签与自定义标签。在很多情况下,我们需要为系统提供可配置化的支持,通过Spring提供的可扩展Schema的支持(XSD)来实现想要的配置。常见的自定义标签为事务标签(<tx:annotation-driven>)
扩展Spring自定义标签配置大致步骤:
1、创建一个需要扩展的组件。
2、定义一个XSD文件用于描述组件内容。
3、创建一个文件,实现BeanDefinitionParser接口,用于解析XSD文件中的定义和组件定义。
4、创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。
5、编写Spring.handlers和Spring.schemas文件。
总结
Spring加载自定义的大致流程是遇到自定义标签,根据bean获取对应的命名空间,再由命名空间去查找Spring.handlers和Spring.schemas去找对应的handler和XSD,进而找到对应的handler和解析元素的Psrser,从而完成整个自定义元素的解析。简单来说Spring将自定义标签解析工作委托给用户实现。
注意:
1、在Spring.handlers中需要配置命名空间与命名空间处理器的映射关系。
2、当得到自定义命名空间处理后会马上执行命名空间初始化(进行自定义bean的注册)。
3、getHandlerMappings主要功能是读取配置文件并将配置文件信息缓存到map中。(过程中有保证同步synchronized)
4、需要先注册bean才能调用父类方法handler.parse的解析方法(自定义解析类)。另外自定义解析函数parseInternal并不是直接调用自定义解析函数doParse,而是先进行一系列的数据准备,包括对beanClass、scope、lazyInit等属性准备。