多实例让源码开发更复杂,学习如何正确的扩展 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 提供了一系列静态方法用于获取共享应用实例的数据

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

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

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

2、如何获取SPI扩展实例

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

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

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

  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时返回默认实例: 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

最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54b)