(2-2)、解析自定义命名空间下的结点

自定义命名空间下的结点解析主要有以下几个步骤:

  1. 获取结点所属的命名空间;
  2. 根据获取到的命名空间找到命名空间所对应的命名空间处理器;
  3. 使用命名空间处理器去解析结点中所包含的数据并转化为BeanDefinition;
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 获取对应的命名空间
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    // 根据命名空间找到对应的NamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    // ......
    // 调用NamespaceHandler进行解析
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
(2-2-1)查找命名空间所对应的NameSpaceHandler
public NamespaceHandler resolve(String namespaceUri) {
    // 获取所有的命名空间与对应的命名空间处理器之间的映射关系
    // key: 命名空间URI,value:命名空间处理器/命名空间处理器所对应的全路径名
    Map<String, Object> handlerMappings = getHandlerMappings();

    // 获取当前命名空间所对应的命名空间处理器
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        // 已经被实例化,则直接返回
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        // 对应的命名空间处理器还未被实例化,此时它是一个字符串,代表类的全路径名
        String className = (String) handlerOrClassName;
        try {
            // 实例化命名空间处理器
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            
            // 调用命名空间处理器的init方法
            namespaceHandler.init();

            // 将实例化的对象放入缓存,方便下次使用
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        // ......
    }
}

查找命名空间所对应的NameSpaceHandler中有两个主要的方法,一个是获取命名空间与命名空间处理器之间的映射关系,即:getHandlerMappings();另一个是调用命名空间处理器的init()方法,这个方法的主要作用是注册当前命名空间下不同标签所对应的解析器,以便解析式根据命名空间和标签找到对应的解析器。
什么是命名空间和标签?

<tx:advice></tx:advice>
其中:tx就是命名空间;advice就是标签

解析getHandlerMappings()方法:

private Map<String, Object> getHandlerMappings() {
    Map<String, Object> handlerMappings = this.handlerMappings;
    // 有则直接返回,没有则进行加载
    if (handlerMappings == null) {
        synchronized (this) {
            handlerMappings = this.handlerMappings;
            // 双重校验机制
            if (handlerMappings == null) {
                // ......
                try {
                    // this.handlerMappingsLocation指的是映射关系所存在的位置
                    // 在创建NamespaceHandlerResolver对应的时候已经在构造函数中指定为:DEFAULT_HANDLER_MAPPINGS_LOCATION
                    // public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
                    // 即映射关系存放在"META-INF/spring.handlers"中
                    Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    // ......
                    
                    handlerMappings = new ConcurrentHashMap<>(mappings.size());
                    // 
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    this.handlerMappings = handlerMappings;
                }
                // ......
            }
        }
    }
    return handlerMappings;
}

查看spring-tx模块META-INF/spring.handlers下存放的映射关系:

spring 读取局域网共享文件夹_spring

可以看到映射关系表示形式为:NameSpaceURI=NameSpaceHandler

通过这里也验证了为什么上面根据命名空间URI获取对应的命名空间处理器时获取到的可能为命名空间处理器的全路径名。

解析init()方法:
首先查看几个NameSpaceHandler的init()方法实现:

  • MvcNamespaceHandler
public void init() {
    registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
    registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
    registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
    registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
    registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
    registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
    registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
    registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
    registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
}
  • TxNamespaceHandler
public void init() {
    registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
    registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
    registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}

通过上面的示例可以看出,init()方法的主要工作就是注册当前命名空间下不同标签所对应的BeanDefinition解析器,也就是说每一种不同的标签都会有一个与之对应的解析器。
BeanDefinitionParser接口:

public interface BeanDefinitionParser {
	BeanDefinition parse(Element element, ParserContext parserContext);
}

查看BeanDefinitionParser接口发现,其中只有一个parse()方法,这个也就是真正执行解析操作得到BeanDefinition的方法

所以如果要自定义标签,则需要实现这个接口并自己提供标签解析方式。

(2-2-2)解析标签得到BeanDefinition

从2-2-1中已经得到命名空间处理器,接下来就是想办法获取到当前标签所对应的BeanDefinitionParser

首先看下NameSpaceHandler接口的结构:

public interface NamespaceHandler {
	void init();
	BeanDefinition parse(Element element, ParserContext parserContext);
	BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}

其中,init()方法用来注册标签与解析器之间的映射关系,见名知意,parse方法就是调用标签解析器进行解析的方法。

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 获取解析器
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    // 调用解析器的parse()方法进行解析
    return (parser != null ? parser.parse(element, parserContext) : null);
}

如何找到元素所对应的解析器?

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 元素名称
    String localName = parserContext.getDelegate().getLocalName(element);
    // 根据元素名称找到对应的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    // ......
    return parser;
}

找元素所对应的解析器步骤:

  1. 得到元素名称
  2. 根据元素名称从this.parsers中获取解析器

private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();

可以看到parsers是一个Map结构,key为元素名称,value为元素所对应的解析器
那么这些映射关系是何时写入到parsers中的呢?

在(2-2-1)中init()方法时提到,命名空间处理器实例化后会调用init()方法,init()方法的主要作用是注册当前命名空间下各个标签所对应的BeanDefinitionParser,所以映射关系应当是在init()方法中写入。所以查看init()方法中调用的registerBeanDefinitionParser()方法。

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    this.parsers.put(elementName, parser);
}

可以看到,registerBeanDefinitionParser方法中执行的操作正是向parser中写入映射关系。

由此,已经得到BeanDefinitionParser,下一步就是进行解析操作,各个自定义标签各有各的实现,不再具体分析~~~End。