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

ApplicationModel.getConfigManager()
ApplicationModel.getEnvironment()
ApplicationModel.getServiceRepository()
ApplicationModel.getExecutorRepository()
ApplicationModel.getName()

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

// 获取默认实例,兼容原来的单应用实例
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() 获取

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

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

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

最后修改 March 13, 2024: update doc (#2939) (c5fac2f6476)