源码级剖析Dubbo与Spring的恩怨情仇

迷惑的表象

使用过Dubbo的攻城狮们一定对“Dubbo采用了全Spring配置方式,透明化接入应用,对应用没有任何API侵入”这句话耳熟能详,那么您是否有思考过,Dubbo是如何实现这种透明化接入的呢?再看一段Dubbo的配置代码:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <!-- 服务提供者名称-->
    <dubbo:application name="dubbo-provider"></dubbo:application>
    <!-- 服务注册中心地址-->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
    <!-- 用dubbo协议在20880端口暴露服务-->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 和本地服务一样实现远程服务 -->
    <bean id="demoService" class="com.peterchen.dubbo.DemoServiceImpl" />
    <!-- 声明需要暴露的服务接口-->
    <dubbo:service interface="com.peterchen.dubbo.DemoService" ref="demoService"/>

</beans>

Dubbo是如何透明化直接使用Spring配置的呢?原因是Dubbo 基于 Spring 的 Schema 进行了扩展,这样Spring在加载配置的时候同时也加载了Dubbo的配置。上面的xml配置文件头部是这样的:

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo

        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

关键的红色部分,其中“xmlns:dubbo”指明了XML的命名空间(XML NameSpace),其值“http://dubbo.apache.org/schema/dubbo”只是一个标识符,用来唯一确定一个命名空间而已,并不会作为检索内容(虽然大部分公司会在这个地址下放置关于XML Schema的描述文件,不放或者地址无法访问也并不影响)。“xsi:schemaLocation”的值是必须指向一个或多个真实存在的xsd文件的,xsd文件主要作用是:

  1. 定义可出现在文档中的元素;
  2. 定义可出现在文档中的属性;
  3. 定义那个元素是子元素;
  4. 定义元素的次序;
  5. 定义子元素的数目;
  6. 定义元素是否为空,或者是否可包含文本;
  7. 定义元素和属性的数据类型;
  8. 定义元素和属性的默认值及固定值;

具体XML Schema相关知识请参考:https://www.w3school.com.cn/schema/index.asp 

背后的真相

仅仅在XML文件加入命名空间和xsd文件路径只能让Spring正常的加载Dubbo相关的配置,但是如何解析和使用Dubbo相关的元素还得熟悉Spring的NamespaceHandler(命名空间处理器),要扩展Spring的命名空间处理器首先需要实现NamespaceHandler这个接口:

public interface NamespaceHandler {
	void init();
	BeanDefinition parse(Element element, ParserContext parserContext);
	BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}

Dubbo的命名空间处理器实现类是:org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

扫描二维码关注公众号,回复: 10557331 查看本文章
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

}

光实现了命名空间处理器还不能立即生效,需要将其配置到spring.handlers中,这样Spring才能加载它并在需要的时候使用它。命名空间处理器需要在classspath下的spring.handlers文件中进行配置,Dubbo的命名空间处理器配置文件位于dubbo-x.x.x.jar中的META-INF/spring.handlers。

 

 spring.handlers的内容是:

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

其含义就是凡是遇到“http://dubbo.apache.org/schema/dubbo”和“http://code.alibabatech.com/schema/dubbo”命名空间的标签都使用org.apache.dubbo.config.spring.schema.DubboNamespaceHandler来解析。而在DubboNamespaceHandler实现中我们发现所有 dubbo 的标签(除了annotation),都是统一用 DubboBeanDefinitionParser 进行解析的,annotation是使用AnnotationBeanDefinitionParser进行解析,它是基于一对一属性映射,将 XML 标签解析为 Bean 对象的。

Sping在启动时就会解析xml文件并加载相应的bean,处理自定义的XML配置标签的方法是org.springframework.beans.factory.xml.BeanDefinitionParserDelegate类的parseCustomElement函数:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = this.getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
}

代码很简单,Spring首先从已读取到的元素ele中获取到该元素使用的NamespaceURI,然后根据NamespaceURI的值获取到NamespaceHandler的具体实现类,再使用具体的命名空间处理器去解析自定义的标签元素(handler.parse()方法)。

经历这么一番“云雨”后,Dubbo中定义的Bean就被成功解析和加载到Spring 的容器中了。

发布了12 篇原创文章 · 获赞 26 · 访问量 3443

猜你喜欢

转载自blog.csdn.net/baidu_23747517/article/details/105207191