博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
dubbo源码解析一
阅读量:6543 次
发布时间:2019-06-24

本文共 7613 字,大约阅读时间需要 25 分钟。

  最近研究dubbo,准备把dubbo翻个底朝天。

      dubbo框架的介绍,官网的开发文档已经很详细了,不再介绍。文档也有源码导读,覆盖了大部分关键模块,大致看了一遍,对dubbo对理解加深了不少,不过中间也有不少疑问。个人认为,dubbo的开发文档非常详尽,从使用、设计、源码方面都使开发者对dubbo的认识变得简单。但是而我写源码解析的原因,是因为官方文档的局限,毕竟受众太多,内容全而固定,如果不是非常严肃的研究框架,有种想睡着的意思。本文从dubbo使用者的角度,带着问题过一遍dubbo的整体流程,进一步加深理解并留下文字备忘。

      一般人看代码习惯先找入口,我也是,比如一般java程序的main函数。我们先找找dubbo的入口,dubbo的使用,一般以下几个步骤:

1、开发provider接口及实现类,spirng的xml配置dubbo bean,内容有注册中心、端口、接口bean等;

2、comsumer同上

3、comsumer端引入provider端的接口定义

4、comsumer端注入provider端bean,像本地一样调用接口

以下是一个comsumer的demo,stockService是dubbo远程接口

public static void main(String args[]) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "dubbo-client.xml" }); final StockService stockService = (StockService) context.getBean("StockService"); int previousAmount = 0; previousAmount = stockService.getSum().intValue(); System.out.println("previousAmount=" + previousAmount); System.exit(0); } 我们就从标红的代码开始看起,StockService是comsumer引入provider端的一个接口,既然可以在这可以直接使用,必然是dubbo为之生成了一个代理。 在标红处打断点,可以看出StockService的代理类,如下

 

 这里能看出,StockService的代理类是由jdk动态代理生成,执行的invoke方法是传入的InvokerInvocationHandler实例的invoke方法。

继续debug

InvokerInvocationHandler实例构造方法,传入一个invoker,最后调用invoker.invoke。Invoker是dubbo里的实体域,任何执行体都往invoker上靠,详细信息见,在这里,简单的说就是一个封装里远程调用的代理对象。继续走,对InvokerInvocationHandler右键find usages,只有两个地方使用

两个代理工厂,javassist 和 jdk 代理,看代码可以看出,先根据拿到的接口,生成代理类,并实例化。

两种方式功能一样,dubbo默认使用javassist,对生成代理的几种方式,dubbo作者的可以参考下。

代理类反编译后格式如下

以上代码来自,并不是我的demo,能理解即可。可以看到,和jdk的动态代理生成的代理类非常相似,实现接口的方法,直接调用来invocationHandler的invoke方法。

看到这里,冒出了几个问题。

1、invoker在dubbo作为执行体,什么时候初始化并加载的?

2、第一层invoker为什么是mock?invoke链的结构和内容怎么生成的?

第一个问题不难想到,dubbo依赖spring框架,bean的生命周期必然被spring的管理使用远程服务接口,得先引入服务,接下来参照,分析引入服务的过程

 

服务引用过程

我们追踪源码的顺序是 调用服务->发现一些内容已经加载(如invoker)-> 追踪内容如何初始化->继续看服务如何调用。

在源码追踪过程中,我们约定默认大部分配置为缺省,并跳过安全检查、分支逻辑。

 

 以上来自官方文档。

ReferenceBean对应了dubbo配置的reference标签,看下demo里的配置

这个配置经过dubbo的解析器解析后,就是referenceBean,因为referenceBean需要返回stockService的一个代理类,所以得实现FactoryBean接口。

FactoryBean是spring的一个扩展点,被其他bean注入的时候,并不是返回当前类的实例,而是调用覆盖接口的getObject方法,返回一个自定义的类对象。

就开始的demo来说,当执行 final StockService stockService = (StockService) context.getBean("stockService"); 的时候,即注入stockService的时候,会调用id=stockService的bean的getObject方法。这也就是开发文档提到的懒汉式引用。

接下来我们关注referenceBean.getObject方法,源码不贴了,详细流程在开发文档上有,查看

主要工作是

1、导入其他dubbo配置到bean中,如果没有尝试从系统变量或者其他路径导入

2、createProxy,传入的map参数,保存了其他dubbo配置即本地ip等信息

createProxy方法包含内容(按demo配置)

1、加载注册中心  List<URL> us = loadRegistries(false);

2、调用Protocol的代理类refer方法 构建 Invoker 实例 invoker = Protocol$Adpative.refer(interfaceClass, urls.get(0));

3、生成代理类 return (T) proxyFactory.getProxy(invoker);

 loadRegistries 读取配置

<dubbo:registry address="127.0.0.1:2181" protocol="zookeeper" timeout="300000"/>

封装成URL,格式如下

作为参数传入Protocol$Adpative的refer方法。

Protocol$Adpative是Protocol的动态代理类,作为一个静态属性,在serverConfig由spi的自适应语句生成。

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

protocol代理类默认由javaassist生成,生成的class的refer内容如下:

public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)            throws java.lang.Class {        if (arg1 == null)            throw new IllegalArgumentException("url == null");        com.alibaba.dubbo.common.URL url = arg1;        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());        if (extName == null)            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("                    + url.toString() + ") use keys([protocol])");        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader                .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);        return extension.refer(arg0, arg1);    } 这里url的协议头是registry,所以extName=registry传入 getExtension方法,返回类Protocol实例extension,接下来我们看下返回的extension是什么。

