基于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

}

 

}

 

 

 

posted @ 2012-10-12 21:06  阿牛  阅读(741)  评论(1编辑  收藏  举报