Dubbo 基于xml文件分析主流程源码 (4)

news2024/11/25 18:44:27

目录

前提

JDK实现SPI

Dubbo实现SPI

Dubbo源码

1. 找到Dubbo的命名空间处理类,也就是Dubbo的入口类

 2. 将dubbo标签交给spring进行管理,就是从 BeanDefinition----> Bean的过程。

3. 服务暴露

4. 服务引入

总结

仿写Dubbo


前提

1. Dubbo源码是深度依赖Spring的,因此了解Spring源码是分析Dubbo的前提,否则会很吃力。

2. SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件

JDK实现SPI

说的直白点,SPI就是面向接口编程。就是创建接口的全限定名称的文件,然后在文件中配置好这个接口的实现类,然后代码中就可以获取到这个接口的、配置好的所有的实现类。JDK是已经提供了实现SPI思想的实现类:ServiceLoader。

瞄一眼源码:

下面演示使用JDK提供的类实现SPI的代码:

1、首先配置接口的全限定名称:

接口:

package com.enjoy.spi.tt;

public interface P {
    public void sayHello();
}

 实现类P1

package com.enjoy.spi.tt;

public class P1 implements P {

    @Override
    public void sayHello() {
        System.out.println("hello P1");
    }
}

 实现类P2

package com.enjoy.spi.tt;

public class P2 implements P {
    @Override
    public void sayHello() {
        System.out.println("hello P2");
    }
}

测试类:

 以上就是JDK提供的SPI实现方式,很强大,但也有不足。比如:实现类很多,我们每次都要拿到所有的实现类,无法精确定位到具体的某一个。

Dubbo实现SPI

基于JDK提供的SPI实现的缺陷,Dubbo进行了补强。下面,我只会分析部分核心补强功能,为我们下面Dubbo源码分析提供一些铺垫。

Dubbo提供的实现SPI思想的类: ExtensionLoader。

 配置全限定接口文件:

接口也需要打上对应的Dubbo注解

 P3实现类:

package com.enjoy.spi.tt2;

public class P3 implements PP {
    @Override
    public void sayHello() {
        System.out.println("hello P3");
    }
}

P4:

package com.enjoy.spi.tt2;

public class P4 implements PP {
    @Override
    public void sayHello() {
        System.out.println("hello P4");
    }
}

测试类:

Dubbo源码

基于xml形式解读dubbo源码,其实相对而言还是非常清晰的。

1. 找到Dubbo的命名空间处理类,也就是Dubbo的入口类

 2. 将dubbo标签交给spring进行管理,就是从 BeanDefinition----> Bean的过程。

3. 服务暴露

接口:

package com.enjoy.service;

import com.enjoy.entity.OrderEntiry;

public interface OrderService {
    OrderEntiry getDetail(String id);//目标对象,方法,参数
}

实现类需要打上Dubbo的@Service注解

package com.enjoy.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.enjoy.entity.OrderEntiry;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public OrderEntiry getDetail(String id) {
        System.out.println(super.getClass().getName()+"被调用一次:"+System.currentTimeMillis());
        OrderEntiry orderEntiry = new OrderEntiry();
        orderEntiry.setId("O0001");
        orderEntiry.setMoney(1000);
        orderEntiry.setUserId("U0001");

        return orderEntiry;
    }

}

配置xml

<?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:context="http://www.springframework.org/schema/context"
       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://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
	 http://code.alibabatech.com/schema/dubbo
	 http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!--全局配置-->
    <dubbo:provider timeout="3000" />
    <!-- 服务提供方应用名称, 方便用于依赖跟踪 -->
    <dubbo:application name="busi-server"/>
    <!-- 使用本地zookeeper作为注册中心 -->
    <dubbo:registry address="zookeeper://192.168.0.105:2181" />

    <!--name指示使用什么协议监听端口:dubbo/rmi/rest-->
    <dubbo:protocol id="d1"  name="dubbo" port="20880" />
    <dubbo:protocol id="d2"  name="dubbo" port="20882" />

    <!-- 通过xml方式配置为bean, 让spring托管和实例化 -->
    <bean id="orderService" class="com.enjoy.service.OrderServiceImpl"/>
    <!-- 声明服务暴露的接口,并暴露服务 -->
    <dubbo:service interface="com.enjoy.service.OrderService" ref="orderService" protocol="d1" />
