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文件太长,截取一部分如下图

解析自定义标签组件,实现BeanDefinitionParser接口,其中parse是BeanDefinitionParser定义的接口方法,交给第三方去实现,也就是解析自定义标签的核心逻辑

自定义Handler,扩展自NamespaceHandlerSupport,向spring注册自定义组件解析器,像我们最熟悉的<dubbo:service/>、<dubbo:reference/>标签等

配置spring.handlers、spring.schemas文件,在dubbo项目中我们可以看到该配置文件再config模块下的spring扩展模块,Spring会通过SPI技术进行加载该模块,使第三方服务与spring解耦。


做好这些之后,下面我们启动下dubbo服务,通过跟踪dubbo的启动一起看下spring是如何解析dubbo标签的。
配置下我们的dubbo提供者服务,我们使用xml的形式来配置,引入xsd文件,并使用dubbo的相关标签,application表示应用信息,registry表示注册中心,protocol表示协议,service表示服务提供者。

然后去启动我们的服务,同样使用ClassPathXmlApplicationContext类来加载我们的配置信息,只是配置文件改为我们配置的dubbo-provider.xml。

还是走相同的代码逻辑,我们再走一遍加深一下记忆:

进入到AbstractApplicationContext的refresh中:

进入obtainFreshBeanFactory,调用refreshBeanFactory方法:

进入refreshBeanFactory,开始看到加载BeanDefinitions的代码:

进入loadBeanDefinitions方法中,设置解析配置文件的环境和解析器BeanDefinitionReader

继续进入重载方法AbstractXmlApplicationContext的loadBeanDefinitions,发现委托给BeanDefinitionReader去解析加载BeanDefinition

进入BeanDefinitionReader的loadBeanDefinitions方法中:

继续进入BeanDefinitionReader的loadBeanDefinitions重载方法:

还是重载方法:

重载方法:

再重载:

doLoadBeanDefinitions,带do开头的说明是真正干活的方法了

进入doLoadBeanDefinitions,分为两个步骤,先将xml配置文件解析未document,然后再去解析bean标签,我们直接看解析bean标签部分

委托给BeanDefinitionDocumentReader去完成解析注册

doRegisterBeanDefinitions方法去处理doc获取到的元素,又一个do

终于又来到了parseBeanDefinitions方法这里

注意看第一个子节点node的标签是dubbo:application,这个肯定不是spring自己定义的标签,所以就走到下面的方法delegate.parseCustomElement(ele)中,解析自定义标签,我们再截取下我们的配置文件做个比对


所以继续顺着代码往下走,进入org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)方法中

也是一个重载方法,继续往下走,这里就开始获取handler了,我们看到这个NamespaceHandler是通过readerContext调用getNamespaceHandlerResolver方法后再调用resolve方法并传入namespaceUri获取到的,namespaceHandlerResolver是什么呢,我们通过debug控制台输出的对象信息可以看到,namespaceHandlerResolver包含了有一个handlerMappings的map结构属性,保存了namespaceUri和NamespaceHandler的映射关系,我们这个标签是dubbo的,所以不用想就知道取到的映射一定是DubboNamespaceHandler,我们可以进去看一下:

进入resolve后,大致分为7个步骤

先拿到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

重新放入handlerMappings中,覆盖原来namespaceUri映射的value值,供下一次直接返回
返回handler以后,就开始解析标签了,这个时候就会进入dubbo中的parse方法中进行解析,最终返回DeanDefinition对象

我们继续进入DubboNamespaceHandler的parse方法中,最终的parse交给了父类NamespaceHandlerSupport来处理

进入到NamespaceHandlerSupport中,先是根据Element去找对应的解析器,找的方法其实也是通过Map保存key-value的形式直接找的,此时为DubboBeanDefinitionParser,找到之后就开始解析

怎么解析我们就不关注了,这个就是属于第三方的业务了,如果以后想开发基于spring的组件,可以参考dubbo的自定义标签解析流程。
好了,spring的自定义标签解析就看到这里