Spring源码分析之IOC (三)

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);
        }
    }

首先说下使用自定义标签需要哪些条件和步骤:

  1. 有需要自定义扩展的组件

  1. 有自定义xsd文件,用于自定义标签

  1. 有解析自定义标签组件,实现BeanDefinitionParser接口,用来解析xsd文件中的标签定义和自定义扩展组件定义

  1. 有自定义的Handler,扩展自NamespaceHandlerSupport,用来将解析组件注册到spring容器中

  1. 配置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个步骤

  1. 先拿到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
  1. 根据namespaceUri获取对应的NamespaceHandler映射

  1. 判断获取到的NamespaceHandler的Class类型,如果是NamespaceHandler类型,说明已经初始化该对象了,直接返回

  1. 如果还是String类型,说明还没有初始化,则通过ClassUtils.forName创建Class对象

  1. 通过BeanUtils.instantiateClass(handlerClass),转换为NamespaceHandler类型,此时的NamespaceHandler即DubboNamespaceHandler

  1. 调用NamespaceHandler的init方法,即DubboNamespaceHandler的init

  1. 重新放入handlerMappings中,覆盖原来namespaceUri映射的value值,供下一次直接返回

返回handler以后,就开始解析标签了,这个时候就会进入dubbo中的parse方法中进行解析,最终返回DeanDefinition对象

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

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

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

好了,spring的自定义标签解析就看到这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值