Spring源码分析之IOC (二)主要讲了默认标签的解析,这期主要说下自定义标签的解析,结合rpc框架dubbo一起看怎么来解析自定义标签的。
对应的spring解析入口:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//判断是否是默认的命名空间标签,比如<beans>,<bean>等spring自带的标签,
// 如果不是就进入else标签中,比如dubbo的<dubbo:service>,由dubbo自己去解析
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;
//如果是spring标签,就直接处理
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
//如果不是spring的标签,则找对应的标签解析器去解析
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
//如果不是spring默认标签,则找对应的标签解析器去解析
delegate.parseCustomElement(root);
}
}
首先说下使用自定义标签需要哪些条件和步骤:
有需要自定义扩展的组件
有自定义xsd文件,用于自定义标签
有解析自定义标签组件,实现BeanDefinitionParser接口,用来解析xsd文件中的标签定义和自定义扩展组件定义
有自定义的Handler,扩展自NamespaceHandlerSupport,用来将解析组件注册到spring容器中
配置spring.handlers、spring.schemas文件
我们看下dubbo的在自定义解析中是怎么处理的
首先需要自定义扩展的组件:
//dubbo服务接口
public interface DemoService {
public String sayHello();
}
//dubbo服务实现
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
logger.info("Hello " + name + ", request from consumer: " + RpcContext.getServiceContext().getRemoteAddress());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello " + name + ", response from provider: " + RpcContext.getServiceContext().getLocalAddress();
}
}
自定义的xsd文件,dubbo的xsd文件太长,截取一部分如下图
![](https://i-blog.csdnimg.cn/blog_migrate/4123d87b8eb848188dad5d40df21324c.png)
解析自定义标签组件,实现BeanDefinitionParser接口,其中parse是BeanDefinitionParser定义的接口方法,交给第三方去实现,也就是解析自定义标签的核心逻辑
![](https://i-blog.csdnimg.cn/blog_migrate/ca45b9b923f1997f59e6b0b4da147298.png)
自定义Handler,扩展自NamespaceHandlerSupport,向spring注册自定义组件解析器,像我们最熟悉的<dubbo:service/>、<dubbo:reference/>标签等
![](https://i-blog.csdnimg.cn/blog_migrate/4c38d89e4cf4c324e898f2d0e8a7cfae.png)
配置spring.handlers、spring.schemas文件,在dubbo项目中我们可以看到该配置文件再config模块下的spring扩展模块,Spring会通过SPI技术进行加载该模块,使第三方服务与spring解耦。
![](https://i-blog.csdnimg.cn/blog_migrate/cc5070298d8c9a0e45d71e04595c6fc0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/62388f7e6d2ea9c7c06f8c3d39ca093a.png)
做好这些之后,下面我们启动下dubbo服务,通过跟踪dubbo的启动一起看下spring是如何解析dubbo标签的。
配置下我们的dubbo提供者服务,我们使用xml的形式来配置,引入xsd文件,并使用dubbo的相关标签,application表示应用信息,registry表示注册中心,protocol表示协议,service表示服务提供者。
![](https://i-blog.csdnimg.cn/blog_migrate/8e29788f422fb433c1e505b6498fdf19.png)
然后去启动我们的服务,同样使用ClassPathXmlApplicationContext类来加载我们的配置信息,只是配置文件改为我们配置的dubbo-provider.xml。
![](https://i-blog.csdnimg.cn/blog_migrate/5ed396a3ee5260213a6ff0799d5c9db0.png)
还是走相同的代码逻辑,我们再走一遍加深一下记忆:
![](https://i-blog.csdnimg.cn/blog_migrate/46e9f9c84b956adad84336441adeb042.png)
进入到AbstractApplicationContext的refresh中:
![](https://i-blog.csdnimg.cn/blog_migrate/1c17c20858117c887319b95a26e4d5ad.png)
进入obtainFreshBeanFactory,调用refreshBeanFactory方法:
![](https://i-blog.csdnimg.cn/blog_migrate/640c1a34d38fb12e20e1b8cab319093c.png)
进入refreshBeanFactory,开始看到加载BeanDefinitions的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/b45b5694034952657168905646c46113.png)
进入loadBeanDefinitions方法中,设置解析配置文件的环境和解析器BeanDefinitionReader
![](https://i-blog.csdnimg.cn/blog_migrate/bd68835ad6162ed6f1dc03414d6eb530.png)
继续进入重载方法AbstractXmlApplicationContext的loadBeanDefinitions,发现委托给BeanDefinitionReader去解析加载BeanDefinition
![](https://i-blog.csdnimg.cn/blog_migrate/9032e8c825a67efb7f5163fe28489d9e.png)
进入BeanDefinitionReader的loadBeanDefinitions方法中:
![](https://i-blog.csdnimg.cn/blog_migrate/edb8db6c845c9dbef479a61af878ab8a.png)
继续进入BeanDefinitionReader的loadBeanDefinitions重载方法:
![](https://i-blog.csdnimg.cn/blog_migrate/4fe9f4d171a510c1d71b393fc2e0a874.png)
还是重载方法:
![](https://i-blog.csdnimg.cn/blog_migrate/50554cad3ab8886216866bfd01d71353.png)
重载方法:
![](https://i-blog.csdnimg.cn/blog_migrate/99cbbd52d13d78643f813a16b003ff39.png)
再重载:
![](https://i-blog.csdnimg.cn/blog_migrate/ecd7fcab06005a84710c99fda8badf09.png)
doLoadBeanDefinitions,带do开头的说明是真正干活的方法了
![](https://i-blog.csdnimg.cn/blog_migrate/4d023b490e8c02c8480506fa1f23e659.png)
进入doLoadBeanDefinitions,分为两个步骤,先将xml配置文件解析未document,然后再去解析bean标签,我们直接看解析bean标签部分
![](https://i-blog.csdnimg.cn/blog_migrate/61461193e90dbb8f894670148a672f10.png)
委托给BeanDefinitionDocumentReader去完成解析注册
![](https://i-blog.csdnimg.cn/blog_migrate/8300faf2a14824fd552f7006f41d8c86.png)
doRegisterBeanDefinitions方法去处理doc获取到的元素,又一个do
![](https://i-blog.csdnimg.cn/blog_migrate/15821c1a8ab3b152980b77a15d0ae709.png)
终于又来到了parseBeanDefinitions方法这里
![](https://i-blog.csdnimg.cn/blog_migrate/908c77f6a0f7cedd233db7270aa0d364.png)
注意看第一个子节点node的标签是dubbo:application,这个肯定不是spring自己定义的标签,所以就走到下面的方法delegate.parseCustomElement(ele)中,解析自定义标签,我们再截取下我们的配置文件做个比对
![](https://i-blog.csdnimg.cn/blog_migrate/735ea15c1ca9d8d5904a55461378d46d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/02738979e35fb249f33dd3a39a5836e6.png)
所以继续顺着代码往下走,进入org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)方法中
![](https://i-blog.csdnimg.cn/blog_migrate/185807d36fa4b5a5f6422a516e8112d3.png)
也是一个重载方法,继续往下走,这里就开始获取handler了,我们看到这个NamespaceHandler是通过readerContext调用getNamespaceHandlerResolver方法后再调用resolve方法并传入namespaceUri获取到的,namespaceHandlerResolver是什么呢,我们通过debug控制台输出的对象信息可以看到,namespaceHandlerResolver包含了有一个handlerMappings的map结构属性,保存了namespaceUri和NamespaceHandler的映射关系,我们这个标签是dubbo的,所以不用想就知道取到的映射一定是DubboNamespaceHandler,我们可以进去看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/119f9e91cb75e8add11bd4d5fdf42e37.png)
进入resolve后,大致分为7个步骤
![](https://i-blog.csdnimg.cn/blog_migrate/1f2bbf81c50ad02bcda0508a676f8bc2.png)
先拿到handlerMappings属性,此时都是value都是字符串,是我们配置的spring.handlers、spring.schemas文件里的内容:
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
根据namespaceUri获取对应的NamespaceHandler映射
判断获取到的NamespaceHandler的Class类型,如果是NamespaceHandler类型,说明已经初始化该对象了,直接返回
如果还是String类型,说明还没有初始化,则通过ClassUtils.forName创建Class对象
通过BeanUtils.instantiateClass(handlerClass),转换为NamespaceHandler类型,此时的NamespaceHandler即DubboNamespaceHandler
调用NamespaceHandler的init方法,即DubboNamespaceHandler的init
![](https://i-blog.csdnimg.cn/blog_migrate/7edec07ea87540d74fc0ab2987461761.png)
重新放入handlerMappings中,覆盖原来namespaceUri映射的value值,供下一次直接返回
返回handler以后,就开始解析标签了,这个时候就会进入dubbo中的parse方法中进行解析,最终返回DeanDefinition对象
![](https://i-blog.csdnimg.cn/blog_migrate/eec445189e92d9803e1186a4965f15f0.png)
我们继续进入DubboNamespaceHandler的parse方法中,最终的parse交给了父类NamespaceHandlerSupport来处理
![](https://i-blog.csdnimg.cn/blog_migrate/4c87bff82959250ca943bcfcb2af0502.png)
进入到NamespaceHandlerSupport中,先是根据Element去找对应的解析器,找的方法其实也是通过Map保存key-value的形式直接找的,此时为DubboBeanDefinitionParser,找到之后就开始解析
![](https://i-blog.csdnimg.cn/blog_migrate/634395d0280ba33e8ab1b74be0294e2b.png)
怎么解析我们就不关注了,这个就是属于第三方的业务了,如果以后想开发基于spring的组件,可以参考dubbo的自定义标签解析流程。
好了,spring的自定义标签解析就看到这里