XML自定义标签的解析
终于提到自定标签的解析了,这里可以从DefaultBeanDefinitionDocumentReader.parseBeanDefinitions中调用的parseCustomElement方法开始!
自定义标签的使用
Spring提供了可扩展Schema的支持,这是一个不错的折中方案,扩展Spring自定义标签配置,大致需要以下几个步骤(前提是要把Spring的Core包加入项目中)。
- 创建一个需要扩展的组件
- 定义一个XSD文件描述文件描述组件内容
- 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。
- 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。
- 编写Spring.Handler文件和Spring.schemas文件。
关于实例这里就不列举了
自定义标签解析
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 获取自定义的命名空间
String namespaceUri = getNamespaceURI(ele);
// 从读取器上下文中,通过命名空间获取相应的标签处理Handler
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进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
获取标签的命名空间
这个获取方法的实现w3c已经帮我们实现了,我们在XML文档中定义了我们的文档命名空间。
提取自定义的标签处理器
有了命名空间,接下来就是查找Handler了,进入代码查看我们发现用于查找的NamespaceHandlerResolver在ReaderContext的读取上下文中,可以指定,默认是createDefaultNamespaceHandlerResolver!
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
我们来看一下resolve方法。首先获取所有已经配置的Handler映射,进入那个方法仔细研究你会发现就是先将默认路径(可以更改)WEB-INF/spring.handlers的文件内容读入系统属性中,然后设置Map,放入DefaultNamespaceHandlerResolver的handlerMappings中。加载的时候读取得到的是String,然后加载该类,实例化一个对象(所以默认的构造函数是必不可少的),实例化之后,调用了init方法!然后替换handlerMappings中原来的字符串,返回对应的实例。
所以我们在定义自己的Handler的时候,要继承NamespaceHandlerSupport(继承了NamespaceHandler),然后需要做自己的动作的时候,需要去重写init方法来实现自己的逻辑,这里就是关键所在。
标签解析
最后调用handler的parse方法进行,我们使用的是继承NamespaceHandlerSupport,看一下他的parse实现。它首先去找一个parser类的实例,然后调用这个类的实例的parse方法进行处理。
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 获取元素名称,也就是你定义的元素的名称
String localName = parserContext.getDelegate().getLocalName(element);
// 根据这个名称找到对应的解析器
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
这里你也许会奇怪,这个Parser是怎么找到的呢?我们看一下NamespaceHandlerSupport的实现类,你会发现里面有registerBeanDefinitionParser的方法,那么我们定义的handler中并没有注册Parser呀!所以呢,就要提到上面说的继承init方法,在里面调用registerBeanDefinitionParser方法注册Parser,我们来看一下代码:
public class MyNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("customElement", new CustomBeanDefinitionParser());
}
}
ok,Parser什么的都找到了,再来看一下我们定义的Parser吧!它继承AbstractSingleBeanDefinitionParser!我们看一下代码:
public class CustomBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
// Element对应的类
protected Class getBeanClass(Element element) {
return CustomElement.class;
}
//从element中解析并提取对应的元素
protected void doParser(Element element, BeanDefinitionBuilder bean) {
String elementName = element.getAttribute("name");
String desc = element.getAttribute("desc");
// 将提取到的数据放入BeanDefinitionBuilder中,待到完成所有bean的解析后统一注册到beanFactory中
if(StringUtils.hasText(elementName)) {
bean.addPropertyValue("name", elementName);
}
if(StringUtils.hasText(desc)) {
bean.addPropertyValue("desc", desc);
}
}
}
发现没有定义parser方法,他是父类AbstractBeanDefinitionParser的。这个方法首先调用parseInterval方法解析,后面的处理就是检查、注册、通知监听器等。再来看一下parseInterval方法。它在AbstractSingleBeanDefinitionParser中定义。
这个方法做了一些初始化的工作,设置一些常规的BeanDefinition的属性,然后掉用了doParse方法解析,最后注册。贴一下源码:
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
// 定义一个BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 父标签的名称,默认为空,设置需要自定义Parser重写getParentName方法
String parentName = getParentName(element);
if (parentName != null) {
// 设置parent name
builder.getRawBeanDefinition().setParentName(parentName);
}
// 获取自定义标签中的class,此时会调用自定义解析器的方法,最好重写getBeanClass方法
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
// 或者通过方法名来获取,自定义解析器需要重写这个方法
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
if (parserContext.isNested()) {
// Inner bean definition must receive same scope as containing bean.
// 如果存在父类Bean,则使用父类Bean的scope属性
builder.setScope(parserContext.getContainingBeanDefinition().getScope());
}
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
// 配置延迟加载
builder.setLazyInit(true);
}
// 调用子类重写的doParse方法,进行解析
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
doParse的例子可以查看上面的example代码!
到这里大家应该明白了,上面提到自定义标签的实现步骤在Spring中都是怎么实现的了吧!?