MITK中两种微服务的三层架构实现对比

两种微服务的三层架构实现对比

按 OSGi 规范的三层架构(模块层 / 生命周期层 / 服务层)对比

CppMicroServices(us::)与 CTK PluginFramework 的实现细节。

源码位置:

  • CppMicroServices: D:/MITK/Modules/CppMicroServices/core/src/
  • CTK: D:/MITK-superbuild-2023.12/ep/src/CTK/Libs/PluginFramework/

OSGi 规范定义三层:模块层 (Module Layer,打包与依赖)、

生命周期层 (Lifecycle Layer,安装/启动/停止)、

服务层(Service Layer,注册/发现)。两套实现对三层的完成度截然不同。


一、模块层(Module Layer)------"模块是什么、依赖怎么声明"

CppMicroServices(us::) CTK
模块单位 普通动态库(.dll/.so),无特殊打包 CTK 插件(带 META-INF/MANIFEST.MF 的 Qt 插件)
元数据 可选 manifest.json(版本、autoLoadDir) MANIFEST.MF 强制Plugin-SymbolicNamePlugin-VersionRequire-PluginPlugin-ActivationPolicyctkPluginConstants.h: L132-240
依赖声明 没有------模块间依赖靠链接器(编译期)解决 Require-Plugin: com.acme.test; plugin-version="[1.0,2.0)"------带版本区间的运行期依赖(L204-209)
依赖解析 无解析过程 checkRequirePlugin:递归解析 Require 链 + 版本区间匹配
资源系统 编译进二进制的资源容器(usModuleResourceContainer 资源存 SQLitePLUGIN_RESOURCES_TABLE
身份注册 ModuleRegistry: name → Module*(自增 id) ctkPlugins: location(URL) → QSharedPointer<ctkPlugin>(SQLite 持久化安装状态)

CTK 依赖解析实现(ctkPluginFrameworkContext.cpp: L269-300

cpp 复制代码
void ctkPluginFrameworkContext::checkRequirePlugin(ctkPluginPrivate *plugin)
{
  QListIterator<ctkRequirePlugin*> i(plugin->require);   // MANIFEST 里的 Require-Plugin 列表
  while (i.hasNext())
  {
    ctkRequirePlugin* pr = i.next();
    // 按名字 + 版本区间找候选插件
    QList<ctkPlugin*> pl = plugins->getPlugins(pr->name, pr->pluginRange);
    for (...每个候选 p2...)
    {
      if (tempResolved.contains(p2))          ok = p2;   // 正在本轮解析中(处理循环依赖)
      else if (RESOLVED_FLAGS & p2->state)    ok = p2;   // 已解析
      else if (p2->state == ctkPlugin::INSTALLED)
        // 递归解析依赖的依赖(tempResolved 集合防环)
        ...
    }
  }
}

核心差异

us:: 的模块层是退化的 ------没有版本、没有依赖解析、没有安装概念,

模块 = 已被 OS 加载的库。CTK 实现了完整 OSGi 模块层 ------

版本区间依赖、解析失败可诊断、安装状态跨进程持久(SQLite)。


二、生命周期层(Lifecycle Layer)------"状态机与钩子"

CTK:完整六态状态机(ctkPlugin.h: L87-103

复制代码
UNINSTALLED ← INSTALLED → RESOLVED → STARTING → ACTIVE
                             ↑                    │
                             └──── STOPPING ←─────┘
  • INSTALLED→RESOLVEDgetUpdatedStatectkPlugin_p.cpp: L174-198
    触发依赖解析,成功才置 RESOLVED 并广播事件------"装了"和"能用"是两个状态
  • 启动start0()ctkPlugin_p.cpp: L720-790):
cpp 复制代码
ctkPluginException* ctkPluginPrivate::start0()
{
  fwCtx->listeners.emitPluginChanged(ctkPluginEvent(STARTING, ...));

  pluginLoader.load();                                    // QPluginLoader 加载库
  pluginActivator = qobject_cast<ctkPluginActivator*>(
                        pluginLoader.instance());          // Qt 插件根对象即 Activator
  pluginActivator->start(pluginContext.data());            // 回调 start()
  state = ctkPlugin::ACTIVE;
  ...
  // 异常回滚路径(L780-789):
  // STOPPING → removePluginResources() → context invalidate → 退回 RESOLVED
}
  • 每一步状态迁移都发 ctkPluginEvent(STARTING/STARTED/STOPPING/STOPPED)
  • 支持懒激活Plugin-ActivationPolicy: eager/lazy
  • start 中途被 uninstall 有专门检测(STATECHANGE_ERROR)

Activator 接口(ctkPluginActivator.h: L71-111):

cpp 复制代码
class ctkPluginActivator {
  virtual void start(ctkPluginContext* context) = 0;
  virtual void stop(ctkPluginContext* context) = 0;
};

CppMicroServices:二态(usModule.h: L64,166

复制代码
未加载 ⇄ 已加载(IsLoaded() 一个 bool)
  • 加载即注册即启动 :静态初始化(US_INITIALIZE_MODULE)→
    ModuleRegistry::RegisterModule::Start 一气呵成,没有中间可停留的状态
  • Activator 获取用原生符号查找usModule.cpp: L133-135):
cpp 复制代码
std::string activator_func = "_us_module_activator_instance_" + d->info.name;
void* sym = ModuleUtils::GetSymbol(d->info, activator_func.c_str());  // dlsym 等价
d->moduleActivator = activatorHook();
d->moduleActivator->Load(d->moduleContext);
  • 事件只有 LOADING/LOADED/UNLOADING/UNLOADED 四种,无 RESOLVED 概念
  • 无异常回滚状态机Activator::Load 抛异常直接向上传播
    (L152-156 注释明说"语义上视为 noexcept")

核心差异

CTK 的生命周期与 .so 加载解耦 ------库可以加载着但插件停在 RESOLVED 未激活;

BlueBerry ExtensionRegistry 正是利用这一点:插件 RESOLVED 就解析它的 plugin.xml,

不必 start(懒激活的基础)。us:: 的生命周期与 .so 加载同一

简单但无法表达"就绪未启动"。


三、服务层(Service Layer)------"几乎是同一份代码"

这一层两者都是 Knopflerfish 的完整移植,结构逐字段对应:

机制 CppMicroServices CTK 差异
双表存储 services + classServicesusServiceRegistry_p.h: L63-80 同名双表(ctkServices_p.h: L64-71 仅容器(STL vs QHash)
ranking 排序 lower_bound 有序插入,Getback()
服务本体 InterfaceMap = std::map<string, void*> QObject* + QStringList 接口名 CTK 服务必须是 QObject ,用 qobject_cast 校验接口
使用记账 dependents: Map<Module*,int> dependents: QHash<QSharedPointer<ctkPlugin>,int> 同构
作用域 singleton / module / prototype 三种(SERVICE_SCOPE singleton + ctkServiceFactory us:: 多 prototype
事件分发 回调接口 + 监听器分桶(objectclass/service.id 两级 cache) Qt 信号槽ctkServiceSlotEntry:receiver+slot 名) 事件投递机制不同
LDAP 过滤 usLDAPExpr ctkLDAPExpr 同一算法
自动清理 RemoveModuleResources:卸载时注销全部服务+归还引用 removePluginResources():stop 时同样操作 同构
跟踪器 us::ServiceTracker(.tpp 模板) ctkServiceTracker 同构

核心差异只有两点

  1. CTK 服务绑定 Qt 元对象系统 :服务必须是 QObject、事件走信号槽、
    接口用 Q_DECLARE_INTERFACE + qobject_cast;us:: 零 Qt 依赖靠模板。
  2. 锁粒度:CTK 部分用 QReadWriteLock,us:: 单 Mutex。

总结:一张表看三层完成度

OSGi 层 CppMicroServices CTK
模块层 ◐ 退化:无版本/无依赖解析,模块=已加载的库 ● 完整:MANIFEST + Require-Plugin 版本区间 + 递归解析 + SQLite 持久化
生命周期层 ◐ 二态(加载/未加载),静态初始化自动走完 ● 完整六态状态机 + 事件 + 懒激活 + 异常回滚
服务层 ● 完整(Knopflerfish 移植) ● 完整(同一份 Knopflerfish 移植,Qt 化)

这解释了 MITK 的分工为什么合理:

  • Modules 层用 us:: ------算法库不需要"安装/解析/延迟启动"这些重量级语义,
    加载即用、零依赖最合适
  • Plugins 层用 CTK ------UI 插件需要版本依赖、按需激活、
    RESOLVED 阶段读 plugin.xml,非完整生命周期不可

两者服务层同源同构,所以开发者跨层切换时 API 心智模型几乎不变。


关键源码文件索引

CppMicroServices(Modules/CppMicroServices/core/ 下)

内容 文件 关键行
模块层 模块表 src/module/usModuleRegistry.cpp L39-73
模块层 manifest.json 解析 src/module/usModulePrivate.cpp L53-68
生命周期层 二态判定 include/usModule.h L64, L166(IsLoaded)
生命周期层 Start/符号查找 src/module/usModule.cpp L119-171
生命周期层 自动注册宏 include/usModuleInitialization.h L57-120
服务层 服务双表 src/service/usServiceRegistry_p.h L63-80
服务层 注册私有体/dependents src/service/usServiceRegistrationBasePrivate.h L40-115
服务层 监听器分桶 src/service/usServiceListeners_p.h L43-65

CTK(ep/src/CTK/Libs/PluginFramework/ 下)

内容 文件 关键行
模块层 MANIFEST 头常量 ctkPluginConstants.h L132-240
模块层 Require-Plugin 解析 ctkPluginFrameworkContext.cpp L269-300(checkRequirePlugin)
模块层 插件表 ctkPlugins_p.h L45-63
模块层 SQLite 存储 ctkPluginStorageSQL.cpp L38-39
生命周期层 六态状态机 ctkPlugin.h L59-103
生命周期层 RESOLVED 判定 ctkPlugin_p.cpp L174-198(getUpdatedState)
生命周期层 start0/QPluginLoader/回滚 ctkPlugin_p.cpp L720-790
生命周期层 Activator 接口 ctkPluginActivator.h L71-111
服务层 服务双表 ctkServices_p.h L40-71
服务层 信号槽事件 ctkServiceSlotEntry_p.h L45-75

两种微服务的应用实例追踪

本文是《两种微服务的三层架构实现对比》的补充:各取一个真实 MITK 例子,

端到端描述具体实现过程。

  • 例一(CppMicroServices):InteractionEventObserver 交互事件观察者
  • 例二(CTK):IDataStorageService 数据仓库服务

例一 CppMicroServices:InteractionEventObserver

us:: 微服务在 MITK 里最典型的应用:鼠标交互事件如何广播给"不认识的"观察者 (十字光标同步链路中的 DisplayActionEventBroadcast 正是靠它接入的)。

① 定义接口(Modules/Core)

Modules/Core/include/mitkInteractionEventObserver.h

cpp 复制代码
struct InteractionEventObserver {
  virtual void Notify(InteractionEvent* event, bool isHandled) = 0;
  ...
};

只有头文件,没有任何实现依赖------消费方和提供方都只 include 它。

② 提供方注册服务(构造时)

Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp: L46-48------对象构造时把自己注册进服务表:

cpp 复制代码
mitk::DisplayActionEventBroadcast::DisplayActionEventBroadcast()
{
  us::ServiceProperties props;
  props["name"] = std::string("DisplayActionEventBroadcast");
  // 以 InteractionEventObserver 接口注册自身
  m_ServiceRegistration =
      us::GetModuleContext()->RegisterService<InteractionEventObserver>(this, props);
}

内部发生的事:

  • RegisterServiceServiceRegistry::RegisterServiceusServiceRegistry.cpp: L87
  • 接口名 us_service_interface_iid<InteractionEventObserver>() 作为 key
    写入 classServices
  • 立即广播 ServiceEvent::REGISTERED
  • 析构时 m_ServiceRegistration.Unregister() 自动注销

③ 消费方用 ServiceTracker + LDAP 过滤器查找

Modules/Core/src/Interactions/mitkDispatcher.cpp: L40-49------每个渲染窗口的事件分发器构造时建跟踪器:

cpp 复制代码
// 构造 LDAP 过滤器:只要 InteractionEventObserver 这个接口的服务
std::string classInteractionEventObserver =
  "(" + us::ServiceConstants::OBJECTCLASS() + "="
      + us_service_interface_iid<InteractionEventObserver>() + ")";
// 再按属性过滤:全局的 或 专属本 renderer 的
us::LDAPFilter filter("(&(|" + specificRenderer + anyRenderer + ")"
                      + classInteractionEventObserver + ")");
m_EventObserverTracker =
    new us::ServiceTracker<InteractionEventObserver>(us::GetModuleContext(), filter);

LDAP 过滤器的实战用法:不只按接口,还按 props 里的属性筛选------同一接口的多个服务实例可以按属性精确圈定

④ 每次事件分发时动态取服务

mitkDispatcher.cpp: L198-213------每处理一个鼠标/键盘事件:

cpp 复制代码
/* Notify InteractionEventObserver */
const std::vector<us::ServiceReference<InteractionEventObserver>> listEventObserver =
    m_EventObserverTracker->GetServiceReferences();   // 拿当前时刻的全部匹配服务
for (auto it : listEventObserver) {
  InteractionEventObserver* observer = m_EventObserverTracker->GetService(it);
  if (observer && observer->IsEnabled())
    observer->Notify(event, eventIsHandled);           // 广播
}

每次都现查 ------运行中途新加载一个带观察者的模块(比如测量工具模块注册了自己的观察者),下一个鼠标事件就会自动广播给它,Dispatcher 一行代码不用改

完整链路

复制代码
DisplayActionEventBroadcast 构造
  → RegisterService<InteractionEventObserver>(this)     [提供方,Modules/Core]
      → classServices["...InteractionEventObserver"] += 本实例
                          ↓ 运行期解耦
QmitkRenderWindow 收到鼠标事件
  → Dispatcher::ProcessEvent
      → ServiceTracker(LDAP过滤).GetServiceReferences()      [消费方,互不 include 实现]
          → observer->Notify(event)
              → SetCrosshair/Scroll/...
                  → 十字光标同步链路

例二 CTK:IDataStorageService

CTK 服务在 MITK 里最核心的应用:所有 View 如何拿到全局唯一的 DataStorage

① 定义接口(带 Qt 元对象声明)

Plugins/org.mitk.core.services/src/mitkIDataStorageService.h: L29-49

cpp 复制代码
struct MITK_CORE_SERVICES_PLUGIN IDataStorageService {
  virtual IDataStorageReference::Pointer GetDataStorage() const = 0;
  virtual IDataStorageReference::Pointer GetDefaultDataStorage() const = 0;
  ...
};

// CTK 服务必须做 Qt 接口声明------这是与 us:: 的关键差异
Q_DECLARE_INTERFACE(mitk::IDataStorageService, "org.mitk.service.IDataStorageService")

② 提供方在插件 Activator::start 里注册

Plugins/org.mitk.core.services/src/internal/mitkPluginActivator.cpp: L90-91------org.mitk.core.services 插件被 CTK 启动(进入 ACTIVE 状态)时:

cpp 复制代码
void org_mitk_core_services_Activator::start(ctkPluginContext* context)
{
  dataStorageService.reset(new DataStorageService());          // 创建实现
  context->registerService<mitk::IDataStorageService>(dataStorageService.data());
}

对比例一:us:: 的注册发生在对象构造/模块加载 时;CTK 的注册发生在插件 start 时------三层对比文档里"生命周期绑定点不同"在真实代码中的体现。

CTK 内部用 qobject_cast 校验实现确实携带 Q_DECLARE_INTERFACE 声明的接口,然后写入 ctkServices 的双表。

③ 消费方用 ctkServiceTracker

Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.cpp

cpp 复制代码
// L146: 私有类成员------跟踪器
ctkServiceTracker<mitk::IDataStorageService*> m_DataStorageServiceTracker;

// L51-57: 构造时打开跟踪
QmitkAbstractViewPrivate(QmitkAbstractView* qq)
  : m_DataStorageServiceTracker(QmitkCommonActivator::GetContext())  // 插件上下文
{
  m_DataStorageServiceTracker.open();    // 开始跟踪(服务出现/消失自动感知)
}

// L390-400: 任何 View 调 GetDataStorage() 时
mitk::DataStorage::Pointer QmitkAbstractView::GetDataStorage() const
{
  mitk::IDataStorageService* dsService = d->m_DataStorageServiceTracker.getService();
  if (dsService != nullptr)
    return dsService->GetDataStorage()->GetDataStorage();
  return nullptr;
}

④ 自动清理

  • View 析构 → m_DataStorageServiceTracker.close() → 停止跟踪
  • 插件 stop → removePluginResources() → 注销服务 + 清理依赖计数
  • 服务注销时,所有持有引用的 View 自动收到 serviceRemoved 信号,getService() 返回 nullptr

完整链路

复制代码
org.mitk.core.services 插件启动(ACTIVE)
  → Activator::start()
      → registerService<IDataStorageService>(dataStorageService)     [提供方]
          → ctkServices["org.mitk.service.IDataStorageService"] += 本实例
                          ↓ 运行期解耦
QmitkAbstractView 构造
  → ctkServiceTracker.open()
      → 监听服务注册/注销信号
          → 任何 View 调 GetDataStorage()
              → tracker.getService()
                  → DataStorageService::GetDataStorage()
                      → 全局唯一 DataStorage 实例

对比小结

维度 CppMicroServices(InteractionEventObserver) CTK(IDataStorageService)
注册时机 对象构造时(模块加载即注册) 插件 start 时(ACTIVE 状态才注册)
接口声明 纯 C++ 结构体,模板元编程 Q_DECLARE_INTERFACE + Qt 元对象
服务发现 ServiceTracker + LDAPFilter(属性过滤) ctkServiceTracker(Qt 信号槽监听)
生命周期绑定 与模块加载/卸载同步 与插件状态机(INSTALLED→RESOLVED→ACTIVE)同步
典型场景 跨模块事件广播(鼠标交互) 插件间共享单例(全局 DataStorage)

这两个实例完整展示了三层架构对比在 MITK 中的实际应用:us:: 用于模块层 的轻量事件广播,CTK 用于插件层的完整生命周期管理 + 服务共享。

相关推荐
在水一缸2 小时前
深入浅出:Node.js 下一代 ORM 架构设计与实战解析
数据库·微服务·云原生·node.js·orm·架构设计
Wang's Blog2 小时前
Go-Zero项目开发30: 微服务配置中心的设计与实现
开发语言·微服务·golang·go-zero
Wang's Blog5 小时前
Go-Zero项目开发33: IM实时通信前后端对接与微服务基础设施实践
开发语言·微服务·golang·go-zero
beibeix20155 小时前
MITK中org.blueberry.ui.qt 插件功能分析
mitk
还有你Y21 小时前
读懂微服务
微服务·云原生·架构
fanly112 天前
Surging AI Agent:基于.NET 生态一站式本地大模型 + 向量检索微服务解决方案
微服务·.net core·microservice
道影子2 天前
阴阳引力多维基元:超越二进制的计算革命
人工智能·深度学习·微服务·云原生·架构
Ulyanov3 天前
Python雷达电子对抗仿真引擎(一):打破单体瓶颈,构建微服务与ECS架构的顶层设计
开发语言·python·微服务·云原生·架构·雷达电子对抗
REST_30274 天前
告别Excel孤岛:一款任务链路可视化工具带来的真实改变
大数据·微服务·云计算·编辑器·excel·动态规划