Spring 中的标签分为默认标签和自定义标签两类,上一篇我们分析了默认标签的解析过程,当然在分析过程中我们也看到默认标签中嵌套了对自定义标签的解析,这是因为默认标签中可以嵌套使用自定义标签。然而,这和本篇所要讨论的自定义标签还是有些区别的,上一篇中介绍的自定义标签可以看作是 <bean />
标签的子标签元素,而本篇所要分析的自定义标签是与 <bean />
这类标签平级的标签。
自定义标签的定义与使用
在开始分析自定义标签的解析过程之前,我们还是通过示例演示一下自定义标签的定义和使用方式,整体与上一篇所介绍的类似,但还是有些许差别。自定义标签分为 5 步:
创建标签实体类;
定义标签的描述 XSD 文件;
创建一个标签元素解析器,实现 BeanDefinitionParser 接口;
创建一个 handler 类,继承自 NamespaceHandlerSupport 抽象类;
编写 spring.handlers 和 spring.schemas 文件。
本节我们自定义实现一个与 <alias />
标签功能类似的自定义标签,用于为指定的 bean 添加别名。第一步,先创建标签对应的实体类:
1 2 3 4 5 6 7 public class Alias { private String name; private String alias; }
第二步,定义标签的 XSD 文件 custom-alias.xsd,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8"?> <schema xmlns ="http://www.w3.org/2001/XMLSchema" targetNamespace ="http://www.zhenchao.org/schema/alias" xmlns:tns ="http://www.zhenchao.org/schema/alias" elementFormDefault ="qualified" > <element name ="alias" > <complexType > <attribute name ="id" type ="string" /> <attribute name ="name" type ="string" /> <attribute name ="parentName" type ="string" /> <attribute name ="c_name" type ="string" /> <attribute name ="c_alias" type ="string" /> </complexType > </element > </schema >
第三步,创建标签元素解析器。解析器需要实现 BeanDefinitionParser 接口,这里我们继承该接口的抽象子类 AbstractSingleBeanDefinitionParser,并覆盖相应的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class CustomBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return Alias.class; } @Override protected void doParse (Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String beanName = element.getAttribute("c_name" ); Assert.hasText(beanName, "The 'name' in alias tag is missing!" ); Assert.isTrue(parserContext.getRegistry().containsBeanDefinition(beanName), "No bean name " + beanName + " exist!" ); String alias = element.getAttribute("c_alias" ); Assert.hasText(beanName, "The 'alias' in alias tag is missing!" ); String[] aliasArray = alias.replaceAll("\\s+" , "" ).split("[,;]" ); for (final String ali : aliasArray) { parserContext.getRegistry().registerAlias(beanName, ali); } } }
上述方法首先会判断对应的 beanName 是否存在,如果存在的话就建立 beanName 与 alias 之间的映射关系。
第四步,创建标签 handler 类,继承自 NamespaceHandlerSupport 抽象类,用于注册第三步中定义的标签解析器:
1 2 3 4 5 6 7 8 public class CustomNamespaceHandler extends NamespaceHandlerSupport { @Override public void init () { this .registerBeanDefinitionParser("alias" , new CustomBeanDefinitionParser()); } }
第五步,编写 spring.handlers 和 spring.schemas 文件:
1 http://www.zhenchao.org/schema/alias=org.zhenchao.handler.CustomNamespaceHandler
1 http://www.zhenchao.org/schema/alias.xsd=META-INF/custom-alias.xsd
最后,演示一下如何使用上述自定义标签,首先需要在 <beans />
标签属性中定义标签的命名空间:
1 2 3 4 5 6 <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:myalias ="http://www.zhenchao.org/schema/alias" xsi:schemaLocation = "http: //www.springframework.org /schema /beans http: //www.springframework.org /schema /beans /spring-beans.xsd http: //www.zhenchao.org /schema /alias http: //www.zhenchao.org /schema /alias.xsd "
然后使用我们自定义的标签为已定义的 bean 添加别名:
1 2 <myalias:alias id ="myAlias" c_name ="myBean" c_alias ="aaa; bbb" />
这样我们完成了利用自定义的标签为 myBean 添加别名的功能。
自定义标签的解析过程
了解了如果自定义和使用自定义标签,下面开始分析 Spring 如何解析自定义标签。首先,回顾一下开始解析标签的入口函数 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
,如下::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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)) { this .parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
上一篇中我们分析了默认标签的解析过程,也就是 DefaultBeanDefinitionDocumentReader#parseDefaultElement
方法,接下来我们来分析自定义标签的解析过程,即 BeanDefinitionParserDelegate#parseCustomElement
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public BeanDefinition parseCustomElement (Element ele) { return this .parseCustomElement(ele, null ); } public BeanDefinition parseCustomElement (Element ele, @Nullable BeanDefinition containingBd) { String namespaceUri = this .getNamespaceURI(ele); if (namespaceUri == null ) { return null ; } NamespaceHandler handler = this .readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null ) { this .error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]" , ele); return null ; } return handler.parse(ele, new ParserContext(this .readerContext, this , containingBd)); }
上述方法首先获取自定义标签的命名空间定义,然后基于命名空间解析得到对应的 NamespaceHandler 实现类,最后调用 NamespaceHandler#parse
方法对自定义标签进行解析处理,这里本质上调用的就是前面自定义实现的 CustomBeanDefinitionParser#doParse
方法。先来看一下 NamespaceHandler 的解析过程,位于 DefaultNamespaceHandlerResolver#resolve
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public NamespaceHandler resolve (String namespaceUri) { Map<String, Object> handlerMappings = this .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); } } }
上述方法的执行逻辑可以概括为:
从 spring.handlers 获取所有注册的 handler 集合;
从集合中获取 namespace 对应 handler 实例;
如果 handler 已经被解析过,则返回对应的 handler 实例;
否则,利用反射创建 handler 实例,并初始化;
注册解析得到的 handler 实例。
上述过程中的第 4 步稍微复杂一些,下面进一步说明一下具体过程。我们在 spring.handlers 文件中会配置 namespaceUri 与对应 handler 全称类名的映射关系:
1 http ://www.zhenchao.org/schema/alias=org.zhenchao.handler.CustomNamespaceHandler
所以,这一步会基于该配置获取到对应 handler 类的全称类名;然后基于反射机制创建 handler 实例;接下来就是调用 NamespaceHandler#init
方法对 handler 实例进行初始化。该方法是由开发人员自己实现的,我们前面的例子中通过该方法将我们自定义的解析器 CustomBeanDefinitionParser 注册到 handler 实例中。
完成了对 handler 实例的解析,接下来就是调用 NamespaceHandler#parse
方法处理自定义标签:
1 2 3 4 5 6 public BeanDefinition parse (Element element, ParserContext parserContext) { BeanDefinitionParser parser = this .findParserForElement(element, parserContext); return (parser != null ? parser.parse(element, parserContext) : null ); }
上述实现主要分为 获取解析器 和 解析自定义标签 两个步骤,其中获取解析器就是依据我们使用的标签名从之前注册的 map 数据结构中获取相应的对象,然后调用 AbstractBeanDefinitionParser#parse
方法执行解析操作,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public final BeanDefinition parse (Element element, ParserContext parserContext) { AbstractBeanDefinition definition = this .parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { try { String id = this .resolveId(element, definition, parserContext); if (!StringUtils.hasText(id)) { parserContext.getReaderContext().error( "Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag" , element); } String[] aliases = null ; if (this .shouldParseNameAsAliases()) { String name = element.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } } BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); this .registerBeanDefinition(holder, parserContext.getRegistry()); if (this .shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); this .postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException ex) { String msg = ex.getMessage(); parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element); return null ; } } return definition; }
上述方法中的第一步是整个方法的核心,我们后面细讲,先来看一下第二、三步骤。对于自定义标签来说,id 属性是必备的,此外 Spring 还内置了 name 和 parentName 字段,这些名称是不允许使用的,否则达不到我们预期的结果,笔者第一次使用自定义标签时就踩了坑,用了 name 作为自定义标签属性名,结果就是各种奇怪的问题。
接下来看看第一步的实现逻辑,位于 AbstractSingleBeanDefinitionParser#parseInternal
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 protected final AbstractBeanDefinition parseInternal (Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = this .getParentName(element); if (parentName != null ) { builder.getRawBeanDefinition().setParentName(parentName); } Class<?> beanClass = this .getBeanClass(element); if (beanClass != null ) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { String beanClassName = this .getBeanClassName(element); if (beanClassName != null ) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); if (containingBd != null ) { builder.setScope(containingBd.getScope()); } if (parserContext.isDefaultLazyInit()) { builder.setLazyInit(true ); } this .doParse(element, parserContext, builder); return builder.getBeanDefinition(); }
上述方法首先会初始化创建一个 BeanDefinitionBuilder 对象,然后依据配置设置对象的相应属性,其中会尝试调用自定义标签解析器覆盖实现的 AbstractSingleBeanDefinitionParser#getBeanClass
方法获取 bean 对应的 Class 对象。然后会调用 AbstractSingleBeanDefinitionParser#doParse
方法解析自定义标签,该方法由开发者实现:
1 2 3 4 5 6 7 8 9 10 11 protected void doParse (Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String beanName = element.getAttribute("c_name" ); Assert.hasText(beanName, "The 'name' in alias tag is missing!" ); Assert.isTrue(parserContext.getRegistry().containsBeanDefinition(beanName), "No bean name " + beanName + " exist!" ); String alias = element.getAttribute("c_alias" ); Assert.hasText(beanName, "The 'alias' in alias tag is missing!" ); String[] aliasArray = alias.replaceAll("\\s+" , "" ).split("[,;]" ); for (final String ali : aliasArray) { parserContext.getRegistry().registerAlias(beanName, ali); } }
最后,返回自定义标签对应的 BeanDefinition 实例。
总结
本文演示了如何按照规范定义和使用自定义标签,并分析了 Spring 解析用户自定义标签的实现。分析到此,对于配置文件的解析过程也就基本分析完了。Spring 将一个个 bean 的静态配置解析成 BeanDefinition 实例注册到 IoC 容器中,接下去就可以调用 BeanFactory#getBean
方法获取 bean 实例了。建和初始化 bean 实例的过程也都由该方法触发,我们将在下一篇对这一过程的具体实现进行探究。
参考
Spring 源码深度解析