企业级服务器设计与实现经验之插件系统基础篇
最初之所以要采用插件的形式进行开发,主要是为了解决功能服务的“热插拔”问题,在决定采用“框架+插件”的方式进行设计后,我们就更进一步,打算将一个个可以分割开来的拥有完整功能的组件都做成插件的形式,并且使同类型的插件的接口兼容,这样在以后需要改变时就可以灵活的进行替换。比如,将通信部分做成通信插件、日志记录部分做成日志插件等等。
首先,我们要弄清楚,什么是插件?我给出了一个定义,可能有失偏颇。
插件又称为扩展,是一种特殊的组件,用于增强和扩展基本框架的行为能力。插件和框架的通信协议是一组接口,插件的各种特性都可以通过该接口进行访问。插件主要有如下特点:
(1)一个插件是一个独立的物理单元。它可以独立的提供一项完整的服务(功能),而不需要依赖于其它插件。
(2)插件能自我描述 ―― 插件的所有对外的发布信息都由插件自己内部提供,而不依赖于外部文件或注册表。
(3)插件能自我管理 ―― 插件如果需要配置信息,则插件自己能读取和修改配置信息,而不是框架来完成这些事情。
(4)插件自我独立 ―― 一个插件不得引用其它的插件。如果一个插件与另一个插件关系紧密,那么应该将这两个插件合成一个插件,或者重新分解为两个独立的插件.
在DataServer系统中,所有的插件从IAddin继承,IAddin接口定义如下:
public interface IAddin { void Start() ; //不同类型的插件对开始和停止的解释不一样,如果一个插件没有此意义,可实现为空 void Stop() ; int ServiceKey {get ;} string ServiceName {get ;} string Description {get ;} //插件说明信息 AddinAppendixInfo AddinAppendixInfo {get ;} string AddinType {get ;} }
接口中的所有方法属性的意义一目了然,其中,ServiceKey是插件关键字,用于区分不同的插件。我们可以把同一类型的插件的关键字限定在某个范围之内,比如,我将通信插件的关键字范围限定在2000-2999之间。AddinAppendixInfo用于描述插件的附加信息,如作者、创建日期、版本号等。
public class AddinAppendixInfo { public string Author ; public string TimeCreated ; public string TimeLastRevised ; public string AddinVersion ; }
AddinType是插件类型,在DataServer中,有如下几种插件类型:
public class AddinTypes { public const string Net = "通信插件" ; public const string Function = "功能插件" ; public const string Reporter = "报告者插件" ; public const string BasicReqDealer = "基本请求处理者插件" ; public const string RespondInterceptor = "回复截获者插件" ; }
每种类型的插件的功用将在后面说明。
大部分插件都需要进行配置,我将配置部分抽象为一个公共的接口,IAddinConfig定义如下:
public interface IAddinConfig { //以下方法返回的控件或窗体必须实现EnterpriseServerBase.Configure.IConfigControl接口 IConfigControl GetAddinConfigForm() ; IConfigControl GetAddinConfigControl() ; }
这样,一个插件如果需要配置信息,则实现该接口即可。前面已经提到“插件如果需要配置信息,则插件自己能读取和修改配置信息,而不是框架来完成这些事情”,插件就是通过IAddinConfig接口来操作配置信息的。GetAddinConfigForm用于获取一个独立的配置窗体,通过此窗体可以完成对插件配置,而GetAddinConfigControl获取的是一个配置控件,一般其结构与功能和配置窗体一样,只不过它可以嵌入到其它任意的窗体中,比如到时,我们可以在主框架中设置一个专门用于配置的TabControl,而其中的每一页就用于加载一个GetAddinConfigControl返回的配置控件,从而实现对配置的统一管理。
IConfigControl究竟是如何操作配置信息的?其定义如下:
public interface IConfigControl { bool Initialize(string configPath ,string objName) ; //objName 是配置文件中的对象名 bool SaveConfig() ; bool ReDisplayConfigInfo() ; //显示更新后的配置信息 event EventHandler ConfigChanged ; }
一般,配置信息保存于配置文件中,所以初始化时要给出配置路径,而objName参数的作用,可以参见前面的《如何使用XCodeFactory自动生成XML配置文件和对应的解析类?》这篇文章,我的所有于配置文件相关的解析类和配置窗体都是通过XCodeFactory自动生成的。XCodeFactory接口的其它几个方法的意思很明显,就不用解释了。
接下来,我们看看如何将插件加载到系统中。
我们都知道,一个插件是一个独立的物理单元,我们如何获得该插件的类型了?有很多办法可以解决,比如,我们访问插件目录下的每个插件,通过Assembly.LoadFrom加载一个插件,然后Type. IsSubclassOf查看其是否实现了某种插件类型的接口,从而判断其类型。我采用了更直观的方式,用插件的名字来标志其类型,比如所有的通信插件都以NetAddin.dll结尾。
可以用一个特定的模块来加载插件,我称之为“插件加载器”,插件加载器用于加载某目录下的所有特定类型的插件,"特定"表现在两个方面:(1)插件的名字以[addinSign].dll结尾;(2)插件中的主类是从[baseAddinType]继承。插件加载器的接口定义如下:
public interface IAddinLoader { ArrayList LoadAddins(string addinFolderPath ,string addinSign ,Type baseAddinType) ; }
很简单,只有一个方法,该方法加载某目录下的所有某特定类型的插件,而返回的列表中的每个元素就引用着一个插件。
插件加载器仅仅负责将某种类型的插件加载进来,但是如何实现将所有的插件加载进来、并动态的加载/移除插件了?我们需要一个管理器组件来完成这件事情,它就是IAddinManagement,其接口定义如下:
public interface IAddinManagement { void LoadAllAddins(string addin_FolderPath ) ; bool ReLoadAddins(out int increment) ; void UnloadAllAddins() ; bool DynRemoveAddin(int serviceKey) ; bool DynRemoveAddin(string serviceName) ; void StartAllAddin() ; void StopAllAddin() ; ArrayList AddinBoxList {get ;} Hashtable GetAllServiceList() ; //key - name event CallBackSimple AddinsChanged ; }
可以看到,IAddinManagement实现了对所有插件的全部管理,其还发布了一个事件AddinsChanged,就是当插件列表发生变化的时候用于通知外部。
关于插件的基础部分,就先介绍这么多,在后面的章节中,我们将深入到各种类型的插件的内部。
原文链接: