Dubbo Export Service#
简介#
整个逻辑大致可分为三个部分:
前置工作,主要用于检查参数,组装
URL
。导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。
向注册中心注册服务,用于服务发现。
源码分析#
服务导出的入口方法是 ServiceBean
的 onApplicationEvent
。
onApplicationEvent(ContextRefreshedEvent event)
: 在收到 Spring 上下文刷新事件后执行服务导出操作isDelay()
:true
: 不延迟导出false
: 延迟导出
supportedApplicationListener
: 表示当前的 Spring 容器是否支持ApplicationListener
,这个值初始为false
1 前置工作#
采用
URL
作为配置信息的统一格式,所有扩展点都通过传递URL
携带配置信息。
1.1 检查配置#
export()
: 对export
和delay
配置进行了检查,并根据配置执行相应的动作ServiceConfig#doExport()
:检测
<dubbo:service>
标签的interface
属性合法性,不合法则抛出异常检测
ProviderConfig
、ApplicationConfig
等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。检测并处理泛化服务和普通服务类
检测本地存根配置,并进行相应的处理
对
ApplicationConfig
、RegistryConfig
等配置类进行检测,为空则尝试创建,若无法创建则抛出异常
1.2 多协议多注册中心导出服务#
ServiceConfig-doExportUrls()
: 对多协议,多注册中心进行了支持AbstractInterfaceConfig#loadRegistries(boolean provider)
: 加载注册中心链接检测是否存在注册中心配置类,不存在则抛出异常
构建参数映射集合,也就是
map
构建注册中心链接列表
遍历链接列表,并根据条件决定是否将其添加到
registryList
中
1.3 组装 URL#
URL
之于 Dubbo,犹如水之于鱼
doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)
注意:这里的
URL
并非java.net.URL
,而是com.alibaba.dubbo.common.URL
appendParameters(Map<String, String> parameters, Object config)
: 用于将对象字段信息添加到map
中实现上则是通过反射获取目标对象的 getter 方法,并调用该方法获取属性值
然后再通过 getter 方法名解析出属性名
如果用户传入了属性名前缀,此时需要将属性名加入前缀内容
最后将
<属性名,属性值>
键值对存入到map
中
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
if (methods != null && !methods.isEmpty()) {
// 检测 type 属性是否为空,或者空串(分支1 ⭐)
if (argument.getType() != null && argument.getType().length() > 0) {
if (argument.getIndex() != -1) {
// 检测 ArgumentConfig 中的 type 属性与方法参数列表中的参数名称是否一致,不一致则抛出异常(分支2 ⭐)
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
} else { // 分支3 ⭐
for (int j = 0; j < argtypes.length; j++) {}
}
}
// 用户未配置 type 属性,但配置了 index 属性,且 index != -1
} else if (argument.getIndex() != -1) { // 分支4 ⭐
}
}
}
// 获取 ArgumentConfig 列表
for (遍历 ArgumentConfig 列表) {
if (type 不为 null,也不为空串) { // 分支1
1. 通过反射获取 interfaceClass 的方法列表
for (遍历方法列表) {
1. 比对方法名,查找目标方法
2. 通过反射获取目标方法的参数类型数组 argtypes
if (index != -1) { // 分支2
1. 从 argtypes 数组中获取下标 index 处的元素 argType
2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
} else { // 分支3
1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
2. 添加 ArgumentConfig 字段信息到 map 中
}
}
} else if (index != -1) { // 分支4
1. 添加 ArgumentConfig 字段信息到 map 中
}
}
2 导出 Dubbo 服务#
根据 url
中的 scope
参数决定服务导出方式,分别如下:
scope = none
: 不导出服务scope != remote
: 导出到本地scope != local
: 导出到远程
Invoker
是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke
调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
2.1 Invoker 创建过程#
Invoker
是由ProxyFactory
创建而来,Dubbo 默认的ProxyFactory
实现类是JavassistProxyFactory
JavassistProxyFactory+getInvoker(T proxy, Class<T> type, URL url)
Wrapper+getWrapper(Class<?> c)
: 仅包含一些缓存操作Wrapper-makeWrapper(Class<?> c)
:初始化操作
c1
: 用于存储setPropertyValue
方法代码c2
: 用于存储getPropertyValue
方法代码c3
: 用于存储invokeMethod
方法代码pts
: 用于存储成员变量名和类型ms
: 用于存储方法描述信息(可理解为方法签名)及Method
实例mns
: 为方法名列表dmns
: 用于存储“定义在当前类中的方法”的名称
为
public
级别的字段生成条件判断取值与赋值代码为定义在当前类中的方法生成判断语句,和方法调用语句
处理 getter、setter 以及以 is/has/can 开头的方法
通过
ClassGenerator
为刚刚生成的代码构建 Class 类,并通过反射创建对象
ClassGenerator
: 核心是toClass()
的重载方法toClass(ClassLoader, ProtectionDomain)
,该方法通过 javassist 构建 Class
2.2 导出服务到本地#
exportLocal(URL url)
:首先根据
URL
协议头决定是否导出服务若需导出,则创建一个新的
URL
并将协议头、主机名以及端口设置成新的值然后创建
Invoker
,并调用InjvmProtocol
的export
方法导出服务
InjvmProtocol+export(Invoker<T> invoker)
: 仅创建了一个InjvmExporter
2.3 导出服务到远程#
RegistryProtocol+export(final Invoker<T> originInvoker)
:调用
doLocalExport
导出服务向注册中心注册服务
向注册中心进行订阅
override
数据创建并返回
DestroyableExporter
RegistryProtocol-doLocalExport(final Invoker<T> originInvoker)
DubboProtocol+export(Invoker<T> invoker)
:获取服务标识
创建
DubboExporter
将
<key, exporter>
键值对放入缓存中本地存根相关代码
启动服务器
优化序列化
DubboProtocol-openServer(URL url)
: 若某个端口上已有服务器实例,此时则调用reset
方法重置服务器的一些配置DubboProtocol-createServer(URL url)
:检测是否存在
server
参数所代表的Transporter
拓展,不存在则抛出异常创建服务器实例
检测是否支持
client
参数所表示的Transporter
拓展,不存在也是抛出异常
Exchangers+bind(URL url, ExchangeHandler handler)
HeaderExchanger+bind(URL url, ExchangeHandler handler)
: 创建HeaderExchangeServer
实例Transporters+bind(URL url, ChannelHandler... handlers)
Transporters+getTransporter()
: 获取的Transporter
是在运行时动态创建的,类名为TransporterAdaptive
,也就是自适应拓展类。默认为NettyTransporter
NettyTransporter+bind(URL url, ChannelHandler listener)
NettyServer
AbstractServer+AbstractServer(URL url, ChannelHandler handler)
AbstractServer#doOpen()
:NettyServer
创建的过程,dubbo 默认使用的NettyServer
是基于 netty 3.x 版本实现的protected void doOpen() throws Throwable { NettyHelper.setNettyLoggerFactory(); // 创建 boss 和 worker 线程池 ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true)); ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true)); ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS)); // 创建 ServerBootstrap bootstrap = new ServerBootstrap(channelFactory); final NettyHandler nettyHandler = new NettyHandler(getUrl(), this); channels = nettyHandler.getChannels(); bootstrap.setOption("child.tcpNoDelay", true); // 设置 PipelineFactory bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() { NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this); ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("decoder", adapter.getDecoder()); pipeline.addLast("encoder", adapter.getEncoder()); pipeline.addLast("handler", nettyHandler); return pipeline; } }); // 绑定到指定的 ip 和端口上 channel = bootstrap.bind(getBindAddress()); }
2.4 服务注册#
对于 Dubbo 来说,注册中心虽不是必需,但却是必要的
RegistryProtocol+export(Invoker<T> originInvoker)
RegistryProtocol+register(URL registryUrl, URL registedProviderUrl)
获取注册中心实例
向注册中心注册服务
2.4.1 创建注册中心#
AbstractRegistryFactory+getRegistry(URL url)
:先访问缓存
缓存未命中则调用
createRegistry
创建Registry
然后写入缓存
ZookeeperRegistryFactory+createRegistry(URL url)
ZookeeperRegistry+ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter)
CuratorZookeeperTransporter+connect(URL url)
: 默认为CuratorZookeeperTransporter
CuratorZookeeperClient+CuratorZookeeperClient(URL url)
: 用于创建和启动CuratorFramework
实例public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorWatcher> { private final CuratorFramework client; public CuratorZookeeperClient(URL url) { super(url); try { // 创建 CuratorFramework 构造器 CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder() .connectString(url.getBackupAddress()) .retryPolicy(new RetryNTimes(1, 1000)) .connectionTimeoutMs(5000); String authority = url.getAuthority(); if (authority != null && authority.length() > 0) { builder = builder.authorization("digest", authority.getBytes()); } // 构建 CuratorFramework 实例 client = builder.build(); // 添加监听器 client.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState state) { if (state == ConnectionState.LOST) { CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED); } else if (state == ConnectionState.CONNECTED) { CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED); } else if (state == ConnectionState.RECONNECTED) { CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED); } } }); // 启动客户端 client.start(); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } } }
2.4.2 节点创建#
以 Zookeeper 为例,所谓的服务注册,本质上是将服务配置数据写入到 Zookeeper 的某个路径的节点下
FailbackRegistry+register(URL url)
ZookeeperRegistry#doRegister(URL url)
: 调用了 Zookeeper 客户端创建服务节点AbstractZookeeperClient+create(String path, boolean ephemeral)
: 通过递归创建当前节点的上一级路径,然后再根据ephemeral
的值决定创建临时还是持久节点CuratorZookeeperClient+createEphemeral(String path)
: 通过 Curator 框架创建节点
整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务
2.5 订阅 override 数据#
// 待补充
Tips#
class
+
method:public
class
#
method:protected
class
-
method:private