业务开发中常用到插件架构实现,特别是外挂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接口