XML自定义标签的解析

终于提到自定标签的解析了,这里可以从DefaultBeanDefinitionDocumentReader.parseBeanDefinitions中调用的parseCustomElement方法开始!

自定义标签的使用

Spring提供了可扩展Schema的支持,这是一个不错的折中方案,扩展Spring自定义标签配置,大致需要以下几个步骤(前提是要把Spring的Core包加入项目中)。

  1. 创建一个需要扩展的组件
  2. 定义一个XSD文件描述文件描述组件内容
  3. 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。
  4. 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。
  5. 编写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中都是怎么实现的了吧!?

results matching ""

    No results matching ""