zoukankan      html  css  js  c++  java
  • 基于AppDomain的插件开发自动加载(三)

    前面已经得到了热插拔的插件原型,这次讨论如果插件是服务提供者怎么办?

    我能想到的,

    1. 需要在起动时加载所有插件
    2. 然后在插件变动时,及时卸载旧的插件,加载新的插件。
    3. 如果有新插件放在目录中,需要马上加载新的插件。
    4. 如果插件被删除,我们要把对应的服务也移除。

    最终使用时,如下:

    private void FormMain_Load(object sender, EventArgs e)

    {

    var inst = PluginManager.Instance;

    inst.PluginChanged += OnPluginChanged;

    }

    void OnPluginChanged(object sender, PluginManagerEventArgs e)

    {

    if (e.ChangeType == PluginChangeType.Created)

    {

             // 这里初始化插件,提供服务

    e.PluginInstance.Run(DateTime.Now.ToString());

    }

    }

     

    一、 监视目录,第一想到的便是 FileSystemWatcher ,我们就用它来实现监视一个目录。 如果有经验的,会知道这个类的Changed事件,在文件变化时,因为文件属性多次变化,也会激发多次。我这里的解决方案是:

    把收到的变动放置在容器中,任你变化,再你不再变化时,我统一处理一次。其中,重命名,理解为删除原来的,增加新文件。

    /// <summary>

    ///监视插件目录

    ///输出插件DLL的变更,修改,删除,增加

    /// </summary>

    public class PluginManager

    {

    #region实现

    #region字段

    private static PluginManager _instance = null;

    private FileSystemWatcher pluginWatcher;

    private Timer timerProcess = null;

    private ConcurrentDictionary<string, FileSystemEventArgs> changedPlugins = new ConcurrentDictionary<string, FileSystemEventArgs>();

    private ConcurrentDictionary<string, PluginCallerProxy> plugins = new ConcurrentDictionary<string, PluginCallerProxy>();

    #endregion

    static PluginManager()

    {

    if (_instance == null)

    {

    lock (typeof(PluginManager))

    {

    if (_instance == null)

    _instance = new PluginManager();

    }

    }

    }

    private PluginManager()

    {

    string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins");

    //监控

    this.pluginWatcher = new FileSystemWatcher(path, "*.dll");

    this.pluginWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;

    this.pluginWatcher.Changed += OnPluginChanged;

    this.pluginWatcher.Created += OnPluginChanged;

    this.pluginWatcher.Deleted += OnPluginChanged;

    this.pluginWatcher.Renamed += OnPluginRenamed;

    pluginWatcher.EnableRaisingEvents = true;

    timerProcess = new Timer(new TimerCallback(e => this.ProcessChangedPlugin()));

    //加载所有

    Directory.GetFiles(path, "*.dll").ToList().ForEach(file =>

    {

    FileInfo fi = new FileInfo(file);

    this.changedPlugins[fi.Name] = new FileSystemEventArgs(WatcherChangeTypes.Created, fi.DirectoryName, fi.Name);

    });

    this.timerProcess.Change(10, -1);

    }

    void OnPluginRenamed(object sender, RenamedEventArgs e)

    {

    //重命名,理解为去掉原来的,增加新命名的

    FileInfo old = new FileInfo(e.OldFullPath);

    this.changedPlugins[old.Name] = new FileSystemEventArgs(WatcherChangeTypes.Deleted, old.DirectoryName, old.Name);

    FileInfo n = new FileInfo(e.FullPath);

    this.changedPlugins[n.Name] = new FileSystemEventArgs(WatcherChangeTypes.Created, n.DirectoryName, n.Name);

    //1秒后再处理

    this.timerProcess.Change(1000, -1);

    }

    void OnPluginChanged(object sender, FileSystemEventArgs e)

    {

    Debug.Print(e.Name + e.ChangeType);

    //记录变更

    this.changedPlugins[e.Name] = e;

    //1秒后再处理

    this.timerProcess.Change(1000, -1);

    }

    protected void ProcessChangedPlugin()

    {

    }

    #endregion

    }

    二、 插件最终要提供给使用者 IPlugin 供用户调用,但是如果把 PluginLoader.RemotePlugin 直接提供给用户,则如果插件升级替换旧有逻辑时,其引用不易马上更新,造成仍用调用原来指向卸载的AppDomain的引用,造成调用失败。 所以我们这里需要引入 PluginCallerProxy ,里面保存真实跨域引用,我们插件更新时,直接更新这里的引用就一改全改了。

    internal class PluginCallerProxy : IPlugin

    {

    private IPlugin _plugin;

    private PluginLoader _pluginLoader;

    private System.Threading.ReaderWriterLockSlim locker = new ReaderWriterLockSlim();

    internal PluginLoader PluginLoader

    {

    get

    {

    return _pluginLoader;

    }

    set

    {

    _pluginLoader = value;

    this.Plugin = _pluginLoader == null ? null : _pluginLoader.RemotePlugin;

    }

    }

    internal IPlugin Plugin

    {

    get

    {

    locker.EnterReadLock();

    try

    {

    if (_plugin == null)

    {

    throw new PluginException("插件已经卸载");

    }

    return _plugin;

    }

    finally

    {

    locker.ExitReadLock();

    }

    }

    set

    {

    locker.EnterWriteLock();

    try

    {

    _plugin = value;

    }

    finally

    {

    locker.ExitWriteLock();

    }

    }

    }

    public PluginCallerProxy(PluginLoader loader)

    {

    this.PluginLoader = loader;

    this.Plugin = loader.RemotePlugin;

    }

    public Guid PluginId

    {

    get { return Plugin.PluginId; }

    }

    public string Run(string args)

    {

    return Plugin.Run(args);

    }

    public string Run(string args, Action action)

    {

    return Plugin.Run(args, action);

    }

    public string Run(string args, Func<string> func)

    {

    return Plugin.Run(args, func);

    }

    }

    public class PluginManager

    {

    protected void ProcessChangedPlugin()

    {

    #region处理插件变化

    foreach (var kv in this.changedPlugins)

    {

    FileSystemEventArgs e;

    if (changedPlugins.TryRemove(kv.Key, out e))

    {

    Debug.Print(e.Name + "=>" + e.ChangeType);

    switch (e.ChangeType)

    {

    case WatcherChangeTypes.Created:

    {

    //加载

    var loader = new PluginLoader(e.Name);

    var proxy = new PluginCallerProxy(loader);

    plugins.TryAdd(e.Name, proxy);

    OnPluginChange(new PluginManagerEventArgs(e.Name, PluginChangeType.Created, proxy));

    }

    break;

    case WatcherChangeTypes.Deleted:

    {

    PluginCallerProxy proxy;

    if (plugins.TryRemove(e.Name, out proxy))

    {

    OnPluginChange(new PluginManagerEventArgs(e.Name, PluginChangeType.Deleted, proxy));

    var loader = proxy.PluginLoader;

    proxy.PluginLoader = null;

    loader.Unload();

    }

    }

    break;

    case WatcherChangeTypes.Changed:

    {

    PluginCallerProxy proxy;

    if (plugins.TryGetValue(e.Name, out proxy))

    {

    OnPluginChange(new PluginManagerEventArgs(e.Name, PluginChangeType.Deleted, proxy));

    var loader = proxy.PluginLoader;

    loader.Unload();

    loader = new PluginLoader(e.Name);

    proxy.PluginLoader = loader;

    OnPluginChange(new PluginManagerEventArgs(e.Name, PluginChangeType.Created, proxy));

    }

    }

    break;

    }

    }

    }

    #endregion

    }

    }

  • 相关阅读:
    条件注释判断浏览器版本<!--[if lt IE 9]>
    动态加载js、css 代码
    C趣味题目
    shell脚本
    Logiscope学习网址
    将double型小数点后面多余的零去掉
    qt和makefile学习网址
    微软推出的工作流引擎
    java例子
    js 定义hash类
  • 原文地址:https://www.cnblogs.com/evlon/p/plugin_autoload.html
Copyright © 2011-2022 走看看