</beans>

所有的Dubbo都会交给Spring进行管理,生成对应的BeanDefinition。而服务暴露的核心流程,我们只需要关注以上的接口和实现类实例化Bean的过程即可。核心方法还是initializeBean。在分析Spring源码的时候,这个方法已经强调过很多次了。

 下面开启debug,关注xml文件中配置的

<dubbo:service interface="com.enjoy.service.OrderService" ref="orderService" />

逐步debug来到 afterPropertiesSet方法处,而这个方法被Dubbo的子类ServiceBean重写。

 也就是说ServiceBean会把服务端的Bean进行封装,像

<dubbo:provider timeout="3000" delay="1"/>

<dubbo:application name="busi-server" />

<dubbo:registry address="zookeeper://192.168.0.105:2181" />

<dubbo:protocol name="dubbo" port="20882" />

这些标签对应的Bean对象。在我们处理需要暴露的服务(OrderService)的时候,都会被封装进OrderService对应的ServiceBean对象中。

 

下面再来看看服务暴露的详细内容:

 进入方法:

 这个方法核就是2件事:

1.  拿到xml文件中配置的信息

2. 根据配置的协议进行服务暴露:

继续进行服务暴露的底层代码: doExportUrlsFor1Protocol方法很长,直接跳转到核心代码处,核心逻辑就是将ServiceBean对象包装成Invoker对象进行暴露

至此,服务暴露分析完毕。再往下就是更为底层的网络协议的内容的,没有研究过,不做分析。

4. 服务引入

通过上诉代码分析,我们知道服务暴露的最直接的代码是在ServiceBean的 afterPropertiesSet方法中。 其实,服务引入就是 ReferenceBean 的 afterPropertiesSet方法中:

 核心代码:

其实,服务端也会实例化对应的ReferenceBean对象,而这个对象的ref指向的是一个代理对象。

那么这个代理对象是如何生成的呢?

进入 createProxy 方法,我们发现它还是干了2件事情:

1. 根据接口信息和URL信息生成Invoker对象,服务暴露的时候也是生成Invoker对象进行暴露的,正好对应。

2.  协议根据invoker对象,生成一个代理对象。

至此,消费端对象引入分析完毕。

总结

 Dubbo的服务注册与发现机制,核心动作就2个。protocol.export(serviceInvoker
) 将服务给暴露出去, protocol.refer(DemoService.class, registryUrl)将网络上的
服务引入进来。

1. 服务端启动,我们会将配置好的Dubbo需要暴露的Bean对象生成ServiceBean,然后包装成Invoker对象发送到注册中心。
2. 消费端引入到invoker对象,会先生成一个ReferenceBean对象,而这个对象的ref会指向一个代理对象,这个代理对象是有Invoker生成的。

3.消费端调用的时候,实际上最终调用的是这个代理对象,而代理对象会直接刷锅给invoker对象,由网络处理消费端的invoker对象。

4. 服务端会根据invoker对象的实际处理逻辑,调用到服务端的具体业务逻辑进行相应的处理。

个人觉得,Dubbo就是一个增强版的RPC而已。
 

仿写Dubbo

既然是仿写,肯定是仿写核心流程,我们就不再需要zookeeper了。直接写一个服务端通过网络协议暴露出去,服务端直接引入即可。

1.  dubbo暴露的对象使用的是invoker,那么我们也需要使用这个invoker对象。

2.  Dubbo的服务注册与发现机制,核心动作就2个。protocol.export(serviceInvoker
) 将服务给暴露出去, protocol.refer(DemoService.class, registryUrl)将网络上的
服务引入进来。那么我们肯定也是要从重写协议,至少需要export和refer这两个方法的。

下面开始仿照重写:

Dubbo最终的Invoker对象:

咱们也仿写Invoker:

package com.enjoy.protocol;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.*;
import com.enjoy.service.DemoService;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * SimpleInvoker.
 */
public class SimpleInvoker<T> implements Invoker<T> {
    private  T target;
    private Class<T> type;
    private URL url;

    public SimpleInvoker(T service, Class<T> type, URL url){
        this.target = service;
        this.type = type;
        this.url = url;
    }

