Spring框架中自定义XML标签

NamespaceHandler/BeanDefinitionParser

Posted by Jay on January 14, 2019

Spring框架中自定义XML标签及解析过程分析

转自李懿名-spring自定义xml标签

要实现自定义的xml配置,需要有两个默认spring配置文件来支持。一个是spring.schemas,一个是spring.handlers,前者是为了验证你自定义的xml配置文件是否符合你的格式要求,后者是告诉spring该如何来解析你自定义的配置文件。

自定义标签涉及的核心接口为:

  • NamespaceHandler——命名空间处理器
  • BeanDefinitionParser——BeanDefinition解析器 实际使用的时候,一般分别继承类:

  • NamespaceHandlerSupport:init()
  • AbstractBeanDefinitionParser:parse()

spring事务标签为例(前提:要了解bean注册过程):

一、配置文件

  • spring.handlers

  • spring.schemas

二、调用逻辑

org.springframework.context.support.AbstractApplicationContext#refresh 
org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory 
org.springframework.context.support.AbstractApplicationContext#refreshBeanFactory 
org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory 
org.springframework.context.support.AbstractRefreshableApplicationContext#loadBeanDefinitions
org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
  • 在第3步中 registerBeanDefinitions 调用 createReaderContext
// class XmlBeanDefinitionReader
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	int countBefore = getRegistry().getBeanDefinitionCount();
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}
  • 第4步 createReaderContext 中调用getNamespaceHandlerResolver方法获取: NamespaceHandlerResolver
// class XmlBeanDefinitionReader
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;
}

new 一个 DefaultNamespaceHandlerResolver

 // class XmlBeanDefinitionReader
 protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
 	return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
 }

DEFAULT_HANDLER_MAPPINGS_LOCATION: META-INF/spring.handlers

// DefaultNamespaceHandlerResolver
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
    this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
    Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
    this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    this.handlerMappingsLocation = handlerMappingsLocation; // 重要,命名空间处理器位置
}

DefaultNamespaceHandlerResolver的属性:private volatile Map handlerMappings保存的是 namespace URINamespaceHandler的映射

  • 第5步调用DefaultBeanDefinitionDocumentReader(父类BeanDefinitionDocumentReader)的 registerBeanDefinitions 方法
// BeanDefinitionDocumentReader
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }

    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}

delegate.parseCustomElement(ele); 这一行代码是解析自定义标签

// class DefaultBeanDefinitionDocumentReader
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root); // 解析自定义标签
    }
}
  1. 根据元素标签获取:NamespaceHandler,并调用其 init 方法
  2. 调用 NamespaceHandlerparse 方法,在 parse方法里解析标签,然后返回BeanDefinition,后续会被spring注册
 // class BeanDefinitionParserDelegate
public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

三、定制点

  • 定制 init 方法:一般在init方法中注册BeanDefinitionParser, 存储在其父类NamespaceHandlerSupport的属性:parsers

  • 定制 parse 方法:一般在parse方法中返回BeanDefinition