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

news2025/1/12 19:03:47

目录

前提

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/501926.html

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

相关文章

Information-Theoretic Segmentation by Inpainting Error Maximization总结笔记

一、《Inpainting Error Maximization》提出的提问和解决方法 《Inpainting Error Maximization》提出的提问&#xff1a;神经网络通常需要大量的手动标记的训练数据来达到最先进的性能。对于标记数据稀缺或昂贵的问题的适用性往往取决于从 相关领域迁移学习到的表示的能力。 …

在Ubuntu系统中安装JDK 17并配置环境变量

文章目录 打开终端&#xff0c;更新Ubuntu软件包列表&#xff1a;安装OpenJDK 17&#xff1a;检查JDK是否正确安装&#xff1a;配置环境变量&#xff1a; 结语 在Ubuntu系统中安装JDK 17并配置环境变量&#xff0c;可以按照以下步骤进行&#xff1a; 打开终端&#xff0c;更新U…

基于SpringBoot+Vue的宠物管理系统了解一下

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

C/C++每日一练(20230508) 相交链表、字符数组、排序链表

目录 1. 相交链表 &#x1f31f;&#x1f31f; 2. 字符数组 ※ 3. 排序链表 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 相交链表 给你两个单链表…

【Sentinel 规则持久化配置】

Sentinel 规则持久化 Sentinel 规则持久化一、修改order-service服务1.引入依赖2.配置nacos地址 二、修改sentinel-dashboard源码1. 解压2. 修改nacos依赖3. 添加nacos支持4. 修改nacos地址5. 配置nacos数据源6. 修改前端页面7. 重新编译、打包项目8.启动 Sentinel 规则持久化 …

Vulfocus-struts2初了解

CVE-2013-2135 漏洞原理&#xff1a; 配置了通配符*&#xff0c;访问name.action时使用name.jsp来渲染页面&#xff0c;但是在提取name解析时&#xff0c;对其执行了OGNL表达式解析&#xff0c;所以导致了命令执行。如果一个请求与任何其他定义的操作不匹配&#xff0c;它将匹…

注意力机制之SGE Attention

论文 Spatial Group-wise Enhance: Improving Semantic Feature Learning in Convolutional Networks 论文链接 paper:Spatial Group-wise Enhance: Improving Semantic Feature Learning in Convolutional Networks 模型结构 论文主要内容 卷积神经网络&#xff08;CNN&a…

uboot的环境变量相关源码分析

一、uboot的环境变量基础 1.1、环境变量的作用 (1)让我们可以不用修改uboot的源代码&#xff0c;而是通过修改环境变量就可以影响uboot运行时的一些特性。譬如说修改bootdelay环境变量就可以更改系统开机自动启动时倒计时的秒数。 1.2、环境变量的优先级 环境变量的优先级高…

c++(日期类)

本章主要以日期类为例&#xff0c;练习重载各种运算符&#xff0c;需要重点掌握&#xff1a; 1、日期类的<、 <、 、>、 >、 !、重载 2、日期类的 、 、-、-、、--、重载 3、日期类 - 日期类 4、日期类的 << 、>>重载 5、权限问题 目录 1、运算符…

DOM事件(下)

事件执行机制 ●今天来聊一聊事件的执行机制 ●什么是事件的执行机制呢&#xff1f; ○思考一个问题&#xff1f; ○当一个大盒子嵌套一个小盒子的时候&#xff0c;并且两个盒子都有点击事件 ○你点击里面的小盒子&#xff0c;外面的大盒子上的点击事件要不要执行 事件的传播&…

ASEMI代理ADM706SARZ-REEL原装ADI车规级ADM706SARZ-REEL

编辑&#xff1a;ll ASEMI代理ADM706SARZ-REEL原装ADI车规级ADM706SARZ-REEL 型号&#xff1a;ADM706SARZ-REEL 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOIC-8 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;8 工作温度:-40C~85C …

JavaWeb《后端内容:2. MVC-IOC-ServletContext-事务管理-过滤器Filter》

1. 准备和回顾 本篇基于上一篇JavaWeb《后端内容&#xff1a;1. Tomcat - Servlet - Thymeleaf》 继续使用mvc进行优化&#xff0c;复制上面模块的代码&#xff0c;并新建工件和项目和配置服务器 这里可以再好好复习揣摩一下这里index页面的逻辑部分&#xff0c;尤其是关键字的…

PostgreSQL类型系统——Data Types

PostgreSQL Data Types PostgreSQL has a rich set of native data types available to users. Users can add new types to PostgreSQL using the CREATE TYPE command. PostgreSQL有一组丰富的本地数据类型可供用户使用。用户可以使用CREATE TYPE命令向PostgreSQL添加新类型…

[Gitops--12]微服务项目发布

微服务项目发布 1. 微服务项目发布 [流水线] [创建] [下一步] [创建] 1.1 mall-gateway 确认项目中的路由配置都正确 mall-gateway/src/main/resources/application.yml如果不一样就批量替换一下,一共7处 1.2 mall-auth-server mall-auth-server1.3 mall-cart 1.4 mall-c…

ChatGLM-LLaMA-chinese-insturct 学习记录(含LoRA的源码理解)

ChatGLM-LLaMA-chinese-insturct 前言一、实验记录1.1 环境配置1.2 代码理解1.2.1 LoRA 1.4 实验结果 二、总结 前言 介绍&#xff1a;探索中文instruct数据在ChatGLM, LLaMA等LLM上微调表现&#xff0c;结合PEFT等方法降低资源需求。 Github: https://github.com/27182812/Ch…

Win10任务栏透明,3个超好用解决方法!

案例&#xff1a;win10任务栏透明怎么办&#xff1f; 【我的电脑不知道为什么任务栏突然就变透明了&#xff0c;现在不知道该如何解决&#xff0c;遇到这种情况应该怎么办呀&#xff1f;】 Win10任务栏是Windows 10操作系统的一部分&#xff0c;通常默认为不透明。然而&#…

asp.net+sqlserver企业公司进销存管理系统

基于WEB的进销存管理系统主要企业内部提供服务&#xff0c;系统分为管理员&#xff0c;和员工2部分。 在本基于WEB的进销存管理系统中分为管理员&#xff0c;和普通用户2中模式&#xff0c;其中管理人员主要是对企业内商品类型。商品信息商品的出入库信息&#xff0c;以及员工…

堆栈溢出一般是什么原因?

堆栈是一个在计算机科学中经常使用的抽象数据类型。堆栈中的物体具有一个特性&#xff1a; 最后一个放入堆栈中的物体总是被最先拿出来&#xff0c; 这个特性通常称为后进先出(LIFO)队列。 堆栈中定义了一些操作。 两个最重要的是PUSH和POP。 PUSH操作在堆栈的顶部加入一 个元素…

MySQL深度分页

1. 什么是深度分页 深度分页问题的本质是在 MySQL 数据库中&#xff0c;通过 LIMIT 和 OFFSET 关键字进行分页时&#xff0c;MySQL 需要在每次查询时扫描整张表&#xff0c;直到找到当前页的数据。这种查询方式需要进行大量的磁盘 I/O 和内存操作&#xff0c;导致查询效率非常…

Microsoft Edge新功能测评体验

Microsoft Edge使用体验 Microsoft Edge是一款现代化的浏览器&#xff0c;它拥有众多功能和强大的性能&#xff0c;为用户带来更加流畅的浏览体验。 Edge最近推出了分屏功能&#xff0c;支持一个窗口同时显示两个选项卡&#xff0c;这可以大大提高生产力和多任务处理能力。 一…