Spring 源码解析:自定义标签解析
一、使用示例
步骤 1:创建User
实体
步骤 2:定义一个XSD
文件描述组件内容
步骤 3:创建BeanDefinitionParser
接口的实现类,用来解析 XSD 文件中的定义和组件定义。
步骤 4:创建NamespaceHandlerSupport
实现类,目的是将组件注册到 Spring 容器中。
步骤 5:编写spring.handlers
和spring.schemas
文件,默认位置是/META-INF
目录下
步骤 6:在配置文件oldbean.xml
中引入对应的命名空间以及 XSD 之后,就可以配置<myname:user ... />
了
步骤 7:进行测试
二、源码解析
在第 2 讲中,我们已经介绍了关于默认标签的解析过程。那么我们还是需要将视角在回到parseBeanDefinitions(...)
方法上来,从下图源码截图中我们可以看出来,我们首先是来判断root
和root的子节点
是否是默认表空间,即:通过delegate.isDefaultNamespace(...)
来进行判断:
【如果是默认表空间】执行
默认标签
解析——delegate.parseDefaultElement(ele, delegate);【如果不是默认表空间】则执行自定义标签
解析——delegate.parseCustomElement(ele);
下面我们来看一下parseCustomElement(...)
方法的具体实现,具体来说有如下 3 个步骤:
【首先】获得
namespaceUri
,此处是通过 org.w3c.dom.Node 中的getNamespaceURI()�
方法进行获取的;【其次】获得解析该自定义标签的NamespaceHandler
实现类。【最后】调用该实现类的parse(...)
方法进行解析操作。
2.1> getNamespaceURI(ele)方法解析
此方法是用于获得 namespaceUri,此处是通过 org.w3c.dom.Node 中的getNamespaceURI()
方法进行获取的,以使用示例中为例,将会返回的**namespaceUri=“www.muse.com/schema/user…
2.2> resolve(namespaceUri)方法解析
此方法是用来获得解析该自定义标签的 NamespaceHandler 实现类,为下图中红框处代码:
在此处的this.readerContext.getNamespaceHandlerResolver()
方法中,实际会返回 DefaultNamespaceHandlerResolver 实例对象。
上面我们了解了 DefaultNamespaceHandlerResolver 实例对象的创建过程之后,那么下面我们就来分析一下它的resolve(namespaceUri)
这个方法的内部实现,下面是该方法的源码部分:
在getHandlerMappings()
方法中,我们获得了系统加载的所有NamespaceHandler
实例对象的映射,映射关系为:key=uri,value=NamespaceHandler 实现类。但是,如果我们发现加载的handlerMappings等于null
,那么我们就需要去加载META-INF/spring.handlers
文件中的配置信息,将其生成NamespaceHandler
实例对象的映射。所以,综上所示,getHandlerMappings()
方法的主要功能就是读取spring.handlers
的配置文件并将配置文件缓存在 map 中。
那么以我们的演示例子来说,handlerMappings
中是包含了 11 个xxxNamespaceHander
实例对象的映射关系的,在下图中,红框部分就是我们自定义的UserNamespaceHandler
。
那么在调用 namespaceHandler.init() 方法的时候,其实调用的是UserNamespaceHandler
实例的init()
方法,该方法是我们自己实现的。如下图所示:
当我们调用 handlerMappings.put(namespaceUri, namespaceHandler) 方法时,那么就将原本 String 类型的 value 值“com.muse.springbootdemo.UserNamespaceHandler
”,替换为UserNamespaceHandler
实例对象了。如下图所示:
2.3> parse(...)方法解析
下面我们再来看一下的parse(...)
方法,该方法是用来进行自定义标签的解析操作。
在parse(...)
方法中我们可以看到,首先是通过findParserForElement(element, parserContext)
方法来找到 localName 对应的解析器。以我们上面的示例为例,我们在 oldbean.xml 中配置的是<myname:user id="user" name="muse" email="muse@163.com"/>
,那么获得了localName
就等于“user”。由于我们在UserNamespaceHandler
类中已经配置了 user 与 UserBeanDefinitionParser 实例对象的对应关系,所以parser
就拿它作为方法的返回值。具体详情,请见下图所示:
在上面的代码逻辑中,我们已经获得到了parser
示例对象(UserBeanDefinitionParser
实例对象),那么我们通过调用 parser 对象的 parser(element, parserContext) 方法对自定义标签执行解析操作。下面是该方法涉及的源码部分:
我们在上面可以看到,对自定义标签进行解析是在parseInternal(element, parserContext)
方法中执行的,
在doParse(element, parserContext, builder)
方法中,执行了真正的自定义标签解析逻辑,那么既然是自定义标签,是无法通过 Spring 进行解析的,而是需要我们自己提供自定义解析类XxxBeanDefinitionParser
来实现doParse(...)
方法的,具体如下所示:
今天的文章内容就这些了:
评论