[fix] 加载插件时,需要搜索当前目录。close https://github.com/NewLifeX/NewLife.XCode/issues/62
智能大石头 authored at 2025-08-28 14:02:43
5.33 KiB
X
using System.Reflection;
using NewLife.Log;
using NewLife.Reflection;

namespace NewLife.Model;

/// <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;
}

/// <summary>插件管理器</summary>
public class PluginManager : DisposeBase, IServiceProvider
{
    #region 属性
    /// <summary>宿主标识,用于供插件区分不同宿主</summary>
    public String? Identity { get; set; }

    /// <summary>宿主服务提供者</summary>
    public IServiceProvider? Provider { get; set; }

    /// <summary>插件集合</summary>
    public IPlugin[]? Plugins { get; set; }

    /// <summary>日志提供者</summary>
    public ILog Log { get; set; } = XTrace.Log;
    #endregion

    #region 构造
    /// <summary>实例化一个插件管理器</summary>
    public PluginManager() { }

    /// <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>
    /// <param name="disposing">从Dispose调用(释放所有资源)还是析构函数调用(释放非托管资源)。
    /// 因为该方法只会被调用一次,所以该参数的意义不太大。</param>
    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);

        if (disposing)
        {
            // 倒序销毁
            var ps = Plugins;
            if (ps != null)
            {
                for (var i = ps.Length - 1; i >= 0; i--)
                {
                    ps[i].TryDispose();
                }
                Plugins = null;
            }
        }
    }
    #endregion

    #region 方法
    /// <summary>加载插件。仅保留属于当前宿主且实例化成功的插件,支持从容器服务提供者中实例化</summary>
    public void Load()
    {
        var list = new List<IPlugin>();
        // 此时是加载所有插件,无法识别哪些是需要的
        foreach (var type in LoadPlugins())
        {
            if (type == null) continue;

            try
            {
                // 插件类注册到容器中,方便后续获取
                var container = Provider?.GetService<IObjectContainer>();
                container?.TryAddTransient(type, type);

                var obj = Provider?.GetService(type) ?? Provider?.CreateInstance(type) ?? type.CreateInstance();
                if (obj is IPlugin plugin) list.Add(plugin);
            }
            catch (Exception ex)
            {
                Log?.Debug(String.Empty, ex);
            }
        }
        Plugins = list.ToArray();
    }

    /// <summary>加载插件。仅保留属于当前宿主的插件</summary>
    /// <returns></returns>
    public IEnumerable<Type> LoadPlugins()
    {
        // 此时是加载所有插件,无法识别哪些是需要的
        foreach (var type in AssemblyX.FindAllPlugins(typeof(IPlugin), true))
        {
            if (type == null) continue;

            // 如果有插件特性,并且所有特性都不支持当前宿主,则跳过
            var atts = type.GetCustomAttributes<PluginAttribute>(true);
            if (atts != null && atts.Any(a => a.Identity != Identity)) continue;

            yield return type;
        }
    }

    /// <summary>开始初始化。仅保留初始化成功的插件</summary>
    public void Init()
    {
        var ps = Plugins;
        if (ps == null || ps.Length <= 0) return;

        var list = new List<IPlugin>();
        foreach (var item in ps)
        {
            try
            {
                if (item.Init(Identity, this)) list.Add(item);
            }
            catch (Exception ex)
            {
                Log?.Debug(String.Empty, ex);
            }
        }

        Plugins = list.ToArray();
    }
    #endregion

    #region IServiceProvider 成员
    Object? IServiceProvider.GetService(Type serviceType)
    {
        if (serviceType == typeof(PluginManager)) return this;

        return Provider?.GetService(serviceType);
    }
    #endregion
}