多实例让源码开发更复杂,学习如何正确的扩展 SPI 实现
本文对 Dubbo3 多实例改造后如何扩展 SPI、贡献源码等相关变化进行一个简单的总结。
本文对Dubbo 3多实例改造后编码相关变化进行一个简单的总结。
层次模型
从只有ApplicationModel,新增 ScopeModel/FrameworkModel/ModuleModel 表达多实例的层次模型。 每个ScopeModel实例都会创建并绑定属于自己的重要成员:
- ExtensionDirector
- BeanFactory
- ServiceRepository
ScopeModel 作为最基础的模型,可以在SPI/Bean/URL 等持有和传递。
SPI扩展
ExtensionScope
SPI 注解添加scope属性,标记其所属的作用域。 ExtensionScope 与层次模型对应关系:
- FRAMEWORK
- APPLICATION
- MODULE
�
ExtensionDirector
新增ExtensionDirector用于实现多层级的spi管理及依赖注入。
ExtensionDirector spi extension 创建流程如下:
- 每个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对象。
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
最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54b)