注意:文本为外部引用 1 引言 在信息系统开发,用户业务功能变化预先不可知,故要提高系统后期的业务扩展。一般情况下用户需求发生变化,要重新编写代码,编译,生产部署包,然后再更新用户程序。这样的过程比较繁琐。 本文讨论生成后的应用系统与外部编译的业务库实现动态绑定,应用程序在运行过程中动态绑定要实现的外部业务,当业务发生变化,也只是替换这些外部的动态库,不用重新对应用程序进行修改和编译,实现了耦合绑定。同时,业务实例对象可以在程序运行时实现实例化,达到了封装效果。并且降低了调用代码和具体实现类代码的耦合,增强灵活性和可复用性,增加了软件的可维护性。 C#提供的反射机制,再结合自适应数据参数的传递,通过这个技术,我们可以将应用框架中的扩展点以插件式程序集的方式来动态加载、构建,从而实现可动态扩展的应用程序。 2 反射机制的基础知识 反射[1][2]是.NET中重要机制,通过反射,可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件,以及构造函数等。还可以获得每个成员的名称、限定符和参数等。.NET的应用程序结构分为应用程序域、程序集、模块、类型和成员几个层次,公共语言运行库加载器管理应用程序域,这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。程序集包含模块,而模块包含类型,类型又包含成员,反射则提供了封装程序集、模块和类型的对象。我们可以使用反射动态地创建类型的实例,将类型绑定到现有对象或从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。 反射通常具有以下用途:① 使用Assembly定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例;② 使用Module了解如下的类似信息,如模块的程序集以及模块中的类等:③ 使用CoustructorInfo了解如下的类似信息,如构造函数的名称、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtua1)等;④ 使用MethodInfo来了解如下的类似信息,如方法的名称、返回类型、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtua1)等;⑤ 使用FieldInfo来了解如下的类似信息,如字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值;⑥ 使用EventInfo来了解如下的类似信息,如事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,并添加或移除事件处理程序:⑦ 使用PropertyInfo来了解如下的类似信息,如属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,并获取或设置属性值;⑧ 使用ParameterInfo来了解如下的类似信息,如参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。 3.总体设计思路 插件是一种遵循一定规范的应用程序接口编写出来的程序模块。当应用程序已经部署,但业务发生了变化,这样可以通过读取插件配置信息,载入新的应用构件,实现变化的业务。 对于应用系统的框架而言,扩展点是框架中预先定义的一些“点”。 在框架复用中应用构件的组装需要基于扩展点进行。构造性和演化性是软件的两个本质特征,作为一类重要的可复用软件制品。而基于扩展点可以组装不同的应用构件以适应领域的变化性。则体现了框架对于软件演化特征的支持[3]。 本文涉及到几个概念,插件配置定义,接口定义,方法定义和调用参数定义和返回参数定义。在本插件平台中,配置文件描述插件配置定义,接口定义,方法定义。对于调用参数定义和返回参数定义则采用通用对象和动态对象组[4]来实现传入和返回参数。 插件平台的实现过程如图1所示。当平台运行初始化时,通过读取XML配置信息,装载DLL,通过C#的反射机制分析DLL里的全部实现类和方法。外部构件可以在平台容器中被实例化,并执行插件点的方法。实现的算法不再是编码硬绑定。 图1 PlugPlatform整个过程图 这样,应用程序在运行过程中动态绑定要实现的外部业务,当业务发生变化,也只是替换这些外部的动态库,不用重新对应用程序进行修改和编译,实现了耦合绑定。 4.具体实现 PlugPlatform平台包括四个部分:① 配置文件的获取和解析;② 通用参数和动态参数组处理;③ 插件平台装载DLL并执行外部方法;④ 异常处理。 4.1 配置文件的获取和解析 配置文件以XML Schema为基础,分为两种类型,一种是类配置文件,主要描述关于外部DLL中的类以及方法的内容。第二种配置文件是接口配置文件,主要描述关于外部DLL中的接口以及方法的内容。 类配置文件的XSD如图2所示。
图2 类配置文件的schema图 按照此XSD形成的配置XML如图3所示。 图3 类配置文件的XML树 同理可以接口配置文件的XSD内容(如图4)和XML树(如图5)。
图4 接口配置文件的schema图 图5 接口配置文件的XML树 在实现将XML树状结构的数据转换为二维MethodObject哈希表,MethodObject哈希表是一key/value的键值对,其中key通常可用来快速查找,value用于存储对应于key的值。MethodObject类数据结构如下: MethodObject哈希表中key为保证内容的{wy}性而采用方法的全名。可以,这样形成的主键可以进行快速查找。value用来存储MethodObject对象。同时MethodObject对象与ClassObject对象,InterfaceObject对象和DLLFile对象都是多对一的关系,所以,一旦获得了MethodObject对象,就可以反推出ClassObject对象,InterfaceObject对象和DLLFile对象。 图6 配置XML转MethodObject哈希表 根据XML Schema可以构建XML文档树,对XML文档树的节点进行分层遍历,然后采用递归算法,依次把XML文档树上最边上的叶子转化为方法对象哈希表,实现方式如图6所示。 4.2通用参数和动态参数组处理 对于外部的方法,要传入参数,同时也获得结果。这些都要用一些通用的数据结构来描述。参数必须可以支持任何类型,是一个通用性的参数。通过创建一个数据的通用类,可以保证支持任何数据类型。 由于传入和传出的参数有多有少,这就要求参数组能实现随意的自动增长和减少。通过设计一个动态自增长的参数数组就可以实现。设计模型如图7表示。 图7 DataValueObject类和DynamicArrayObject类的设计模型 动态参数组的增加数组对象方法如下: 动态参数组的删除数组对象方法如下: 4.3插件平台动态装载DLL并执行 PlugFramework是整个PlugPlatform平台的核心内容。主要实现检查和装载DLL文件,动态创建实例化对象,验证并执行外部方法。
图8 PlugPlatform动态装载的全过程 PlugPlatform依据配置文件可以了解装载的外部DLL文件和要求执行的类或接口方法。整个装载和的调用过程如图8说明。 PlugFramework包含有类和接口,这些类和接口之间有继承、实现、关联关系。其类图如图9所示。 图9 PlugPlatform类图 PlugFramework各个类的具体详细描述如下: ? ? 序号 ? ? 名称 ? ? 实现功能 ? ? 备注 ? ? 1 ? ? Plus.PlusConfig ? ? 插件平台的配置信息类,可创建工厂类 ? ? 类 ? ? 2 ? ? Plus.PlugFactory ? ? 插件平台的工厂类,可以创建方法实现类 ? ? 类 ? ? 3 ? ? Plus.IAction ? ? 方法实现的接口 ? ? 接口 ? ? 4 ? ? Plus.Framework.PlugAction ? ? 方法实现的抽象祖先类 ? ? 类 ? ? 5 ? ? Plus.Framework.ClassAction ? ? 类对象的实现类 ? ? 类 ? ? 6 ? ? Plus.Framework.InterfaceAction ? ? 接口对象的实现类 ? ? 类 ? ? 7 ? ? Plus.Framework.AssemblyManager ? ? Assembly的管理类,生成Assembly。 ? ? 类 ? ? 8 ? ? Plus.Framework.TypeManager ? ? Type的管理类,可实现对Type、Object的生成和检查。包括动态方法的调用。 ? ? 类 ? ? 动态调用外部方法的核心代码如下所示: 图10表示PlugPlatform实现全过程。下面分别对每个步骤做一个详细描述: ① 外部应用请求动态调用。 ② PlusConfig类根据配置文件创建PlugFactory对象。 ③ PlugFactory对象创建一个Action对象。 ④ Action对象获得MethodObject对象组,逆向产生DllFileObject对象。 ⑤ 根据DllFileObject对象中的DLL文件信息,Action对象通过AssemblyManager类获得Assembly对象。 ⑥ Action对象使用Assembly对象创建TypeManager对象。 ⑦ Action对象传递MethodObject对象给TypeManager对象。 ⑧ TypeManager对象可依据MethodObject对象获得ClassObject对象。并使用ClassObject对象信息动态创建一个外部ClassObject对象的实例instance。 ⑨ TypeManager对象使用instance和MethodObject对象信息调用instance的动态方法。instance把执行结果返回给Action对象。 ⑩ Action对象把执行结果返回给外部应用。
图10 PlugPlatform实现的顺序图 其中Plus工厂模式采用了Factory模式。对于Assembly的生成采用了Singleton模式。 4.4 异常处理 由于应用程序中有很多不可预料的问题,本平台在很多地方都有可能出现人为错误,如找不到配置文件;配置文件的格式不对,不能解析配置文件;类或接口名称写错了,不能实例化类;方法名称写错了,不能执行方法等等。增加异常处理主要是增强其容错性,在这里就不做更多的说明。 5.应用实例 本例子程序主要有三个方面组成:XML配置文件、外部DLL文件和PlusPlatform调用代码。 5.1 XML配置文件 采用的XML配置文件有两个,一个是针对类对象的XML配置文件,一个是针对接口对象的配置文件。 其中类对象的XML配置文件: 接口对象的配置文件与类对象配置文件基本相同,只不过配置信息中由类换成了接口: 5.2 DLL文件内容 其编译的DLL文件为UserLibrary.dll,该dll文件包括两个类和一个接口,其内部代码为: 5.3 调用插件平台代码 调用代码也分为两类,一类是针对类对象处理的,代码如下: 另一类是针对接口对象处理,代码如下: 可以对返回的DynamicArrayObject做分解查看,满足设计要求。 6.结束语 反射机制结合动态数组很好地解决了应用软件的后期维护和升级。对于应用软件的变化,可不改动任何现有的程序,只要修改XML配置文件的相应对象名称和加载
public class MethodObject {
private ClassObject classobject = null;
private InterfaceObject interfaceobject = null;
private DllFileObject dllfileobject = null;
private string name = string.Empty;
private string simplename = string.Empty;
private string implementname = string.Empty;
?
public string MethodName {get { return this.name; } set { this.name = value; } }
public string SimpleName { get { return this.simplename; }set { this.simplename = value; } }
public string ImplementName {get { return this.implementname; }set { this.implementname = value; }}
public ClassObject ClassObject {get { return this.classobject; }set { this.classobject = value; }}
public InterfaceObject InterfaceObject {get {return this.interfaceobject; }set {this.interfaceobject = value; }}
public DllFileObject DLLFileObject{ get { return this.dllfileobject; } set { this.dllfileobject = value; }}
}public DynamicArrayObject addObject(DataValueObject obj) {
if (Objects == null) {
Objects = new DataValueObject[1];
Objects[0] = obj;
return this; }
else {
DataValueObject[] objectList = new DataValueObject[Length + 1];
for (int i = 0; i < Length; i++) { objectList[i] = Objects[i]; }
objectList[Length] = obj;
Objects = objectList;
return this;}
}public DynamicArrayObject deleteObject(int idx) {
if (Objects == null) return this;
else {
if (idx >= 0 && idx < Length && Length > 1) {
DataValueObject[] objectList = new DataValueObject[Length - 1];
for (int i = 0; i < idx; i++) {objectList[i] = Objects[i];}
for (int i = idx + 1; i < Length; i++) {objectList[i - 1] = Objects[i]; }
Objects = objectList;
return this; }
else {
if (idx == 0 && Length <= 1) {
Objects = null;
return this; }
else return this; } }
}
public object InvokeClassMethod(String className, object[] objectArgs, String methodName, object[] methodArgs) {
Object[] newArgs = new Object[methodArgs.Length];
Object thisObject = new Object();
Type type = CreateType(className);
MethodInfo[] methods = type.GetMethods();
Object instance = CreateObject(type, objectArgs);
foreach (MethodInfo m in methods) {
if (m.Name == methodName) {
newArgs = ConvertArgsType(m, methodArgs);
try {
if (!m.IsStatic) thisObject = m.Invoke(instance, newArgs); //非静态方法,使用类实例调用
else thisObject = m.Invoke(null, newArgs);
return thisObject;}
catch (Exception e){throw new PlusException("不能动态调用方法,原因:" + e.Message, e); }}
}
return thisObject;
}<?xml version="1.0" encoding="utf-8" ?>
<PlugPlatformResource>
<DllFile name="UserLibrary.dll" filepath ="/" objectType ="class">
<classobject name="UserLibrary.UserTest1" >
<Methodobject>testAction01</Methodobject>
<Methodobject>testAction02</Methodobject>
<Methodobject>testAction03</Methodobject>
</classobject>
<classobject name="UserLibrary.UserTest2" >
<Methodobject>testAction01</Methodobject>
</classobject> ?
</DllFile>
</PlugPlatformResource><?xml version="1.0" encoding="utf-8" ?>
<PlugPlatformResource>
<DllFile name="UserLibrary.dll" filepath ="/" objectType ="interface"> ?
<interfaceobject name="InterfaceTest1" implement="UserLibrary.UserTest2" >
<Methodobject>testAction01</Methodobject>
</interfaceobject>
</DllFile>
</PlugPlatformResource>public class UserTest1 {
public DynamicArrayObject testAction01(DynamicArrayObject outObject) {
DynamicArrayObject thisObject = new DynamicArrayObject();
//分解DynamicArrayObject
DataValueObject do1 = null;
string ls = null;
for (int i = 0; i < outObject.Length; i++) {
do1 = outObject.getObject(i);
ls += (String)do1.getDataValue(); }
DataValueObject do2 = new DataValueObject();
do2.setDataType(do1.getDataType()).setDataValue(ls);
//组装DynamicArrayObject,返回DynamicArrayObject
thisObject.addObject(do2);
return thisObject;
}
public DynamicArrayObject testAction02(DynamicArrayObject outObject) {
return outObject; }
}
public class UserTest2 : InterfaceTest1 {
public DynamicArrayObject testAction01(DynamicArrayObject outObject) {
DynamicArrayObject thisObject = new DynamicArrayObject(); ?
//分解DynamicArrayObject
DataValueObject do1 = null;
string ls = null;
for (int i = 0; i < outObject.Length; i++) {
do1 = outObject.getObject(i);
ls += (String)do1.getDataValue(); }
DataValueObject do2 = new DataValueObject();
do2.setDataType(do1.getDataType()).setDataValue(ls);
//组装DynamicArrayObject,返回DynamicArrayObject
thisObject.addObject(do2);
return thisObject;
}
public DynamicArrayObject testAction02(DynamicArrayObject outObject){
return outObject;}
}
public interface InterfaceTest1 {
DynamicArrayObject testAction01(DynamicArrayObject outObject);
} DynamicArrayObject thisObject = new DynamicArrayObject();
DataValueObject do1 = new DataValueObject();
DataValueObject do2 = new DataValueObject();
do1.setDataType("string").setDataValue("类测试:{dy}个对象值."); ?
do2.setDataType("string").setDataValue("第二个对象值.");
thisObject.addObject(do1).addObject(do2);
string dllFile = Application.StartupPath + "\DllClassFile.xml";
?
PlugFactory factory = PlusConfig.BuildFactory(dllFile);
IAction action = factory.CreatAction();
DynamicArrayObject outputObject = action.Execute("UserLibrary.UserTest1.testAction01", thisObject);DynamicArrayObject thisObject = new DynamicArrayObject();
DataValueObject do1 = new DataValueObject();
DataValueObject do2 = new DataValueObject();
do1.setDataType("string").setDataValue("接口测试:{dy}个对象值."); ?
do2.setDataType("string").setDataValue("第二个对象值.");
thisObject.addObject(do1).addObject(do2);
string dllFile = Application.StartupPath + "\DllInterfaceFile.xml";
PlugFactory factory = PlusConfig.BuildFactory(dllFile);
IAction action = factory.CreatAction();
DynamicArrayObject outputObject = action.Execute("InterfaceTest1.testAction01", thisObject);