Dubbo官网上的文档是2.6.X的版本的,因为需要用到了比较新的Dubbo2.7.3,并且需要修改服务的暴露和引入的逻辑来适应项目。所以研究了一下,自己总结的笔记。(大量伪代码,自己下一份同版本的源码对比的看——可以像看小说一样阅读)
所需要的的前置理解
spring容器的刷新事件
(ApplicationEvent)事件抽象类,它下面已经有4个已经实现好的事件。
(ContextRefreshedEvent)容器刷新事件、(ContextStartedEvent)容器启动事件
(ContextClosedEvent)容器关闭事件、(ContextStoppedEvent)容器停止事件
URL
在Dubbo中,URL的作用十分重要,采用URL作为配置信息的统一格式,通过传递URL来传递配置信息。
注意:这里的URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。
Dubbo服务暴露(导出)的过程
Dubbo服务的导出过程基于spring容器发布刷新事件,Dubbo在接受到事件后,会立即执行服务的导出逻辑。
1、前置工作,主要是检查参数(配置文件)、组装URL(根据配置文件,组装URL ,通过传递URL,来传递配置信息)
2、导出服务,包含导出服务到本地(JVM)和导出服务到远程
3、向注册中心注册服务,用于服务的发现
一、起点
服务导出的入口方法是ServiceBean的onApplicationEvent方法。
//ServiceBean 是 Dubbo 与 Spring 框架进行整合的关键,可以看做是两个框架之间的桥梁。具有同样作用的类还有 ReferenceBean。
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//是否已导出&&是否已被取消导出 就是这个服务没有导出,并且(这个服务没有被取消导出),进入方法
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
//导出服务的方法
export();
}
}
二、前置工作
前置工作包含两个部分,配置检查、URL装配。
在导出服务之前,Dubbo需要检查用户的配置是否合理(包括为用户补充默认配置),检查完成后,根据这些配置组装URL。(URL作为配置信息的同一格式,通过传递URL来传递配置信息)
2.1.1检查配置
@Override
public void export() {
//先执行父类的export()方法
super.export();
// Publish ServiceBeanExportedEvent()
publishExportEvent();
}
------------------------分-------割--------线-----------------------------
父类的export()方法
public synchronized void export() {
//检查和更新附属的配置文件
checkAndUpdateSubConfigs();
// shouldExport 默认值为 true 如果export==null?true:export
// 应该导出为true导出,为null也导出 , 为false则不导出
//如果只想本地启动服务,不把服务暴露出去给别人使用,可以配置<dubbo:provider export="false" />禁止服务导出
if (!shouldExport()) {
return;
}
//如果应该延迟导出,就去延迟导出 ,这里不研究
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 应该导出+不延迟导出=立即导出服务
doExport();
}
}
------------------------分-------割--------线-----------------------------
下面分析 doExport方法
protected synchronized void doExport() {
// 这个服务不需要导出
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
//这个服务已经导出了
if (exported) {
return;
}
exported = true;
//如果path为空,给它赋值
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
//多协议多注册中心导出服务
doExportUrls();
}
------------------------分-------割--------线-----------------------------
2.1.2多协议多注册中心导出服务
//Dubbo允许我们使用不同的协议导出服务,也允许我们向多个 注册中心注册服务
//多协议多注册中心导出服务
private void doExportUrls() {
//通过loadRegistries加载注册中心链接(下面会分析这个方法)
List<URL> registryURLs = loadRegistries(true);
//遍历协议、在每个协议下导出服务
for (ProtocolConfig protocolConfig : protocols) {
// 通过(路径+guoup+version)构建一个string类型的 路径key 从协议配置类中,获取上下文路径.
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
//提供者模型
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
//ApplicationModel持有所有的提供者模型 .init初始化提供者模型
ApplicationModel.initProviderModel(pathKey, providerModel);
//组装URL 传入协议配置类 注册集合
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
loadRegistries 方法的逻辑
1、检测是否存在注册配置类集合,如果存在,遍历这个集合
2、检测address是否合法,如果合法,构建参数映射集合,也就是 map
3、构建注册中心链接列表 //List<URL> urls = UrlUtils.parseURLs(address, map);
4、遍历链接列表,并根据条件决定是否将其添加到 registryList (注册集合) List<URL> 中
------------------------分-------割--------线-----------------------------
2.1.3组装URL
配置检查完毕之后,根据配置以及一些其他信息,组装URL。URL是Dubbo配置的载体,通过URL可以让Dubbo的各种配置,在各个模块之间传递。URL 非常重要。下面是URL的组装过程。
doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
如果协议名为空,或空串,则将协议名变量设置为 dubbo
添加 side、版本、时间戳以及进程号等信息到 map 中
通过反射将对象的字段信息添加到 map 中
检测 <dubbo:method> 标签中的配置信息,并将相关配置添加到 map 中。
methods 为 MethodConfig 集合 ,MethodConfig中 存储了<dubbo:method> 的配置信息
检测 generic(通用的) 是否为 "true",并根据检测结果向 map 中添加不同的信息
添加 token 到 map 中
官方文档还有 (判断协议名是否为 injvm 也改到下面了、获取上下文路径 下面)
// 上述的大量文字只做了一件事,就是把版本、时间戳、方法名、以及各种配置对象信息放入Map中。
//配置对象信息有点笼统,其实就是你配置文件里写的标签。比如:<dubbo:application name="demo-provider"/>,对应源码里的application实体。<dubbo:protocol name="dubbo" host="192.168.0.29" port="20880" />,对应源码里的ProtocolConfig实体。这样是不是好理解多了。
获取IP
获取端口
URL 的构造方法 (协议名、IP、端口、上下文路径、map ) 创建URL对象
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
到此-->URL构建完毕。
//------------------------ 导出Dubbo服务-----------------------------
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
// 作用范围三个取值(none:什么都不做、scope != remote,导出到本地scope != local,导出到远程)
String scope = url.getParameter(SCOPE_KEY);
//如果scope !=none 则进入方法
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
// scope != remote 导出到本地
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
//导出到本地
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
// scope != local 导出到远程
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
// 日志
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
// 如果协议只是 injvm , 而不是redister
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
// 加载监视器链接
URL monitorUrl = loadMonitor(registryURL);
// 如果 monitor不等于null 将监视器链接作为参数添加到URL中
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
// 日志
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
// 对于提供程序,这用于启用自定义代理来生成调用程序
//获取代理秘钥
String proxy = url.getParameter(PROXY_KEY);
//如果秘钥 不为空
if (StringUtils.isNotEmpty(proxy)) {
// 把秘钥 添加到 registryURL 的map参数中
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
// 为服务提供类(ref),生成Imvoker
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//导出 服务 并生成exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
//不存在注册中心 ,仅仅导出服务
} else {
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
// 从 2.7.0版本 开始有了服务数据的存储
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
}
}
Invoker
上一段代码看完的话,发现不管是导出到本地,还是导出到远程。进行服务导出之前,均需要先创建Invoker,这是一个很重要的步骤。
Dubbo 官方解释:
Invoker是实体域,它是Dubbo的核心模型,其他模型都向它靠拢,或者转换成它。它代表一个可执行体,可向它发起Invoker调用,这个调用有可能是一个本地的实现,也有可能是一个远程的实现,也有可能是一个集群的实现。
Invoker是ProxyFactory创建的,Dubbo的默认代理工厂实现类是JavassistProxyFactory,对了说到这里,大家都知道的动态代理有三种(JDK的基于接口的动态代理、CGLib基于父类的动态代理、javassist基于字节码的动态代理)Dubbo源码中的动态代理默认采用的是javassist动态生成字节码来实现的。这个可以在配置文件中指定动态代理的方式,只不过一般没人用。
<!-- 默认情况下使用Javassist来进行动态代理-->
<dubbo:provider proxy="jdk" />或<dubbo:consumer proxy="jdk" />
下面进行Invoker的分析
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
// 为目标类创建 Wrapper(方法就是,从缓存中获取Wrepper ,如果缓存中没有,创建,写入缓存)
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
// 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
------------------------分-------割--------线-----------------------------
这里分析的是getWrapper方法中,如果缓存没有命中,创建Wrapper的方法makeWrapper();
// makeWrapper();方法分块理解(其实就是class的生成)
//一
进行一些初始化操作,创建c1、c2、c3、以及pts、ms、mns、等变量以及向c1、c2、c3中添加方法定义和类型装换代码。
//二
用于public级别的的字段生成天剑判断取值与赋值代码。
//三
为定义在当前类的方法生成判断语句,和方法调用语句。(需要对方法的重载进行校验)
//四
用于处理getter、setter以及以is\has\can开头的方法。处理方式是通过正则表达式获取方法的类型,以及属性名。之后为属性名生成判断语句,然后为方法生成调用语句。
//五
通过ClassGenerator 为刚刚生成的代码构建Class类,并通过反射创建对象。ClassGenerator 是Dubbo自己封装的。
**导出服务到本地**
//导出服务到本地(暴露在JVM中,不需要网络通信)
// 创建一个新的URL 将协议头、Host、端口设置成新的值(指向本地的值),
// 然后创建Invoker,InjvmProtocol的 export方法导出服务
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL) //设置协议头为 injvm
.setHost(LOCALHOST_VALUE) //设置Host为localhost
.setPort(0)
.build();
// 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法
Exporter<?> exporter = protocol.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。到此导出服务到本地就完了
------------------------分-------割--------线-----------------------------
导出服务到远程
//导出服务到远程(将信息暴露给远程客户端,调用时需要网络通信启动netty,打开端口)javassist生成字节码实现代理
打印日志,判断registryURLs不为空,则遍历,获取到每一个注册中心的registryURL对象
如果协议只是 injvm , 而不是redister,continue;
加载monitor链接,如果monitor链接!=null,把monitor链接作为参数添加到url中
//获取代理秘钥,对于提供程序,这用于启动自定义代理来生成调用程序。
String proxy = url.getParameter(PROXY_KEY);
//如果秘钥 不为空
if (StringUtils.isNotEmpty(proxy)) {
// 把秘钥 添加到 registryURL 的map参数中
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
// 为服务提供类(ref),生成Imvoker
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//导出 服务 并生成exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);