    @Override
    public Class<T> getInterface() {
        return type;
    }

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        Method method = null;
        try {
            method = type.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
            return new RpcResult(method.invoke(target, invocation.getArguments()));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public URL getUrl() {
        return url;
    }

    @Override
    public boolean isAvailable() {
        return true;
    }

    @Override
    public void destroy() {

    }

}

Dubbo协议比较复杂,咱就写个最简单的


package com.enjoy.protocol;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.Version;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.rmi.RmiRemoteInvocation;
import com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationFactory;

import java.lang.reflect.Proxy;
import java.rmi.RemoteException;

/**
 * RmiProtocol.
 */
public class RmiProtocol implements Protocol {

    public static final int DEFAULT_PORT = 1099;

    @Override
    public int getDefaultPort() {
        return DEFAULT_PORT;
    }

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        //创建spring rmi服务
        final RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
        rmiServiceExporter.setRegistryPort(invoker.getUrl().getPort());
        rmiServiceExporter.setServiceName(invoker.getUrl().getPath());
        rmiServiceExporter.setServiceInterface(invoker.getInterface());

        //此时目标服务没有,需要通过invoker调通,使用代理
        T service = (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{invoker.getInterface()},
                new InvokerInvocationHandler(invoker));

        rmiServiceExporter.setService(service);//DemoService service,如果能拿它,直接设入就ok
        try {
            rmiServiceExporter.afterPropertiesSet();
        } catch (RemoteException e) {
            throw new RpcException(e.getMessage(), e);
        }
        return null;
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return new SimpleInvoker(doRefer(type,url),type,url);
    }

    public <T> T doRefer(Class<T> type, URL url) throws RpcException {
        final RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
        if (url.getParameter(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()).equals(Version.getProtocolVersion())) {
            rmiProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() {
                @Override
                public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
                    return new RmiRemoteInvocation(methodInvocation);
                }
            });
        }
        rmiProxyFactoryBean.setServiceUrl(url.toIdentityString());
        rmiProxyFactoryBean.setServiceInterface(type);
        rmiProxyFactoryBean.setCacheStub(true);
        rmiProxyFactoryBean.setLookupStubOnStartup(true);
        rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
        rmiProxyFactoryBean.afterPropertiesSet();
        return  (T) rmiProxyFactoryBean.getObject();
    }

    @Override
    public void destroy() {

    }


}

测试类

package com.enjoy;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;
import com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler;
import com.alibaba.dubbo.rpc.proxy.jdk.JdkProxyFactory;
import com.enjoy.protocol.RmiProtocol;
import com.enjoy.protocol.SimpleInvoker;
import com.enjoy.service.DemoService;
import com.enjoy.service.DemoServiceImpl;
import com.enjoy.util.ProtocolUtil;
import org.junit.Test;

import java.io.IOException;
import java.lang.reflect.Proxy;

public class RpcProtocolTest_01 {
    URL url = URL.valueOf("rmi://127.0.0.1:9001/"+ DemoService.class.getName());

    /**
     * Protocol连接服务端invoker
     * 将目标服务调用信息,包装成为invoker实体,暴露到网络上
     *
     * 当网络信息到达,将触发invoker的invoke方法,最终将调用转到目标service上
     */
    @Test
    public void provider() throws IOException {
        DemoService service = new DemoServiceImpl();

        Invoker<DemoService> invoker = new SimpleInvoker(service,DemoService.class,url);
        Protocol protocol = new RmiProtocol();
        //暴露对象
        protocol.export(invoker);
        System.out.println("Dubbo server 启动");
        // 保证服务一直开着
        System.in.read();
    }

    /**
     * Protocol连接消费端invoker
     * 将要调用的信息,包装成invoker实体,向网络发送
     *
     * 本地调用接口代理时,最终方法被转到invoker的invoke方法上,向网络发送
     */
    @Test
    public void consumer() {
        //协议
        Protocol protocol = new RmiProtocol();

        //消费invoker,负责发送协议调用信息
        Invoker<DemoService> invoker = protocol.refer(DemoService.class, url);

        //做一个动态代理,将调用目标指向invoker即可
        DemoService service = (DemoService)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{invoker.getInterface()},
                new InvokerInvocationHandler(invoker));//反射逻辑

