Spring5源码分析(017)——IoC篇之解析bean标签:解析默认标签中的自定义标签
注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总
前面的长篇大论基本上算是讲完了 bean 标签中的默认属性和子元素解析,包括基本属性以及constructor-arg、property、qualifier、meta、lookup-method、replaced-method 这6个子元素等,回顾下,这些分析所做的事情也即是 Spring5源码分析(011)——IoC篇之解析bean标签总览 中提到的第1步,即 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); ,接下来需要处理的则是bean标签中的自定义标签解析,即代码中提到的【2、自定义标签解析(若默认标签下有自定义标签)】:
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. * <p>处理给定的 bean 元素,解析 bean 定义并将其注册到 registry 中 */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 1、委托 BeanDefinitionParserDelegate 进行元素解析。解析成功,则返回的 BeanDefinitionHolder 实例 // 已经包含配置文件中配置的各种属性,例如 class 、 name 、 id 、 alias 之类的属性。 // 解析失败,则返回为 null BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // 2、自定义标签解析(若默认标签下有自定义标签) bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 3、注册 BeanDefinition // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // 4、发出响应事件,通知相关的监听器,完成 bean 加载 // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
对于 bean 标签中的自定义标签解析,这里调用的是 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef),从方法名称我们可以大致猜到:如果需要的话就对 beanDefinition 进行装饰(即有自定义标签时才需要进行装饰,从笔者的实际使用来看,大部分场景下是用不上自定义标签的)。我们来看下一个简单的场景(参考[2]Spring源码深度解析(第2版),郝佳,P67-P69;详细可参考官方文档说明 [1]9.2. XML Schema Authoring https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#xml-custom):
<bean id="test" class="test.MyClass"> <mybean:user username="test" /> <bean/>
当 Spring 中的bean标签使用的是默认的标签配置,而其子元素中却使用了自定义的配置时,这个调用才会起作用。之前在 Spring5源码分析(010)——IoC篇之加载BeanDefinition:解析和注册BeanDefinition 中讲过,在解析和注册 BeanDefinition 时,标签的解析分为2种类型,一种是默认标签的解析,另一种是自定义标签的解析,不过这两种都是针对bean的形式的。这里提到的正是自定义标签解析。那为什么还需要在默认类型解析中单独添加一个方法来处理呢?其实这里的自定义标签指的是bean标签中的属性/子元素,是bean中的一部分,并不能单独解析,而是需要存在于相应的bean。接下来具体分析下 decorateBeanDefinitionIfRequired 的逻辑。目录结构如下:
1、decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef)
/** * Decorate the given bean definition through a namespace handler, if applicable. * <p>如果适用的话,通过命名空间处理器装饰给定的beanDefinition * @param ele the current element * @param originalDef the current bean definition * @return the decorated bean definition */ public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) { return decorateBeanDefinitionIfRequired(ele, originalDef, null); } /** * Decorate the given bean definition through a namespace handler, if applicable. * @param ele the current element * @param originalDef the current bean definition * @param containingBd the containing bean definition (if any) * @return the decorated bean definition */ public BeanDefinitionHolder decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) { BeanDefinitionHolder finalDefinition = originalDef; // Decorate based on custom attributes first. // 1、遍历所有属性,看看是否有需要装饰的自定义属性 NamedNodeMap attributes = ele.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node node = attributes.item(i); finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); } // Decorate based on custom nested elements. // 2、遍历所有的子节点/子元素,看看是否有需要装饰的自定义子元素 NodeList children = ele.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); } } return finalDefinition; }
注:实际处理函数中的第三个参数被设置为空,这个参数其实是父类bean,当对某个嵌套配置进行分析时,这里需要传递父类beanDefinition。进一步分析可以发现其实时为了使用父类的scope属性,以备子类若没有设置scope时默认使用父类的属性,这里分析的时顶层配置,所以传递了null。
2、decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd)
/** * Decorate the given bean definition through a namespace handler, * if applicable. * <p>如果适用的话,通过命名空间处理器装饰给定的beanDefinition * @param node the current child node * @param originalDef the current bean definition * @param containingBd the containing bean definition (if any) * @return the decorated bean definition */ public BeanDefinitionHolder decorateIfRequired( Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) { // 1、获取自定义标签的命名空间 String namespaceUri = getNamespaceURI(node); // 2、对于非默认的自定义标签进行装饰 if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) { // 3、根据命名空间找到对应的命名空间处理器NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler != null) { // 4、使用自定义的命名空间处理器NamespaceHandler进行装饰 BeanDefinitionHolder decorated = handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd)); if (decorated != null) { return decorated; } } else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node); } else { // A custom namespace, not to be handled by Spring - maybe "xml:...". if (logger.isDebugEnabled()) { logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]"); } } } return originalDef; }
上述过程中的解析,其实最主要的还是找到自定义标签所对应的NamespaceHandler然后再做进一步的解析。详细的自定义配置解析后续文章会进一步分析,也可自行参考官网的详细说明 [1]9.2. XML Schema Authoring https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#xml-custom 。
3、参考
- [1]spring 官方文档 5.2.3.RELEASE:9.2. XML Schema Authoring https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#xml-custom
- [2]Spring源码深度解析(第2版),郝佳,P67-P69
- [3]相关注释可参考笔者 github 链接:https://github.com/wpbxin/spring-framework