这里重点在createExtension 方法

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

  1. 通过 getExtensionClasses 获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象
 
以上步骤中,第一个步骤是,依据是传入的Protocol接口和registry关键字加载RegistryProtocol类,第二步反射生成RegistryProtocol对象,第三步骤遍历set方法,从bean工厂找到依赖并注入RegistryProtocol对象。
第四个步骤,则会找到所有Protocol的wrapper类,对RegistryProtocol对象层层包裹,最后返回。

什么是Wrapper类

那什么样类的才是Dubbo扩展机制中的Wrapper类呢?Wrapper类是一个有复制构造函数的类,也是典型的装饰者模式。下面就是一个Wrapper类:

class A{    private A a;    public A(A a){        this.a = a;    }}

类A有一个构造函数public A(A a),构造函数的参数是A本身。这样的类就可以成为Dubbo扩展机制中的一个Wrapper类。

 

怎么配置Wrapper类

在Dubbo中Wrapper类也是一个扩展点,和其他的扩展点一样,也是在META-INF文件夹中配置的。

比如前面举例的ProtocolFilterWrapper和ProtocolListenerWrapper就是在路径dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中配置的:

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol registry=com.alibaba.dubbo.registry.integration.RegistryProtocol qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

在Dubbo加载扩展配置文件时,有一段如下的代码:

这段代码的意思是,如果扩展类有复制构造函数,就把该类存起来,供以后使用。有复制构造函数的类就是Wrapper类。通过clazz.getConstructor(type)来获取参数是扩展点接口的构造函数。注意构造函数的参数类型是扩展点接口,而不是扩展类。

以Protocol为例。配置文件dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中定义了filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper

ProtocolFilterWrapper代码如下:

ProtocolFilterWrapper有一个构造函数public ProtocolFilterWrapper(Protocol protocol),参数是扩展点Protocol,所以它是一个Dubbo扩展机制中的Wrapper类。ExtensionLoader会把它缓存起来,供以后创建Extension实例的时候,使用这些包装类依次包装原始扩展点。

回到createExtension的第四点,dubbo从META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol配置文件中,挨个找到Protocol的wrapper类,把RegistryProtocol对象层层包裹后,返回给ServerConfig对象,最后调用wrapper类的refer方法返回invoker。

Protocol的配置文件有3个wrapper类,按顺序排列如下。

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

层层包裹后,refer的调用路径相反,Protocol$Adpative->QosProtocolWrapper->ProtocolListenerWrapper->ProtocolFilterWrapper->RegistryProtocol

qos开启dubbo命令行功能,ProtocolListenerWrapper对几个关键方法注册监听回调方法,ProtocolFilterWrapper对默认filter和自定义filter分装成链,依次执行,最后执行RegistryProtocol的refer方法。

 

 

1、初始化服务目录Directory类

2、Directory订阅zk的dubbo临时节点更新

3、cluster获取invoker

Directory可以看做是 Invoker 集合,且这个集合中的元素会随注册中心的变化而进行动态调整。Provider和Consumer向Zookeeper注册临时节点,当连接断开时删除相应的注册节点。

Consumer订阅Providers节点的子节点,通过zkclient的通知api实时感知Provider的变化情况,实时同步自身的Invoker对象,保证RPC的可用性。这里的cluster是前面RegistryProtocol的createExtension方法的injectInstanse时注入的自适应cluster对象,和protocol对象一样,先对cluster接口生成动态代理类,在接口方法join里,先实例化@SPI标签的name的FailoverCluster对象,再从配置文件中遍历cluster的wrapper类进行封装,最后执行wrapper类的join方法。

Invoker invoker = cluster.join(directory); 完整调用路径:Cluster$Adpative->MockClusterWrapper->FailoverCluster.join

按照这个调用路径,最后返回了包含FailoverClusterInvoker对象的MockClusterInvoker实例,即文章开头debug进入的第一个invoker

继续回到ReferenceConfig代码,在返回了invoke后还有最后一步 proxyFactory.getProxy(invoker) 生成代理类,这个并不复杂,不再展开讲述。

总结:本文从使用者第一视角跟踪调用远程服务的过程,及过程中代理类,invoker链如何在服务引入的时候初始化。下节继续跟踪invoker链

 

转载于:https://www.cnblogs.com/wxbty/p/10345148.html

你可能感兴趣的文章
Docker for Web Developers目录
查看>>
原生js获取window高和宽
查看>>
每天一个linux命令(19):find命令概述
查看>>
背包九讲(转载)
查看>>
java中的权限修饰符的理解
查看>>
ASP.NET从MVC5升级到MVC6 RC2 总目录 - 发布在RC2Release之后
查看>>
《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记
查看>>
SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)
查看>>
HDU 4864 Task(贪心或高斯消元)
查看>>
java开始到熟悉66-69
查看>>
python time 转换&运算tips
查看>>
使用jQuery的validation后,无法引发asp.net按钮的事件处理程序
查看>>
ios判断设备是iphone还是ipad
查看>>
checkbox
查看>>
Leetcode | Path Sum I && II
查看>>
BLOB
查看>>
hdu 5504 GT and sequence
查看>>
版本新特性
查看>>
借教室 Vijos 1782 NOIP2012 D2T2 Lazy 线段树
查看>>
[转载]如何使用eclipse 生成runnable jar包
查看>>