提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
作为一个框架级组件,Spring充分考虑了框架的扩展性,对于XML标签也不例外。博主结合个人对Spring源码的阅读和理解聊聊Spring对自定义XML标签的处理。
显然,自定义可以开发人员扩充标签库。而扩充标签库为了优雅地扩展Spring容器的功能。从职责划分上来说,具体的功能不是Spring框架关心的,但扩展的方式(扩展标准)是框架应该提供的。这些扩展机制就就像大树的树干,有了这些树干的支持,Spring生态下的其他组件才能在树干上开发结果。从这个角度上说,Spring的核心是IOC。AOP只是在IoC框架下的一种增强扩展,毕竟在XML配置文件中,AOP的支持也是通过扩展标签完成的。
完善的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.
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);
}
}
}
基本上包括这么几个步骤:
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);
}
到这里, 各方法的作用已经明确了。
到这里,我们已经明确了NamespaceHandler的注册和使用方式。基本上包含这么几个过程:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
我们弄清楚了Spring在BeanDefinition加载过程中对自定义XML标签的处理,明确了新建自定义标签的方法。工程实践中可以把可重用模块进行封装,并通过自定义标签的形式加入到Spring容器中。这种思路已经在Spring生态下的扩展模块中得到体现,也让越来越多的开发小伙伴沦为了配置开发工程师。以上就是今天想聊的内容,如果对你有帮助,请让我感受到。