业务开发中常用到插件架构实现,特别是外挂DLL形式的插件。我们封装了插件管理的通用代码。

源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Model/IPlugin.cs

快速入门

星尘代理StarAgent就支持加载插件来实现扩展功能。以下是星尘代理加载并初始化插件的代码。

// 插件管理器
var pm = _PluginManager = new PluginManager
{
    Identity = "StarAgent",
    Provider = this,

    Log = XTrace.Log,
};
_container.AddSingleton(pm);

// 启动插件
WriteLog("启动插件[{0}]", pm.Identity);
pm.Load();
pm.Init();
foreach (var item in pm.Plugins)
{
    if (item is IAgentPlugin plugin)
    {
        try
        {
            plugin.Start();
        }
        catch (Exception ex)
        {
            XTrace.WriteException(ex);
        }
    }
}

管理类PluginManager负责Load加载插件,支持扫描外部DLL。紧接着Init通知插件执行初始化工作。最后就可以执行自己的业务操作了,星尘代理这里是Start。

所谓插件,其实就是实现了IPlugin接口,或者添加了PluginAttribute特性,如下:

/// <summary>星尘代理插件</summary>
public interface IAgentPlugin : IPlugin
{
    /// <summary>开始工作</summary>
    public void Start();

    /// <summary>停止工作</summary>
    /// <param name="reason"></param>
    public void Stop(String reason);
}

/// <summary>星尘代理插件基类</summary>
[Plugin("StarAgent")]
public abstract class AgentPlugin : DisposeBase, IAgentPlugin
{
    /// <summary>服务提供者</summary>
    public IServiceProvider? Provider { get; set; }

    /// <summary>初始化插件</summary>
    /// <param name="identity"></param>
    /// <param name="provider"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public virtual Boolean Init(String? identity, IServiceProvider provider)
    {
        if (identity != "StarAgent") return false;

        Provider = provider;

        return true;
    }

    /// <summary>开始工作</summary>
    public virtual void Start() { }

    /// <summary>停止工作</summary>
    /// <param name="reason"></param>
    public virtual void Stop(String reason) { }
}


核心原理

通用插件实现,一般是接口或者特性。

/// <summary>通用插件接口</summary>
/// <remarks>
/// 为了方便构建一个简单通用的插件系统,先规定如下:
/// 1,负责加载插件的宿主,在加载插件后会进行插件实例化,此时可在插件构造函数中做一些事情,但不应该开始业务处理,因为宿主的准备工作可能尚未完成
/// 2,宿主一切准备就绪后,会顺序调用插件的Init方法,并将宿主标识传入,插件通过标识区分是否自己的目标宿主。插件的Init应尽快完成。
/// 3,如果插件实现了<see cref="IDisposable"/>接口,宿主最后会清理资源。
/// </remarks>
public interface IPlugin
{
    /// <summary>初始化</summary>
    /// <param name="identity">插件宿主标识</param>
    /// <param name="provider">服务提供者</param>
    /// <returns>返回初始化是否成功。如果当前宿主不是所期待的宿主,这里返回false</returns>
    Boolean Init(String? identity, IServiceProvider provider);
}

/// <summary>插件特性。用于判断某个插件实现类是否支持某个宿主</summary>
/// <remarks>实例化</remarks>
/// <param name="identity"></param>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class PluginAttribute(String identity) : Attribute
{
    /// <summary>插件宿主标识</summary>
    public String Identity { get; set; } = identity;
}

插件接口很简单,只有一个Init。因为可能有多套插件系统,为了区分开来不同类型的插件,必须指定Identity。

服务提供者IServiceProvider也很重要,方便插件内部获取各种服务实现。

可以看到,我们IPlugin接口只有Init初始化,并没有销毁的接口。一般来说,我们使用IDispose来表示插件销毁,因此IPlugin无需再次定义类似方法。

为了管理插件,引入管理类PluginManager。

  • Identity。指定当前插件标识,只加载该标识的插件。例如前面入门代码的StarAgent
  • Provider。宿主的服务提供者,插件一般需要跟其它对象交互,这里就是纽带
  • Plugins。插件集合,保管着已加载插件
  • LoadPlugins。扫描插件,可以快速得到插件集合
  • Load。加载插件,扫描后并修改Plugins集合
  • Init。初始化插件,依次调用插件的Init接口