[Spring] 自定义标签解析

Spring 除了实现了默认的标签解析,如 , , , ,还提供了自定义的标签解析机制,由用户实现解析自定义标签的逻辑,如 Spring 实现的自定义标签: , , 等。

本篇博客主要关注自定义标签解析的相关逻辑,Spring 解析标签前准备工作、解析默认标签,可参看我的其他博客。

一、解析入口

Spring 解析 bean 属性的实现逻辑 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 中,会判断元素是否是默认的命名空间 isDefaultNamespace(Element),即 http://www.springframework.org/schema/beans

  • 是则走默认标签解析逻辑: parseDefaultElement(ele, delegate);
  • 否则走自定义标签解析逻辑: delegate.parseCustomElement(ele);

入口如下:

/**
 * 解析 bean 属性:
 * 1.默认标签: import, beans, bean, alias
 * 2.自定义标签: aop, context, tx 等
 */
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);
	}
}

二、解析逻辑

解析自定义标签 BeanDefinitionParserDelegate#parseCustomElement(Element) 的实现如下:

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

从上面的代码,可以看出解析自定义标签的步骤主要为:

  1. 获取命名空间资源标识 namespaceUri
  2. 根据 namespaceUri 获取命名空间处理器 NamespaceHandler
  3. 调用 NamespaceHandler#parse 方法解析标签信息

对应的时序图如下:
[Spring] 自定义标签解析_第1张图片
获取命名空间资源标识 getNamespaceUri(Element) 不过多介绍,接下来就详细介绍获取命名空间解析器和解析自定义标签两部分。

2.1 获取命名空间解析器

获取解析器的逻辑在 DefaultNamespaceHandlerResolver#resolve(String namespaceUri) 中:

/**
 * 获取 NamespaceHandler
 */
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("NamespaceHandler class [" + className + "] for namespace [" +
					namespaceUri + "] not found", ex);
		}
		catch (LinkageError err) {
			throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
					namespaceUri + "]: problem with handler class file or dependent class", err);
		}
	}
}

主要分为以下步骤:

  1. 从指定位置文件加载命名空间映射信息,默认位置为 META-INF/spring.handlers
  2. 根据 namespaceUri 获取处理器名称
  3. 通过反射得到处理器对象,并调用 NamespaceHandler#init 执行初始化逻辑

2.1.1 NamespaceHandler 结构

首先,看下 NamespaceHandler 的类图:
[Spring] 自定义标签解析_第2张图片
NamespaceHandler#init 方法初始化命名空间解析器,NamespaceHandler#parse 方法解析具体的命名空间标签元素。

NamespaceHandlerSupport 为命名空间解析器辅助类,registerBeanDefinitionParser(elementName, beanDefinitionParser) 方法将标签和 bean 属性解析器 BeanDefinitionParser 的映射关系注册到 parsers 中。

Spring 常见的命名空间解析器有:

  • AopNamespaceHandler:AOP 命名空间
  • ContextNamespaceHandler:上下文命名空间
  • TxNamespaceHandler:事务命名空间

2.1.2 加载命名空间映射信息

接着看 DefaultNamespaceHandlerResolver#getHandlerMappings 部分:

/**
 * 默认解析路径,会查询所有的 jar 文件
 */
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

/**
 * 加载 namespaceUri 对应的 NamespaceHandler 信息,懒加载
 */
private Map<String, Object> getHandlerMappings() {
	if (this.handlerMappings == null) {
		synchronized (this) {
			if (this.handlerMappings == null) {
				try {
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
					if (logger.isDebugEnabled()) {
						logger.debug("Loaded NamespaceHandler mappings: " + mappings);
					}
					Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(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 this.handlerMappings;
}

默认从 META-INF/spring.handlers 位置加载配置信息,key-value 方式。

spring-context 模块的命名空间解析器映射关系配置为例:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

2.1.3 执行初始化逻辑

获取到对应的处理器名称之后,先通过反射实例化,再调用 NamespaceHandler#init 方法执行初始化逻辑。

以命名空间 aop 的解析器 AopNamespaceHandler 为例:

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    // 命名空间解析器初始化
	@Override
	public void init() {
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

public abstract class NamespaceHandlerSupport implements NamespaceHandler {

	// 注册 bean 属性解析器
	protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
		this.parsers.put(elementName, parser);
	}
}

Spring AOP 实现了 AspectJAutoProxyBeanDefinitionParser 类来解析 aspectj-autoproxy 标签,实现了 ScopedProxyBeanDefinitionDecorator 类来解析 scoped-proxy 标签。

2.2 解析自定义标签

接下来是解析逻辑 NamespaceHandlerSupport#parse 的实现:

public BeanDefinition parse(Element element, ParserContext parserContext) {
	return findParserForElement(element, parserContext).parse(element, parserContext);
}

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

这里的实现比较简单,从 parses 中获取 bean 属性解析器 BeanDefinitionParser 对象,然后调用 BeanDefinitionParser#parse 方法。

以上,就是 Spring 自定义标签解析机制的逻辑,主要在于 NamespaceHandlerBeanDefinitionParser 两个接口的实现。

你可能感兴趣的:(spring)