自定义标签的解析
前言
在之前的文章提到过,Spring中存在默认标签和自定义标签两种,前面的文章已经分析了Spring中对默认标签的解析过程。所以,这篇文章讲述的就是Spring中自定义标签的解析过程。先来回顾一下,当完成从配置文件到Document的转换并提取对应的root后,就开始了所有元素的解析,而这一过程便开始了默认标签与自定义标签两种格式的区分,函数如下:
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); } }
这篇文章的所有讲述都是围绕着delegate.parseCustomElement(root)开展的。
自定义标签解析
话不多说,探索自定义标签的解析过程:
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); }
继续跟踪代码:
//第二个参数:containingBd为弗雷bean,对顶层元素的解析应该设置为null public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { //获取对应的命名空间 String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } //根据命名空间找到对应的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; } //调用自定义的那么SpaceHandler进行解析 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
上述代码的逻辑配合注释看出还是比较清晰的:首先根据对应的bean获取对应的命名空间,根据命名空间解析获取对应的处理器,然后根据用户自定义的处理器进行解析。接下来我们一步一步来深入了解这些步骤。
(1)获取标签的命名空间
标签的解析是从命名空间的提取开始的,无论是区分Spring中默认标签和自定义标签还是区分自定义标签中不同标签的处理器都是以标签所提供的命名空间为基础的,至于怎么去提取对应元素的命名空间其实并不需要我们亲自去实现的,在www.w3c.dom.Node中已经提供了方法供我们直接调用:
public String getNamespaceURI(Node node) { return node.getNamespaceURI(); }
(2)提取自定义标签处理器
有了上一步的命名空间,就可以进行对NamespaceHandler的提取了,继续之前的 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 在执行代码readerContext初始化的时候其属性namespaceHandlerResolver已经被初始化成了 DefaultNamespaceHandlerResolver 的实例,所以这里调用的resolve()方法,其实是调用的DefaultNamespaceHandlerResolver类中的方法。来看看这个方法:
1 public NamespaceHandler resolve(String namespaceUri) { 2 //获取所有已经配置好的handler映射 3 Map<String, Object> handlerMappings = getHandlerMappings(); 4 //根据命名空间找到对应的信息 5 Object handlerOrClassName = handlerMappings.get(namespaceUri); 6 if (handlerOrClassName == null) { 7 return null; 8 } 9 else if (handlerOrClassName instanceof NamespaceHandler) {//表示已经做过解析的情况 10 //这时直接从缓存中读取 11 return (NamespaceHandler) handlerOrClassName; 12 } 13 else {//表示没有做过解析 14 //类路径 15 String className = (String) handlerOrClassName; 16 try { 17 //使用反射将类路径转化为类 18 Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); 19 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { 20 throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + 21 "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); 22 } 23 //初始化类 24 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); 25 //调用自定义的NamespaceHandler的初始化方法 26 namespaceHandler.init(); 27 //记录到缓存中 28 handlerMappings.put(namespaceUri, namespaceHandler); 29 return namespaceHandler; 30 } 31 catch (ClassNotFoundException ex) { 32 throw new FatalBeanException("Could not find NamespaceHandler class [" + className + 33 "] for namespace [" + namespaceUri + "]", ex); 34 } 35 catch (LinkageError err) { 36 throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + 37 className + "] for namespace [" + namespaceUri + "]", err); 38 } 39 } 40 }
上述代码配合注释逻辑已经较为清晰,这里就不再赘述代码逻辑了。
如果要使用自定义标签,那么其中一项必不可少的操作就是在spring.handlder文件中配置命名空间与命名处理器之间的映射关系。只有这样,Spring才能根据映射关系找到匹配的处理器,而寻找匹配的处理器就是在上述代码中实现的。当获取到自定义的NamespaceHandler之后就可以进行处理器初始化并解析了。
当得到自定义命名空间处理器后会马上执行NamespaceHandler.init()方法来进行自定义BeanDefinitionParser的注册。当然在这里你可以注册多个标签解析器,使得在一个命名空间中可以支持多种标签解析。
注册后,命名空间处理器就可以根据标签的不同来调用不同的处理器进行解析。那么,根据上述代码,我们就可以推断出getHandlerMapping()方法的主要功能就是读取spring.handler中的配置文件并将配置文件缓存在map中。来看一下:
1 private Map<String, Object> getHandlerMappings() { 2 Map<String, Object> handlerMappings = this.handlerMappings; 3 //如果没有被缓存则开始进行缓存 4 if (handlerMappings == null) { 5 synchronized (this) { 6 handlerMappings = this.handlerMappings; 7 if (handlerMappings == null) { 8 if (logger.isTraceEnabled()) { 9 logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]"); 10 } 11 try { 12 //读取配置文件中的属性 13 Properties mappings =//this.handlerMappingsLocation在构造函数中默认被初始化为META-INF/spring.handlers 14 PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); 15 if (logger.isTraceEnabled()) { 16 logger.trace("Loaded NamespaceHandler mappings: " + mappings); 17 } 18 handlerMappings = new ConcurrentHashMap<>(mappings.size()); 19 //将Properties格式文件合并到map格式的HandlerMapping中 20 CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); 21 this.handlerMappings = handlerMappings; 22 } 23 catch (IOException ex) { 24 throw new IllegalStateException( 25 "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex); 26 } 27 } 28 } 29 } 30 return handlerMappings; 31 }
可以看出,与我们猜想的一致。借助了 PropertiesLoaderUtils对属性handlerMappingLocation进行配置文件的读取,handlerMappingLocation被默认初始化为“META-INF/spring.handlers”。
标签解析
得到了解析处理器以及要分析的元素后,Spring就可以将解析工作委托给自定义解析器去解析了,Spring委托了以下这句代码来去实现:
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
此时的这个handler已经被实例化成了我们自定义的MyNamespaceHandler了,同时MyNamespaceHandler已经完成了初始化的工作(就是执行了init()方法),但是我们在实现自定义标签的时候并没有去实现parse方法,所以推断这个方法调用的是父类的实现,我们来看看父类NamespaceHandlerSupport中的parse方法:
public BeanDefinition parse(Element element, ParserContext parserContext) { //寻找解析器并进行解析操作 BeanDefinitionParser parser = findParserForElement(element, parserContext); return (parser != null ? parser.parse(element, parserContext) : null); }
解析过程中首先是寻找元素对应的解析器,进而调用解析器中的parse方法,那么在自定义标签的时候,就是首先执行MyNamespaceHandler类中的init方法目的是注册需要调用的实例,并调用parse方法对其进行进一步的解析。
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { //获取元素名称(例如自定义标签为:<mytag:user 那么localName 为 user) String localName = parserContext.getDelegate().getLocalName(element); //根据localName找到对应的解析器,也就是在实现自定义的MyRegisterBeanDefinitionParser中注册的解析器 BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
而对于parse方法时调用AbstractBeanDefinitionParser类中的方法:
1 public final BeanDefinition parse(Element element, ParserContext parserContext) { 2 //解析自定义标签 3 AbstractBeanDefinition definition = parseInternal(element, parserContext); 4 if (definition != null && !parserContext.isNested()) { 5 try { 6 String id = resolveId(element, definition, parserContext); 7 if (!StringUtils.hasText(id)) { 8 parserContext.getReaderContext().error( 9 "Id is required for element '" + parserContext.getDelegate().getLocalName(element) 10 + "' when used as a top-level tag", element); 11 } 12 String[] aliases = null; 13 if (shouldParseNameAsAliases()) { 14 String name = element.getAttribute(NAME_ATTRIBUTE); 15 if (StringUtils.hasLength(name)) { 16 aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); 17 } 18 } 19 //将AbstractBeanDefinition转换为BeanDefinitionHolder并注册 20 BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); 21 registerBeanDefinition(holder, parserContext.getRegistry()); 22 if (shouldFireEvents()) { 23 //需要通知监听器进行处理 24 BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); 25 postProcessComponentDefinition(componentDefinition); 26 parserContext.registerComponent(componentDefinition); 27 } 28 } 29 catch (BeanDefinitionStoreException ex) { 30 String msg = ex.getMessage(); 31 parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element); 32 return null; 33 } 34 } 35 return definition; 36 }
从上述代码中可以看出,虽然这段代码是对自定义配置文件的解析,但是,这个函数的大部分代码是用来处理解析后的AbstractBeanDefinition转化为BeanDefinitionHolder并注册的功能。真正去做解析的事情是委托给了parseInternal()这个方法。来看一下这个方法:
1 protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { 2 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); 3 String parentName = getParentName(element); 4 if (parentName != null) { 5 builder.getRawBeanDefinition().setParentName(parentName); 6 } 7 //获取自定义标签中的class,此时会调用自定义解析器中的getBeanClass方法 8 Class<?> beanClass = getBeanClass(element); 9 if (beanClass != null) { 10 builder.getRawBeanDefinition().setBeanClass(beanClass); 11 } 12 else { 13 //如果子类没有重写getBeanClass方法则尝试检查子类是否重写getBeanClassName方法 14 String beanClassName = getBeanClassName(element); 15 if (beanClassName != null) { 16 builder.getRawBeanDefinition().setBeanClassName(beanClassName); 17 } 18 } 19 builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); 20 BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); 21 if (containingBd != null) { 22 //若存在父类则使用父类的scope属性 23 builder.setScope(containingBd.getScope()); 24 } 25 if (parserContext.isDefaultLazyInit()) { 26 // 配置延迟加载为ture,即默认为延迟加载 27 builder.setLazyInit(true); 28 } 29 //调用子类重写的doParse方法进行解析 30 doParse(element, parserContext, builder); 31 return builder.getBeanDefinition(); 32 }
可以看出, parseInternal()方法并不是直接调用自定义的doParse()方法,而是进行了一系列的数据准备,包括对beanClass、scope、lazyInit等属性的准备。
至此,自定义标签的解析已经分析完毕。
回顾一下:
虽然我们在自定义标签的时候,只是做了与自己业务逻辑相关的部分(只是重写了doParse和getBeanClass方法),不过我们不做并不代表没有,在这个处理过程中同样也是按照Spring中默认标签的处理方式进行,包括创建BeanDefinition以及进行默认的一些属性的设置,对于这些工作Spring都默默的帮我们实现了,只是暴露出了一些接口供用户实现个性化的业务。
参考:《Spring源码深度解析》 郝佳 编著: