bean的解析分为默认标签和用户自定义标签,前面已经讲过了默认标签的解析过程.今天就针对自定义标签的解析进行一个介绍.郝佳在sping源码深度解析书中说自定义标签主要是为了支持系统可配置功能.我个人认为,自定义标签的使用是为了提高spring的灵活性,用户可以根据自己的应用特点来定义标签和解析器.spring除了管理默认标签的bean还可以为客户管理这些bean.
自定义标签的使用
为了讲解自定义标签的解析过程,首先我们先回顾自定义标签的使用,熟悉标签使用对于源码的理解有很大帮助.自定义标签一般分为5个步骤:
- 创建一个需要拓展的组件
- 为该组件定义一个约束文档XSD文件
- 定义组件解析器,实现BeanDefinitionParser接口,用来解析xsd文件中的定义和组件定义
- 定义组件处理器,扩展NamespaceHandlerSupport,目的是将组件注册到spring容器中
- 编写命名空间与处理器handler的映射文件Spring.handlers,以及xsd的路径文件Spring.schemas用于查找xsd.
1) 创建 POJOpublic class User{
private String userName;
private String email;
}
2) 创建约束模式文件xsd,此步骤省略
3) 定义组件解析器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class UserBeanDefinitionParser extends AstractSingleBeanDefinitionParser{
//指定该bean的类型
proctected Class getBeanClass(Element element){
return User.class;
}
proctected void doParser(Element element,BeanDefinitionBuilder bean){
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
if(StringUtils.hasText(userName){
bean.addPropertyValue("userName",userName);
}
if(StringUtils.hasText(email){
bean.addPropertyValue("email",email);
}
}
}
4) 创建处理器,注册解析器1
2
3
4
5public class MyNamespaceHandler extends NamespaceHandlerSupport{
public void init(){
registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
}
}
5) 编写文件
Spring.handlers
http\://www.lexueba.com/schema/user=MyNamespaceHandler
Spring.schemas
http\://www.lexueba.com/schema/user.xsd=META-INF/Spring-test.xsd
自定义标签的解析
自定义标签的解析首先是使用DOM获取节点的命名空间,然后根据命名空间和Spring.handlers配置文件得到处理器,然后交给处理器处理.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//containingBd 为父类bean, 默认为null 之类.
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
//根据命名空间找到对应的NamespaceHandler
//DefaultNamespaceHandlerResolver.resolve;
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//调用自定义的NamespaceHandler进行解析
//NamespaceHandlerSupport;
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
获取处理器
spring通过懒加载的方式将Spring.handlers中所有命名空间与处理器的映射保存到CocurrentHashMap中,然后根据命名空间获取处理器.因为第一次保存在缓冲中的处理器只是对应的处理器类名,当用户需要使用时首先创建处理器对象,并进行初始化init(),初始化的目的就是为了将该命名空间中的所有解析器注册到spring中.最后保存这个对象到缓冲中,并返回处理器handler,以后调用同一个命名空间时直接返回处理器对象就不用创建新对象.
1 | public NamespaceHandler resolve(String namespaceUri) { |
标签解析
获取了处理器之后,会根据Element的标签从容器中获取对应的解析器,然后标签进行解析.采用建造者模式解析并构建一个BeanDefinition.最后将BeanDefinition转化为BeanDefinitionHolder并注册到容器中.
1 | public BeanDefinition parse(Element element, ParserContext parserContext) { |
parse方法大部分都是对解析后的BeanDefinition进行处理.这里的解析主要是由parseInternal()方法执行.该方法是解析和构建BeanDefinition过程,构建该对象采用建造者模式(Builder):
1 | protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { |