dubbo源码解析(一)spring+dubbo案例整合

目录

 

一、spring+dubbo案例整合

1、项目结构如下

2、dubbo服务/消费配置

二、spring+dubbo入口分析

   1、dubbo依赖配置

  2、DubboNamespaceHandler分析

 2.1、dubbo常用标签

 2.2、dubbo标签解析

 2.3、具体解析


一、spring+dubbo案例整合

        项目github地址:https://github.com/liushangzaibeijing/dubbodemo.git

1、项目结构如下

    该项目是一个父子依赖的多模块maven项目

  •    dubboolddemo(root) 父项目: 主要用于设置子项目通用maven依赖(统一控制通用依赖的版本信息,紫烈不需要声明)
  •        dubbo-api  (child)        子项目: 主要是通用项目依赖(比如接口,实体,枚举,常量)
  •        dubbo-core (child)       子项目:dubbo接口的服务提供方(服务提供者)
  •        dubbo-oms (child)       子项目:dubbo接口的服务调用放(服务消费者)

2、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://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 具体的实现bean -->
    <bean id="demoService" class="com.xiu.dubbo.service.impl.DemoServiceImpl" />
    <!--  dubbo相关配置  -->
    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="dubbo-core" />
    <!-- 使用zookeeper注册中心暴露服务地址,多个地址中间用,号隔开 ?backup=192.168.137.131:2181,192.168.137.132:2181 -->
    <dubbo:registry address="zookeeper://182.92.189.235:2181" />
    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20881" />

    <!-- 声明需要暴露的服务接口 -->
    <!--<dubbo:service timeout="3000" interface="com.xiu.dubbo.service.DemoService" ref="demoService"-->
                   <!--cluster="failover" />-->
    <!-- 也可以使用注解的形式进行处理  使用注解的方式需要对服务提供者使用
         @Service(dubbo自己的注解而非Spring) 服务使用者使用@Reference注解修饰  -->
    <dubbo:annotation package="com.xiu.dubbo.service" />
</beans>
  •     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://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="dubbo-oms" />
    <!-- 使用zookeeper注册中心暴露服务地址,多个地址中间用,号隔开 ?backup=192.168.137.131:2181,192.168.137.132:2181 -->
    <dubbo:registry address="zookeeper://182.92.189.235:2181" />

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:reference id="demoService" interface="com.xiu.dubbo.service.DemoService" />
    <!--失败自动切换,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟 ,可通过retries="2"来设置重试次数(不含第一次)。
    默认就是failover,可以不写 ,包括(failfast/failback/forking) ,forks="2",并行调用多个服务器,只要一个成功即返回。
    通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过它来设置最大并行数。 -->

    <!-- 也可以使用注解的形式进行处理 使用注解形式发现单元测试无法引用到对应的服务-->
    <!--<dubbo:annotation package="com.xiu.dubbo.service" />-->
    <bean id ="omsDemoService" class="com.xiu.dubbo.oms.service.impl.OmsDemoServiceImpl" />


</beans>
  • dubbo的maven依赖
<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dubbo</artifactId>
      <version>2.5.3</version>
      <exclusions>
         <exclusion>
            <groupId>org.springframework</groupId>
           <artifactId>spring</artifactId>
         </exclusion>
       </exclusions>
</dependency>

二、spring+dubbo入口分析

   1、dubbo依赖配置

   查看我们引入dubbo的maven依赖对应的jar如下:

   如上图所示为dubbo与spring整合的入口,一共有3个文件4部分,下面我们针对这些文件来说明其中的作用。在分析这些文件之前我们需要有一定的有关spring自定义标签知识储备才能理解上述文件,有关spring自定义标签请参考我的这篇博文:spring源码分析之自定义标签的解析

  1.  dubbo.xsd文件:dubboxml配置的相关约束文件,使自定义标签使用者按照规范去使用标签,而非随意的添加或者修改标签的属性,所谓xsd约束文件定义了我们书写<dubbo:application> <dubbo:service> <dubbo:referece>标签的时候的合法约束(比如有id属性,以及各个标签的出现顺序等等)。
  2. spring.schemas文件:将对应的命名空间的xsd文件和真实的xsd文件做绑定 用来做自定义标签的校验
  3. spring.handler文件:将命名空间和对应的命名处理器做绑定,让spring根据不同的命名空间获取到其对应的命名处理器从而进行相应的解析处理
  4. DubboNamespaceHandler:命名处理器用来解析dubbo的相关xml配置 。

从上面的分析中我们呢可以知道dubbo通过xml配置后之所以能使用是DubboNamespaceHandler 命名处理器解析相关的标签并生成对应的bean实例被spring容器管理,下面我们来分析一下

DubboNamespaceHandler 的解析过程。

  2、DubboNamespaceHandler分析

   2.1、dubbo常用标签

标签信息标签描述
<dubbo:application />用于配置应用信息,dubbo计算其依赖关系
<dubbo: module />用于配置应用模块信息(适用于一个应用多模块)
<dubbo:protocol />用于配置服务提供者的访问协议
<dubbo:registry />用于配置注册中心 Multicast、Zookeeper、Redis、Dubbo四种注册中心
<dubbo:provider />用于配置服务提供者的通用配置值,provider是原始的服务提供方式:配置参数超级多,比较繁琐,其上的配置为默认可以被所有的<dubbo:service />直接继承,即设置<dubbo:service>和<dubbo:protocol>标签的默认值,
<dubbo:service />用于配置服务提供者暴露自己的服务
<dubbo:consumer />用于配置服务消费者的默认值,即<dubbo:reference>标签的默认值,同 <dubbo:provider />与<dubbo:service />关系
<dubbo:reference />用于配置服务消费者引用服务
<dubbo:annotation />支持注解扫描的方式注册service服务和引用服务
<dubbo: monitor />dubbo监控模块配置

 

 

 

 

