我設想的可擴展結構(插件) (ㄧ)
谈到设计一个可方便扩充功能的应用系统,一般都会想到使用插件结构。插件结构为众人所熟悉,但如果说具体做起来,恐怕每个应用都会有自己不同的实现方案。但我想大致能分为两类:UI插件,后台运行的无任何UI的插件。前者通常需要了解其运行环境(Context),比如文本编辑器的插件需要知道当前编辑的文本;而后者则不一定需要,比如只是在后台开启Thread,长期执行某任务的插件。对于这两类插件,设计上会有比较大的差异。但通常都需要考虑下面的问题:
1.如何得到插件──即如何将插件部署到系统中。
2.主程序如何找到插件的DLL文件。
3.主程序如何在DLL文件中查找插件类。
4.插件的安全性问题。
5.主程序如何和插件进行通信。
6.插件之间是否需要进行通信。如果需要的话,如何进行通信?
7.插件的更新升级问题。
注:主程序和插件都是.net写的程序。
下面就我现在所处的系统环境来描述上述问题。
系统环境如下图:
说明:客户端数量众多,所以如果要手工部署将是巨大的工作量。
1. 如何得到插件。第一种:手工安装插件到客户端。第二种:通过服务器派送到客户端,通过某通信协议将插件文件传输到客户端,客户端接收文件并且存放到某目录下。由于客户端数量非常多,使用手工安装的方式不可行,所以我采用后者。
2. 主程序如何找到插件的DLL文件。第一种:使用应用程序配置文件,将关于插件的配置放到App.config(Web.config)文件中,并且写一个SectionHanlder这样的类来读取配置。第二种:将关于插件的配置存放到单独的XML文件中,这样可以自己写一个类来解析配置文件,也可以使用微软的Enterprise Library来读取配置。第三种:固定将所有的插件存放到某个目录下,使用此方式可以避免在更新了插件后还要更新配置文件,从而避免了很多麻烦的问题,但是代码也复杂一些。考虑到.net强大的反射功能,我使用了第三种。
3. 主程序如何在DLL文件中查找插件类。主程序如何知道哪些类是插件类?第一种:利用Attribute,给插件类打上标记。第二种:使用抽象类,所有的插件类都继承此类。第三种:使用接口,所有的类插件都实现此接口。而我使用了接口的方式。由于反射要求先加载了Assembly后,才能进行查找,所以要考虑到能加载也能卸载Assembly,而不影响主程序。解决的办法就是在一个单独的应用程序域(AppDomain)中加载Assembly,分析完毕后再卸载此应用程序域。
4. 插件的安全性问题。如果插件都是自己的团队写的,这不是一个特别重要的问题;但如果插件会来自第三方,那这些插件可能有些不安全的代码,或者含有一个错误(Bug)。如果想保护系统资源,以及主程序的状态,则有必要考虑安全问题。使用配置工具可以进行方便的设置,如下图:
但是通常不希望自己跑到客户端那里进行手动的设置,所以使用代码来设置才是比较实际的方案。.Net Framework中的SecurityManager是一个为主要存取点给与安全性系统互动的类,使用这个类我们可以将插件的代码安全进行设置。我们可以在All_Code根节点下建立自己的代码组(CodeGroup),条件类型为URL,并且将URL设置为存放插件的目录(当然这个URL需要符合一定的格式),最后你可以根据需要设置此代码组的使用权限集合。
5. 主程序如何和插件进行通信。主程序可以通过插件实现的接口(当然还有别的方式)来和插件进行通信。如果插件需要了解主程序的运行环境,则还需要增加接口(抽象类)。比如对于文本编辑器,主程序可以传给插件当前正在编辑的文本。
6. 插件之间是否需要进行通信。如果需要的话,如何进行通信?插件之间如果要进行通信将会使问题变得复杂很多。因为这样一来,插件之间势必存在依赖,这样在加载插件的时候,就需要考虑加载顺序问题;而且插件还得知道通过什么和其它的插件通信(当然我们不希望插件之间直接进行通信)。第一种:也是一个最简单的办法,就是将插件都设计成Singleton class,这样通过其静态方法(或属性)就可以获得其实例,但是这样的方法总让人觉得有点不舒服;第二种:增加包装类来描述该插件和其它插件的关系,通过该类来获得其它插件的实例,然后通过公共接口进行操作。
7. 插件的更新升级问题。如果需要停止应用才能进行插件的升级,将会给用户带来不佳的用户体验。所以希望能在运行时就进行插件的升级。通常作法是将插件文件拷贝到另外一个目录,然后从此目录加载插件。甚至还需要加个备份目录,以便在加载升级后的插件失败后取得最后一个可用的老版本插件。
以上已经把关键的思路描述出来了,剩下的就是具体的设计和实现了。在下篇随笔中,将给出我的设计,以及关键的代码实现,供大家参考。
希望大家多多提出宝贵意见!
希望大家多多提出宝贵意见!