        String result = service.sayHello("peter");
        System.out.println(result);


    }


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/513820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CentOS7 yum update y更新后黑屏解决方案

解决方法 一 可以ssh访问 因为update的时候更新了系统内核&#xff0c;导致驱动问题&#xff0c;所以会黑屏。 更改一下yum的配置即可解决: vi /etc/yum.conf#增加&#xff1a;excludecentos-release*excludekernel*如果以上问题还未解决&#xff0c;可以试试下面的方法 其…

架构模式之分层模式

1 概念 分层架构模式是一种非常常见的架构设计模式&#xff0c;很多人都在用&#xff0c;可能不知道它的概念。分层模式背后的理念是&#xff0c;具有相同功能的组件将被组织成水平层。因此&#xff0c;每一层在应用程序中都扮演着特定的角色。 在这种模式中&#xff0c;…

自学自动化测试,第一份工作就18K,因为掌握了这些技术

我个人的情况是有1年自动化测试工作经验半年的实习经验&#xff0c;2020年毕业&#xff0c;专业通信工程&#xff0c;大一的时候学过C语言&#xff0c;所以一直对于编程感兴趣&#xff0c;之所以毕业后没做通信的工作&#xff0c;通信行业的朋友应该都明白&#xff0c;通信的天…

DolphinScheduler3.1.5安装部署

1.下载 DolphinScheduler下载地址&#xff1a;https://dolphinscheduler.apache.org/zh-cn/download/3.1.5 选择二进制包 下载&#xff0c;点击 jar 名称 就行 ​ 2.环境 CentOS Linux release 7.5.1804 (Core)java version "1.8.0_212"mysql version 5.7.16-log…

信息化 VS 数字化,哪个更适合当代企业?

各位数据的朋友&#xff0c;大家好&#xff0c;我是老周道数据&#xff0c;和你一起&#xff0c;用常人思维数据分析&#xff0c;通过数据讲故事。 现在大家都在谈数字化转型升级&#xff0c;那到底什么是数字化&#xff0c;是不是新瓶装旧酒呢&#xff1f;今天就和大家来谈谈…

【华为机考】专题突破 第二周:前缀和与差分 1109

刷题顺序参考于 《2023华为机考刷题指南&#xff1a;八周机考速通车》 前言 前缀和是指某序列的前n项和&#xff0c;可以把它理解为数学上的数列的前n项和&#xff0c;而差分可以看成前缀和的逆运算。合理的使用前缀和与差分&#xff0c;可以将某些复杂的问题简单化。 关于各类…

CnOpenData中国标准数据

一、数据简介 按照《中华人民共和国标准化法》的定义&#xff0c;标准是指农业、工业、服务业以及社会事业等领域需要统一的技术要求。标准作为一种通用性的规范语言&#xff0c;在合理利用国家资源、保障产品质量、提高市场信任度、促进商品流通、维护公平竞争、保障安全等方面…

机器学习-9 降维算法——PCA降维

降维算法 算法概述降维的概念降维的作用降维的本质常见算法分类主成分分析&#xff08;PCA&#xff09;降维分析 算法流程PCA算法的流程图PCA算法的实现步骤协方差矩阵 算法应用sklearn库中的主成分分析PCA实现高维数据可视化鸢尾花案例手写体数字图像识别案例 算法总结PCA算法…

MyBatis缓存-一级缓存--二级缓存的非常详细的介绍

