在上篇文章中,提到了在Spring中存在默认标签与自定义标签两种,而且详细分析了默认标签的解析,本文就来分析自定义标签的解析,像Spring中的AOP就是经过自定义标签来进行配置的,这里也是为后面学习AOP原理打下基础。html
这里先回顾一下,当Spring完成了从配置文件到Document的转换并提取对应的root后,将开始全部元素的解析,而在这一过程当中便会区分默认标签与自定义标签两种格式,并分别解析,能够再看一下这部分的源码加深理解:java
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); } }
从上面的函数中也能够看出,当Spring拿到一个元素时首先要作的是根据命名空间进行解析,若是是默认的命名空间,则使用parseDefaultElement()方法进行元素解析,不然使用parseCustomElement()方法进行解析。在本文中,全部的功能解析都是围绕其中的那句代码delegate.parseCustomElement(root)开展的。node
在分析自定义标签的解析过程以前,咱们先了解一下自定义标签的使用过程,这里参考spring文档中的例子。spring
扩展Spring自定义标签配置大体须要如下几个步骤:缓存
接下来咱们将建立一个自定义XML元素,便于经过一个更容易的方式配置SimpleDateFormat类型的bean。配置好以后咱们能够经过下面的方式来定义一个SimpleDateFormat类型的bean:app
<myns:dateformat id = "dateFormat" pattern = "yyyy-MM-dd HH:mm" lenient = "true"/>
给Spring IoC容器建立XML扩展标签的第一步是建立一个新的XML模式来描述对应的标签(下面是咱们将要用来配置SimpleDateFormat对象的schema):dom
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.mycompany.com/schema/myns" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.mycompany.com/schema/myns" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:import namespace="http://www.springframework.org/schema/beans"/> <xsd:element name="dateformat"> <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="lenient" type="xsd:boolean"/> <xsd:attribute name="pattern" type="xsd:string" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
定义了上面的schema以后,咱们就能够直接使用元素<myns:dateformat/>来配置SimpleDateFormat类型的对象了:ide
<myns:dateformat id="dateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
若是没有作上面的工做,咱们可能就须要经过下面的方式来配置SimpleDateFormat类型的对象了:函数
<bean id="dateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-HH-dd HH:mm"/>
<property name="lenient" value="true"/>
</bean>
这个是继承自AbstractSingleBeanDefinitionParser,主要是用来将自定义标签解析成BeanDefinition。工具
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{ protected Class getBeanClass(Element element) { return SimpleDateFormat.class; } protected void doParse(Element element, BeanDefinitionBuilder bean) { // this will never be null since the schema explicitly requires that a value be supplied String pattern = element.getAttribute("pattern"); bean.addConstructorArg(pattern); // this however is an optional property String lenient = element.getAttribute("lenient"); if (StringUtils.hasText(lenient)) { bean.addPropertyValue("lenient", Boolean.valueOf(lenient)); } } }
这个是继承自NamespaceHandlerSupport,主要是将上面的BeanDefinitionParser注册到Spring容器:
public class MyNamespaceHandler extends NamespaceHandlerSupport{ public void init() { registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser()); } }
这两个文件默认位置是在工程资源目录的/META-INF/文件夹下,内容以下(注意要改为本身的包名):
META-INF/spring.handlers http\://www.mycompany.com/schema/myns=spring.customElement.MyNamespaceHandler META-INF/spring.schemas http\://www.mycompany.com/schema/myns/myns.xsd=spring/customElement/myns.xsd
使用自定义的扩展标签和使用Spring提供的默认标签是相似的,能够按照以下配置一个SimpleDateFormat类型的bean:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:myns="http://www.mycompany.com/schema/myns" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd"> <!-- as a top-level bean --> <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> </beans>
配置好以后能够测试一下:
public static void main(String[] args) { XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("customElement.xml")); SimpleDateFormat myTestBean = (SimpleDateFormat)xmlBeanFactory.getBean("defaultDateFormat"); System.out.println( "now time --- "+ myTestBean.format(new Date())); } // 输出结果: now time --- 2020-03-07 20:37
了解了自定义标签的使用以后,咱们来探究一下自定义标签的解析过程。接着文章开头提到的,咱们要从BeanDefinitionParserDelegate的parseCustomElement()方法开始:
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 获取对应的名称空间 String namespaceUri = getNamespaceURI(ele); // 根据命名空间找到对应的NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // 调用自定义的NamespaceHandler进行解析 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
这里能够看出对自定义标签进行解析的思路是根据Element获取对应的名称空间,而后根据名称空间获取对应的处理器,最后根据用户自定义的处理器进行解析,但是看起来简单,实现起来就不是这么简单了,先来看一下名称空间的获取吧。
自定义标签的解析是从名称空间的提取开始的,不管是区分默认标签和自定义标签,仍是区分自定义标签对应的不一样处理器,都是以标签所提供的名称空间为基础的。至于如何提取对应元素的名称空间,已经有现成的实现可供使用,spring中是直接调用org.w3c.dom.Node提供的相应方法来完成名称空间的提取:
public String getNamespaceURI(Node node) { return node.getNamespaceURI(); }
有了名称空间,就能够此来提取对应的NamespaceHandler了,这项工做是由下面这句代码来完成的:
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
这里readerContext的getNamespaceHandlerResolver()方法返回的实际上是DefaultNamespaceHandlerResolver,因此咱们直接进入其resolve()方法中往下看:
public NamespaceHandler resolve(String namespaceUri) { // 获取全部已经配置的handler映射 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); } } }
上面函数中的流程仍是比较清晰的,在前面的自定义标签使用示例中有说到,若是要使用自定义标签,须要在Spring.handlers文件中配置名称空间与名称空间处理器的映射关系。只有这样,Spring才能根据映射关系找到匹配的处理器。
而寻找匹配的处理器就是在上面函数中实现的,当获取到自定义的NamespaceHandler以后就能够进行处理器初始化并解析了。这里咱们再回忆一下前面自定义标签示例中,对于名称空间处理器的内容(咱们在其init()方法中注册了一个解析器)。
在上面的代码中,获取到自定义名称空间处理器后会立刻执行其init()方法来进行自定义BeanDefinitionParser的注册。固然在init()中能够注册多个标签解析器,如<myns:A、<myns:B等,使得myns的名称空间中能够支持多种标签解析。
注册好以后,名称空间处理器就能够根据标签的不一样来调用不一样的解析器进行解析。根据上面的函数和以前的例子,咱们基本能够判断getHandlerMappings()的主要功能就是读取Spring.handlers配置文件并将配置文件缓存在map中:
private Map<String, Object> getHandlerMappings() { // 若是没有被缓存则开始进行缓存 if (this.handlerMappings == null) { synchronized (this) { if (this.handlerMappings == null) { try { // this.handlerMappingsLocation在构造函数中已经被初始化为:META-INF/Spring.handlers 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()); // 将Properties格式文件合并到Map格式的handlerMappings中 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; }
这里是借助工具类PropertiesLoaderUtils对Spring.handlers配置文件进行了读取,而后将读取的内容放到缓存中并返回。
获取到解析器以及要解析的元素后,Spring将解析工做委托给自定义解析器来解析,即下面代码所完成的:
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
此时咱们拿到的handler实际上是咱们自定义的MyNamespaceHandler了,可是咱们前面并无实现parse()方法,因此这里这个应该是调用的父类中的parse()方法,看一下NamespaceHandlerSupport中的parse()方法:
public BeanDefinition parse(Element element, ParserContext parserContext) { // 寻找解析器并进行解析操做 return findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { // 获取元素名称,也就是<myns:dateformat中的dateformat,在上面示例中,localName为dateformat String localName = parserContext.getDelegate().getLocalName(element); // 根据dateformat找到对应的解析器,也就是在registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser()); // 注册的解析器 BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
首先是寻找元素对应的解析器,而后调用其parse()方法。结合咱们前面的示例,其实就是首先获取在MyNamespaceHandler类中的init()方法中注册对应的SimpleDateFormatBeanDefinitionParser实例,并调用其parse()方法进行进一步解析,一样这里parse()方法咱们前面是没有实现的,咱们也试着从其父类找一下:
public final BeanDefinition parse(Element element, ParserContext parserContext) { AbstractBeanDefinition definition = parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { try { String id = 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 = new String[0]; String name = element.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } // 将AbstractBeanDefinition转换为BeanDefinitionHolder并注册 BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); registerBeanDefinition(holder, parserContext.getRegistry()); if (shouldFireEvents()) { // 须要通知监听器则进行处理 BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException ex) { parserContext.getReaderContext().error(ex.getMessage(), element); return null; } } return definition; }
这里虽是对自定义配置进行解析,可是能够看到大部分的代码是用来将解析后的AbstractBeanDefinition转化为BeanDefinitionHolder并将其注册,这点与解析默认标签是相似的,真正去作解析的事情实际上是委托给了parseInternal()函数。而在parseInternal()中也并非直接调用自定义的doParse()函数,而是先进行一系列的数据准备,包括对beanClass、scope、lazyInit等属性的准备:
@Override protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } // 获取自定义标签中的class,此时会调用自定义解析器中的getBeanClass()方法 Class<?> beanClass = getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { // 若子类没有重写getBeanClass方法则会尝试检查子类是否重写getBeanClassName()方法 String beanClassName = getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); if (parserContext.isNested()) { // 若存在父类则使用父类的scope属性 builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } if (parserContext.isDefaultLazyInit()) { // 配置延迟加载 builder.setLazyInit(true); } // 调用子类重写的doParse方法进行解析 doParse(element, parserContext, builder); return builder.getBeanDefinition(); } // 这里就是调用前面示例中咱们本身写的doParse()方法 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { doParse(element, builder); }
到这里就完成了对自定义标签转换成BeanDefinition的整个过程了,回顾一下整个过程,在咱们定义的SimpleDateFormatBeanDefinitionParser中咱们只是作了与本身业务逻辑相关的部分,剩下的包括建立BeanDefinition以及进行相应默认属性的设置,Spring都帮咱们默认实现了,咱们固然也能够本身来完成这一过程,好比AOP就是这样作的,可是本文仍是用最简单的方式来作一个说明。
其实从Spring对自定义标签的解析中也能够体会到Spring的可扩展式设计思路,经过暴露一些接口,咱们就可以方便地实现本身的个性化业务,不只如此,Spring本身即是这项功能的践行者,像AOP、事务都是经过这种方式来定制对应的标签来完成配置需求的。
到这里咱们已经完成了Spring中所有的解析工做的学习,也就是说到这里咱们已经学习了Spring将bean从配置文件加载到内存的完整过程,接下来的任务即是若是使用这些bean,这才是IoC容器的重头戏,后面会详细学习的。