2.2、dubbo标签解析

     DubboNamespaceHandler 的init方法将dubbo的所有标签交由DubboBeanDefinitionParser来进行解析,通过名字大概知道其作用是将dubbo标签解析并转换为BeanDefinition对象

了解过spring源码的bean实例化过程的读者都清楚BeanDefinition是所有spring管理的bean实例的抽象,其中包含我们实际的beanClass,会在合适的时机application.refresh()方法实例化为spring管理的bean实例。有关beanDefintion分析请参考:spring源码分析之BeanDefinition相关

	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("monitor", new DubboBeanDefinitionParser(MonitorConfig.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 DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

2.3、具体解析

    DubboBeanDefinitionParser parse()方法是针对dubbo相关标签xml配置的解析过程。

//<dubbo:xxxx>相关标签的具体解析逻辑  
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
        //初始化一个BeanDefintion对象(是我们上图中对应的ApplicationConfig、ServiceConfig对应的class的beanDefition)
        //用于spring后续生成对应的bean实例
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        //获取(生成)bean的id
        // 逻辑如下:直接获取其中的id属性 没有获取name, 还没有获取interface属性(ProtocolConfig特殊处理为dubbo)
        //没有获取className, 接着判断是否已经存在,存在id后拼接数字 最终将获取的id放入BeanDefinition的MutablePropertyValues(属性集合中)
        String id = element.getAttribute("id");
        if ((id == null || id.length() == 0) && required) {
            String generatedBeanName = element.getAttribute("name");
            if (generatedBeanName == null || generatedBeanName.length() == 0) {
                if (ProtocolConfig.class.equals(beanClass)) {
                    generatedBeanName = "dubbo";
                } else {
                    generatedBeanName = element.getAttribute("interface");
                }
            }
            if (generatedBeanName == null || generatedBeanName.length() == 0) {
                generatedBeanName = beanClass.getName();
            }
            id = generatedBeanName;
            int counter = 2;
            while(parserContext.getRegistry().containsBeanDefinition(id)) {
                id = generatedBeanName + (counter ++);
            }
        }
        if (id != null && id.length() > 0) {
            if (parserContext.getRegistry().containsBeanDefinition(id))  {
                throw new IllegalStateException("Duplicate spring bean id " + id);
            }
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        }
        //对<dubbo:protocol>进行解析 主要是其可以配置多个这里将多个进行合并
        if (ProtocolConfig.class.equals(beanClass)) {
            for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
                BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
                PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
                if (property != null) {
                    Object value = property.getValue();
                    if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
                        definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
                    }
                }
            }
        }
        //对<dubbo:service>进行处理 主要是将其class属性包装成BeanDefintion作为ref属性存放到dubbo:service对应的bean
        else if (ServiceBean.class.equals(beanClass)) {
            String className = element.getAttribute("class");
            if(className != null && className.length() > 0) {
                RootBeanDefinition classDefinition = new RootBeanDefinition();
                classDefinition.setBeanClass(ReflectUtils.forName(className));
                classDefinition.setLazyInit(false);
                parseProperties(element.getChildNodes(), classDefinition);
                beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
            }
        }
        //对<dubbo:provider>进行处理 主要是其包含子属性<dubbo:service>进行解析 其中dubbo:provider中的属性对<dubbo:service>都适用
        else if (ProviderConfig.class.equals(beanClass)) {
            parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
        }
        ///对<dubbo:consumer>进行处理 同<dubbo:provider> 前者是消费者配置 后者是服务者配置
        else if (ConsumerConfig.class.equals(beanClass)) {
            parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
        }
        //获取相关dubbo XXXConfig的所有方法 调用其setter方法获取对应的xml配置的属性 并进行相关的解析
        Set<String> props = new HashSet<String>();
        ManagedMap parameters = null;
        for (Method setter : beanClass.getMethods()) {
           //省略其中复杂的属性解析(获取其setXXX 其中xxx应该是对应的xml的属性 对其配置的属性进行解析存放)
        }
        //获取相关dubbo XXXConfig的其他属性信息存放到属性对象的parameters中
        NamedNodeMap attributes = element.getAttributes();
        int len = attributes.getLength();
        for (int i = 0; i < len; i++) {
            Node node = attributes.item(i);
            String name = node.getLocalName();
            if (! props.contains(name)) {
                if (parameters == null) {
                    parameters = new ManagedMap();
                }
                String value = node.getNodeValue();
                parameters.put(name, new TypedStringValue(value, String.class));
            }
        }
        if (parameters != null) {
            beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
        }
        //最终返回对应的beanDefinition对象
        return beanDefinition;
    }

   如上的代码确实比较长,但是经过具体的分析后我们发现其逻辑很简单大致分成两部分.

  • 初始化一个BeanDefinition(RootBeanDefinition对象)设置对应的<dubbo:xxx> 对应的class属性(这个骨架已经处理完成)该beanDefintion会在spring容器启动的过程中实例化
  • 接下来进行不同<dubbo:xxx> 详细数据的解析比如 id属性,attributes属性以及service的class属性,以及获取方法名的属性,最终这些解析出来的属性会存放到beanDefinition的MutablePropertyValues(多属性对象)中,(相当于填充这个BeanDefintion骨架的血肉)。

 

 

 

         

 

 

 


版权声明:本文为liushangzaibeijing原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。