目录 MyBatis-缓存-提高检索效率的利器 缓存-官方文档 一级缓存 基本说明 一级缓存原理图 代码演示 修改MonsterMapperTest.java, 增加测试方法 结果 debug 一级缓存执行流程 一级缓存失效分析 关闭sqlSession会话后 , 一级缓存失效 如果执行sqlSession.clearCache(…

linux安装nacos步骤

安装前提&#xff1a;服务器已安装JDK 一、nacos下载 Nacos下载地址&#xff1a;Releases alibaba/nacos GitHub 根据springboot版本选择nacos版本 版本说明 alibaba/spring-cloud-alibaba Wiki GitHub 二、nacos解压、修改配置文件 #选择安装目录 cd /home/dxhy/appl…

一款基于 Spring Cloud 开源的医疗信息系统

今天给大家介绍一个医院信息系统开源项目&#xff0c;相对比较完整&#xff0c;采用的技术栈是 Spring cloud和Spring boot 2.x&#xff0c;比较主流&#xff0c;正在做这方面系统的童鞋们可以参考一下&#xff01; 主要功能按照数据流量、流向及处理过程分为临床诊疗、药品管…

云原生|详解Kubernetes Operator在项目中的开发应用

目录 一、使用场景 &#xff08;一&#xff09;client-go中处理逻辑 &#xff08;二&#xff09;controller-runtime中处理逻辑 二、使用controller-runtime开发operator项目 &#xff08;一&#xff09;生成框架代码 &#xff08;二&#xff09;定义crd字段 &#xff0…

分布式消息队列RocketMQ概念详解

目录 1.MQ概述 1.1 RocketMQ简介 1.2 MQ用途 1.3 常见MQ产品 2.RocketMQ 基本概念 2.1 消息 2.2 主题 2.3 标签 2.4 队列 2.5 Producer 2.6 Consumer 2.7 NameServer 2.8 Broker 2.9 RocketMQ 工作流程 1.MQ概述 1.1 RocketMQ简介 RocketMQ 是阿里开源的分布式消…

云原生:从基本概念到实践,解析演进与现状

文章目录 云原生&#xff1a;从基本概念到实践&#xff0c;解析演进与现状概念演进之路DockerKubernetesCloud NativeServerless 业界现状总结 结语 云原生&#xff1a;从基本概念到实践&#xff0c;解析演进与现状 本文仅用于简单普及&#xff0c;达到的目的是给没接触过或者很…

苹果手机无法开机?黑屏打不开怎么办?出现这种问题的解决办法分享!

各位在使用苹果手机的小伙伴有没有遇到苹果手机突然就黑屏开不了机&#xff0c;打电话也没有任何反应&#xff0c;手机也无法关机重启&#xff0c;这是什么问题呢&#xff1f;我们遇到这种问题该如何去处理呢&#xff1f; 小编今天就来跟大家说说苹果手机突然开不了机的原因以及…

【Linux命令】脚本里常用的几个命令

脚本里常用的命令 一、SORT命令1.1、语法格式1.2常用选项 二、uniq命令2.1命令格式2.2常用选项2.3小实验&#xff0c;过滤出现三次以上的IP地址 三、tr命令3.1语法格式3.2常用选项3.3实验 四、cut命令4.1语法格式4.2常用选项 五、split命令5.1语法格式5.2常用选项 六、eval七、…

在行 | “数智”为离散制造发展注入动能

在行业现场解析行业难题&#xff0c; 用主题方案创新数智价值。 制造业作为我国实体经济的主体&#xff0c;是国民经济体系的重要组成部分&#xff0c;其中以离散制造比重最大&#xff0c;是解决就业等民生问题的支柱。随着技术和经济水平的提升&#xff0c;市场对离散制造行业…

CnOpenData淘宝村淘宝镇名单数据

一、数据简介 随着电商的迅猛发展&#xff0c;以淘宝村为代表的新型城镇化不断推进。淘宝镇和淘宝村是电商巨头阿里巴巴推出的一系列支持中小企业、新创企业发展的计划&#xff0c;旨在为中小企业及创新企业提供融资、营销、培训、咨询等服务。截至2022年&#xff0c;全国涌现了…

创新案例 |探索 Tive 80% 的收入增长得益于智能物流服务、跟踪和实时可视化

您正在寻找可靠的物流解决方案吗&#xff1f; Tive 是领先的智能物流服务提供商&#xff0c;提供跟踪和实时可见性解决方案。使用 Tive&#xff0c;您可以主动监控公路、空运、海运和铁路运输。它可以帮助您减少运输问题并确保准时和全面交付&#xff0c;从而改善客户体验。 …

融合CDN和单CDN的产品对比

仅针对特定地理位置的公司可以使用单一CDN解决方案&#xff0c;建议网站内容在全球分发的优先选择融合CDN来进行加速。 如果您的网站内容/应用程序大多是静态的&#xff0c;那么单一CDN解决方案可能适合大多数市场需求&#xff1b;但如果您的流量高于平均水平&#xff0c;媒体流…