第三篇 再读Spring 之 解析自定义XML标签


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 为何要自定义
  • 标签的异同
    • 关于XML文件
    • 标签分类
  • 解析过程
    • 标签识别
    • handler管理
      • handler注册
      • handler使用
  • 怎么自定义
  • 小结


作为一个框架级组件,Spring充分考虑了框架的扩展性,对于XML标签也不例外。博主结合个人对Spring源码的阅读和理解聊聊Spring对自定义XML标签的处理。


为何要自定义

显然,自定义可以开发人员扩充标签库。而扩充标签库为了优雅地扩展Spring容器的功能。从职责划分上来说,具体的功能不是Spring框架关心的,但扩展的方式(扩展标准)是框架应该提供的。这些扩展机制就就像大树的树干,有了这些树干的支持,Spring生态下的其他组件才能在树干上开发结果。从这个角度上说,Spring的核心是IOC。AOP只是在IoC框架下的一种增强扩展,毕竟在XML配置文件中,AOP的支持也是通过扩展标签完成的。

标签的异同

关于XML文件

完善的XML文件,包含对校验文件的声明,通常是一个http链接,指向.dtd或者.xsd文件。校验文件用于描述.xml文档的结构以及相关的约束。此外,单个XML文件中可以使用多个校验文件,每个校验文件负责XML文件中相关部分的校验。那相关性是如何体现的呢?

命名空间。也就是不同的校验文件对应一个命名空间,以该命名空间作为前缀的元素,对应的校验文件校验。此外,为了简化标签识别,通常会有一个命名空间作为默认命名空间,体现在标签上就是标签没有前缀。

标签分类

Spring配置文件中标签可以分为2大类,一类是Spring官方提供的,一类是咱们自己提供的。但即便是官方提供的标签,也分散在多个校验文件(命名空间 namespace)下。所以自定义标签指的是框架使用者自己的命名空间下的标签。Spring本身的XML标签IOC相关是根,其他标签都是通过标签扩展机制扩展而来。

个人看来,即使是扩展标签也包含2类,类似于class级别和属性级别,扩展标签作用对象也有property级别和Bean级别的区分。

解析过程

元素颗粒度的标签解析类 BeanDefinitionParserDelegate 在解析元素时包括2类处理。一类是IOC相关的基本逻辑,class和property识别。另一类是有关自定义标签的处理。

标签识别

元素所在名称空间,如果不是Spring默认的名称空间,则通过名称空间解析器查找对应的handler.

handler管理

handler注册

Spring中通过NamespaceHandlerResolver来处理handler的查找逻辑。

public interface NamespaceHandlerResolver {

	/**
	 * Resolve the namespace URI and return the located {@link NamespaceHandler}
	 * implementation.
	 * @param namespaceUri the relevant namespace URI
	 * @return the located {@link NamespaceHandler} (may be {@code null})
	 */
	@Nullable
	NamespaceHandler resolve(String namespaceUri);
}

具体实现类为

DefaultNamespaceHandlerResolver

关键方法resolve的实现如下

public NamespaceHandler resolve(String namespaceUri) {
		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();
				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);
			}
		}
	}

基本上包括这么几个步骤:

  1. getUriMapping, 获取所有URI对应handler的className。默认加载所有META-INF/spring.handlers中定义的uri和handler映射.
  2. 从handlerMapping中获取对应的handler实例,获取失败则继续;
  3. 从UriMapping中获取当前URI对应的className,并通过反射创建对象,然后调用.init() 方法完成初始化;
  4. 将handler对象放入handlerMapping中;
  5. 返回新创建的handler对象;

handler使用

NameSpaceHandler接口定义了三个方法,init, decorate, parse

public interface NamespaceHandler {

	/**
	 * Invoked by the {@link DefaultBeanDefinitionDocumentReader} after
	 * construction but before any custom elements are parsed.
	 * @see NamespaceHandlerSupport#registerBeanDefinitionParser(String, BeanDefinitionParser)
	 */
	void init();

	/**
	 * Parse the specified {@link Element} and register any resulting
	 * {@link BeanDefinition BeanDefinitions} with the
	 * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
	 * that is embedded in the supplied {@link ParserContext}.
	 * 

Implementations should return the primary {@code BeanDefinition} * that results from the parse phase if they wish to be used nested * inside (for example) a {@code } tag. *

Implementations may return {@code null} if they will * not be used in a nested scenario. * @param element the element that is to be parsed into one or more {@code BeanDefinitions} * @param parserContext the object encapsulating the current state of the parsing process * @return the primary {@code BeanDefinition} (can be {@code null} as explained above) */ @Nullable BeanDefinition parse(Element element, ParserContext parserContext); /** * Parse the specified {@link Node} and decorate the supplied * {@link BeanDefinitionHolder}, returning the decorated definition. *

The {@link Node} may be either an {@link org.w3c.dom.Attr} or an * {@link Element}, depending on whether a custom attribute or element * is being parsed. *

Implementations may choose to return a completely new definition, * which will replace the original definition in the resulting * {@link org.springframework.beans.factory.BeanFactory}. *

The supplied {@link ParserContext} can be used to register any * additional beans needed to support the main definition. * @param source the source element or attribute that is to be parsed * @param definition the current bean definition * @param parserContext the object encapsulating the current state of the parsing process * @return the decorated definition (to be registered in the BeanFactory), * or simply the original bean definition if no decoration is required. * A {@code null} value is strictly speaking invalid, but will be leniently * treated like the case where the original bean definition gets returned. */ @Nullable BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext); }

  1. init 方法用于初始化NameSpaceHandler
  2. parse 方法用于对Element级别数据做处理返回BeanDefinition,必要时可能会替换掉Spring原始逻辑产生的BeanDefinition.
  3. decorate 方法用于对Node级别数据做增强处理。和Element的区别是,Node可能是Element级别,也可能是Attr级别的。

到这里, 各方法的作用已经明确了。

怎么自定义

到这里,我们已经明确了NamespaceHandler的注册和使用方式。基本上包含这么几个过程:

  1. 定义namespace, 通过编写dtd/xsd文件完成;
  2. 编写对应的NamespaceHandler,完成具体的自定义标签解析;
  3. 对应class所在工程的META-INF文件夹下定义spring.handlers文件,其中内容如下:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

小结

我们弄清楚了Spring在BeanDefinition加载过程中对自定义XML标签的处理,明确了新建自定义标签的方法。工程实践中可以把可重用模块进行封装,并通过自定义标签的形式加入到Spring容器中。这种思路已经在Spring生态下的扩展模块中得到体现,也让越来越多的开发小伙伴沦为了配置开发工程师。以上就是今天想聊的内容,如果对你有帮助,请让我感受到。

你可能感兴趣的:(Spring学习,spring)