自定义标签的解析

718 阅读4分钟

一、前言

我们在默认标签解析中分析了Spring对默认标签的解析,主要流程是加载配置文件,获取xml文件的校验方式(DTD/XSD),并加载代表xml的Document对象(XML解析的那一套),然后解析Document对象的子对象,对于bean、import、alias、beans标签分情况处理,如果涉及到标签的嵌套,则递归处理,对标签解析完后,将beanDefinition注册到容器中。

除了自定义标签之外,Spring中的其他功能,比如注解借助于<context:component-scan>生命、事务借助于<tx:annotation-driven/>标签,aop借助于aop相关标签等,这些都称之为自定义标签,本文将主要分析自定义标签的解析。

Spring框架的开发者使用了“对扩展开放、对修改关闭”的思想,这使得使用者可以在不修改Spring的前提下,扩展相关功能。

二、自定义标签源码分析

默认标签解析中对于默认标签和自定义标签解析的入口:

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);
		}
	}

@Nullable
public BeanDefinition parseCustomElement(Element ele) {
      return parseCustomElement(ele, null);
}

@Nullable
  public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    //拿到标签的命名空间,比如<context:component-scan>的命名空间是http://www.springframework.org/schema/context
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
      return null;
    }
    //每一种命名空间,对应一种命名空间解析器,用于解析标签
    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));
  }

下面分析DefaultNamespaceHandlerResolver.resolve(String namespaceUrl):

public NamespaceHandler resolve(String namespaceUri) {​
    //拿到所有的命名空间->解析器的映射,这些文件定义在相对路径:META-INF/spring.handlers下。
    ​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);
        if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
          throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
              "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
        }
        NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
        //执行命名空间的初始化方法
        namespaceHandler.init();
        //在map中缓存该命名空间处理器,方便下次查询
        handlerMappings.put(namespaceUri, namespaceHandler);
        return namespaceHandler;
      }
      catch (ClassNotFoundException ex) {
        throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
            "] for namespace [" + namespaceUri + "]", ex);
      }
      catch (LinkageError err) {
        throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
            className + "] for namespace [" + namespaceUri + "]", err);
      }
    }
  }​

//拿到当前项目下,名称为spring.handlers的所有文件,并使用Properties的方式解析成键值对。
private Map<String, Object> getHandlerMappings() {
    Map<String, Object> handlerMappings = this.handlerMappings;
    if (handlerMappings == null) {
      synchronized (this) {
        handlerMappings = this.handlerMappings;
        if (handlerMappings == null) {
          if (logger.isTraceEnabled()) {
            logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
          }
          try {
            Properties mappings =
                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
            if (logger.isTraceEnabled()) {
              logger.trace("Loaded NamespaceHandler mappings: " + mappings);
            }
            handlerMappings = new ConcurrentHashMap<>(mappings.size());
            CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
            this.handlerMappings = handlerMappings;
          }
          catch (IOException ex) {
            throw new IllegalStateException(
                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
          }
        }
      }
    }
    return handlerMappings;
  }​

注:spring.handlers中定义了命名空间与命名空间处理器的映射关系

以spring-context为例:


如上图所示,http\://www.springframework.org/schema/context对应的明明空间解析器是ContextNamespaceHandler,其init()方法如下,发现其中注册了component-scan标签的解析器ComponentScanBeanDefinitionParser,因此对于component-scan标签来说,将调用ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext)解析标签。

@Override
  public void init() {
    registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
    registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
    registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
    registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
    registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
  }​

三、总结

3.1. Spring自定义标签的逻辑

经过以上代码的分析,Spring解析自定义标签的逻辑如下:

  1. 获取标签的命名空间;
  2. 根据命名空间查询对应的命名空间处理器,如果命名空间处理器未被缓存,则扫描spring-handlers文件,使用properties的方式存储命名空间->命名空间处理器的映射关系,并调用命名空间的init()方法,初始化命名空间处理器;
  3. 使用命名空间处理器解析标签。

3.2. 扩展spring标签的流程

如果需要扩展spring命名空间,支持自定义标签,开发过程:

  1. 确定xsd文件,用于校验标签配置正确与否;
  2. 实现NamespaceHandlerSupport,用于引入实现的BeanDefinitionParser(用于解析xml中自定义的标签);
  3. 实现BeanDefinitionParser;
  4. 在项目的META-INF目录下面创建spring.handlers文件,文件的内容是命名空间与命名解析器的映射关系。