CTK 插件之间的依赖

前端之家收集整理的这篇文章主要介绍了CTK 插件之间的依赖前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

简述

插件依赖:是指一个插件在解析(或使用)时,不能脱离其他插件

插件是 CTK 中的基础元件,不同的插件之间可以相互依赖、引用,这样许多插件才会共同协作,实现一些比较复杂的功能

但是,在处理插件依赖的同时,也面临着一个极具挑战性的问题 - 如何保证每个插件都能加载成功?

版权所有:一去丶二三里,转载请注明出处:http://blog.csdn.net/liang19890820

CTK 中的插件依赖

CTK 中的 MANIFEST.MF 文件 中,我们介绍过 CTK 本身提供了一些清单头。其中 Require-Plugin 用于标识插件所依赖的其他插件,头指令 resolution 用于标识 Require-Plugin 头中的解析类型,指令值包括

  • optional(弱依赖):表示所需的插件是可选的,并且即使所需的插件没有被解析,该插件也可以被解析。
  • mandatory(强依赖):表示在解析插件时,所需的插件也必须被解析。如果所需的插件不能被解析,则模块解析失败。

假设,存在 A、B 两个插件,B 依赖 A(即:B 离不开 A)。那么,在插件加载过程中,可能会存在以下情况:

  • 弱依赖(optional

    • A、B 均存在,加载顺序:A -> B
    • A、B 均存在,加载顺序:B -> A
    • A 不存在,加载 B
  • 强依赖(mandatory

    • A、B 均存在,加载顺序:A -> B
    • A、B 均存在,加载顺序:B -> A
    • A 不存在,加载 B

依赖验证

为了测试上述情景,写一个简单的管理类来加载插件

// 指定加载顺序
bool Manager::loadAllPlugins(const QString &path)
{
    QString strDll = path + "/plugin_a.dll";
    bool isALoaded = loadPlugin(strDll);

    strDll = path + "/plugin_b.dll";
    bool isBLoaded = loadPlugin(strDll);

    return (isALoaded && isBLoaded);
}

// 加载插件
bool Manager::loadPlugin(const QString &path)
{
    ctkPluginContext* context = framework->getPluginContext();
    try {
        QUrl location = QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath());
        QSharedPointer<ctkPlugin> plugin = context->installPlugin(location);
        plugin->start(ctkPlugin::START_TRANSIENT);
    } catch(const ctkPluginException &e) {
        qDebug() << QString("Failed to install plugin") << e.what();
        return false;
    }

    return true;
}

在测试时,注意调换插件 A、B 的加载顺序。除此之外,还需要修改插件 B 中的 MANIFEST.MF 文件(弱依赖用 optional,强依赖用 mandatory):

Require-Plugin: plugin.a; resolution:="mandatory"

经过反复测试,暂时可以得出以下结论:

  • 使用 optional:无论被依赖的插件(A)是否存在,也无论加载顺序如何,主插件(B)都可以加载成功。
  • 使用 mandatory:被依赖的插件(A)必须存在,且必须在主插件(B)加载之前被加载(顺序:A -> B),这样才能确保主插件被加载成功。

看似有这么一点意思:弱依赖,有没有你都无所谓;强依赖,必须有你,而且你还得比别人提前。

异常的引发

在使用 mandatory 时你会发现,倘若被依赖的插件(A)未加载或者不存在,则加载主插件(B)时会抛出以下异常:

ctkPluginException: Failed to resolve required plugin: plugin.a

这时因为在调用 ctkPlugin::start() 时,会执行依赖插件的检测:

void ctkPluginFrameworkContext::checkRequirePlugin(ctkPluginPrivate *plugin)
{
// ...
      if (!ok && pr->resolution == ctkPluginConstants::RESOLUTION_MANDATORY)
      {
        tempResolved.clear();
        if (debug.resolve)
        {
          qDebug() << "checkRequirePlugin: Failed to satisfy:" << pr->name;
        }
        throw ctkPluginException(QString("Failed to resolve required plugin: %1").arg(pr->name));
      }
// ...
}

当所依赖的插件没有被解析时,就会抛出异常。

那么,倘若插件之间存在强依赖关系,又不想以硬编码的形式指定加载顺序怎么办?

解决依赖关系

看来,现在的所有问题都集中在一点上:如何保证插件的加载顺序?

若要自己实现一个加载顺序,就必须知道插件所依赖的其他插件。而这些信息可以由 MANIFEST.MF 文件获得,但是要读取这些信息又必须加载插件,这似乎陷入了一个死循环:

要定义加载顺序 -> 就要获取头信息 -> 就要读取 MANIFEST.MF 文件 -> 就要加载插件(存在依赖,又无法正常加载)

通过 CTK 文档,可以发现 MANIFEST.MF 文件的信息其实在安装插件后就可以获取到,也就是 ctkPluginContext::installPlugin() 之后。而异常是由 ctkPlugin::start()引发的,那么何不将这两部分分开,先安装所有插件,再执行启动所有插件,这样就不会存在问题了。

// 加载所有插件
bool Manager::loadAllPlugins(const QString &path)
{   
    // 安装所有插件
    QDirIterator itPlugin(path,QStringList() << "*.para",QDir::Files);
    while (itPlugin.hasNext()) {
        QString pluginPath = itPlugin.next();
        if (!installPlugin(pluginPath))
            return false;
    }

    // 自己实现一个加载规则(按顺序加载:A -> B -> C)
    ctkPluginContext* context = framework->getPluginContext();
    QList<QSharedPointer<ctkPlugin> > plugins = context->getPlugins();
    foreach (QSharedPointer<ctkPlugin> plugin,plugins) {
        QHash<QString,QString> headers = plugin->getHeaders();
        // 获取 Require-Plugin 的信息
        // 可以递归获取各个插件所依赖的插件,形成一条加载链
    }

    return true;
}

看似没太大问题,但再深入思考一下,存在以下几个问题:

  • 解析头信息很容易出错
  • 逻辑不太好写
  • 若存在循环依赖(A -> B -> C -> A),这时应该先加载哪个插件

好不容易发现一条新路,又被堵死了。。。通过循环依赖,可以看出指定插件加载顺序貌似就是一个错误

最终,通过源码可以发现,在执行 CtkPlugin::start() 时,倘若所依赖的插件已经执行 ctkPluginContext::installPlugin() 了,那么也可以成功!所以,修改一下代码

// 加载所有插件
bool Manager::loadAllPlugins(const QString &path)
{
    // 安装所有插件
    QDirIterator itPlugin(path,QStringList() << "*.dll",QDir::Files);
    while (itPlugin.hasNext()) {
        QString pluginPath = itPlugin.next();
        if (!installPlugin(pluginPath))
            return false;
    }

    // 启动所有插件
    ctkPluginContext* context = framework->getPluginContext();
    QList<QSharedPointer<ctkPlugin> > plugins = context->getPlugins();
    foreach (QSharedPointer<ctkPlugin> plugin,plugins) {
        if (!startPlugin(plugin))
            return false;
    }

    return true;
}

// 安装插件
bool Manager::installPlugin(const QString &path)
{
    ctkPluginContext* context = framework->getPluginContext();
    try {
        QUrl location = QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath());
        QSharedPointer<ctkPlugin> plugin = context->installPlugin(location);
    } catch(const ctkPluginException &e) {
        qDebug() << QString("Failed to install plugin") << e.what();
        return false;
    }

    return true;
}

// 启动插件
bool Manager::startPlugin(QSharedPointer<ctkPlugin> plugin)
{
    try {
        plugin->start(ctkPlugin::START_TRANSIENT);
    } catch(const ctkPluginException &e) {
        qDebug() << QString("Failed to start plugin") << e.what();
        return false;
    }

    return true;
}

这时,即使存在强依赖也不出现任何问题,这是因为所有的插件已经被优先安装了(包括它所依赖的插件)。

猜你在找的设计模式相关文章