回顾

大家好,我是老马。

最近 dubbo3.x 在公司内部分享,于是想系统梳理一下。

总体思路是官方文档入门+一些场景的问题思考+源码解析学习。

学习如何正确的扩展 SPI 实现

本文对 Dubbo3 多实例改造后如何扩展 SPI、贡献源码等相关变化进行一个简单的总结。

本文对Dubbo 3多实例改造后编码相关变化进行一个简单的总结。

层次模型

从只有ApplicationModel,新增 ScopeModel/FrameworkModel/ModuleModel 表达多实例的层次模型。

image.png 每个ScopeModel实例都会创建并绑定属于自己的重要成员:

  • ExtensionDirector
  • BeanFactory
  • ServiceRepository

ScopeModel 作为最基础的模型,可以在SPI/Bean/URL 等持有和传递。

SPI扩展

ExtensionScope

SPI 注解添加scope属性,标记其所属的作用域。

image.png ExtensionScope 与层次模型对应关系:

  • FRAMEWORK
  • APPLICATION
  • MODULE

ExtensionDirector

新增ExtensionDirector用于实现多层级的spi管理及依赖注入。

ExtensionDirector spi extension 创建流程如下:

image.png

  • 每个SPI 只能在匹配的Scope的ExtensionDirector上创建,目的是实现层级之间共享实例和正确注入依赖对象。即APPLICATION scope的SPI必定在ApplicationModel绑定的ExtensionDirector上创建,FRAMEWORK scope的SPI必定在FrameworkModel绑定的ExtensionDirector上创建。
  • 可见性与scope作用范围相关,这里的可见性是是否能直接注入依赖。即FRAMEWORK scope的SPI可以在FRAMEWORK/APPLICATION/MODULE 都可见,而 APPLICATION scope的SPI只能在APPLICATION/MODULE 可见。
  • 不可见的SPI需要通过上下文来获取,如可以通过URL传递ScopeModel,可以解决在FRAMEWORK spi访问 APPLICATION spi。

Scope 作用范围如下图: 上层对象可以注入本层及下层的SPI/Bean对象,下层对象不能注入上层的SPI/Bean对象。

image.png

Bean托管

新增ScopeBeanFactory用于内部Bean托管,支持在多个不同模块中共享一个实例对象。 ScopeBeanFactory 也支持scope,注入规则与ExtensionDirector相同。 用法请参考:FrameworkStatusReportService、RemoteMetadataServiceImpl、MetadataReportInstance

ServiceRepository

将原来的ServiceRepository拆分为3个类,分别对应3个层次的模型。 FrameworkServiceRepository ServiceRepository ModuleServiceRepository 将服务接口信息ProviderModel/ConsumerModel/ServiceDescriptor 注册到ModuleServiceRepository 中,同时在FrameworkServiceRepository 保存一份映射,用于根据请求查找对应的服务接口模型。

编码变化总结

1、如何获取ApplicationModel及应用数据

原方法:ApplicationModel 提供了一系列静态方法用于获取共享应用实例的数据

  [java]
1
2
3
4
5
ApplicationModel.getConfigManager() ApplicationModel.getEnvironment() ApplicationModel.getServiceRepository() ApplicationModel.getExecutorRepository() ApplicationModel.getName()

新办法:先找到ApplicationModel实例,然后通过实例的方法获取数据

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 获取默认实例,兼容原来的单应用实例 ApplicationModel.defaultModel().getApplicationEnvironment(); // 根据Module获取ApplicationModel moduleModel.getApplicationModel(); // 通过URL获取ApplicationModel ScopeModelUtil.getApplicationModel(url.getScopeModel()); // 通过Config配置类获取 ScopeModelUtil.getApplicationModel(serviceConfig.getScopeModel()); // SPI/Bean 可通过构造函数注入 public ConfigManager(ApplicationModel applicationModel) { this.applicationModel = applicationModel; } // SPI/Bean 通过实现ScopeModelAware接口注入 public class DefaultGovernanceRuleRepositoryImpl implements GovernanceRuleRepository, ScopeModelAware { private ApplicationModel applicationModel; @Override public void setApplicationModel(ApplicationModel applicationModel) { this.applicationModel = applicationModel; } // ... } // 枚举FrameworkModel的所有Application for (ApplicationModel applicationModel : frameworkModel.getApplicationModels()) { List<RegistryProtocolListener> listeners = applicationModel.getExtensionLoader(RegistryProtocolListener.class) .getLoadedExtensionInstances(); if (CollectionUtils.isNotEmpty(listeners)) { for (RegistryProtocolListener listener : listeners) { listener.onDestroy(); } } } // 枚举所有FrameworkModel for (FrameworkModel frameworkModel : FrameworkModel.getAllInstances()) { destroyProtocols(frameworkModel); }

2、如何获取SPI扩展实例

原方法:是通过静态方法 ExtensionLoader.getExtensionLoader() 获取

  [java]
1
ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(name, wrap);

新方法:通过ScopeModel或者ExtensionDirector获取

  [java]
1
applicationModel.getExtensionLoader(Cluster.class).getExtension(name, wrap);

3、如何查找服务模型

原方法:通过uniqueServiceName来在ServiceRepository 中lookup 服务模型

新方法:通过URL传递ScopeModel/ServiceModel,请参考RegistryProtocol

4、如何跨模块共享bean实例

原方法:通过静态变量保存bean实例

新方法:通过BeanFactory共享实例

5、常用工具类及处理技巧

根据ScopeModel获取某个层次的Model,已经做了兼容处理,scopeModel参数为null时返回默认实例:

  [java]
1
2
3
ScopeModelUtil.getFrameworkMode(scopeModel) ScopeModelUtil.getApplicationMode(scopeModel) ScopeModelUtil.getModuleMode(scopeModel)

需要改造的几种场景

  1. ExtensionLoader.getExtensionLoader()
  2. Application.defaultModel() 或者其它静态方法
  3. 在Framework层获取Application层的对象,如在Protocol中处理Application数据,QOS中遍历所有Application数据,请参考RegistryProtocol。
  4. 在静态方法中访问默认实例的数据
  5. 静态变量的bean实例、cache
  6. SPI接口中静态方法访问数据,可能要拆分为干净的SPI和Bean,请参考FrameworkStatusReportService/FrameworkStatusReporter。
  7. 可能某些URL还没有改造,需要在创建时设置ServiceModel/ScopeModel

参考资料

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/multi-instance/develop/