SAF.ClassFactory是用下面两个.NET关键概念实现的:
- .NET反射。
- .NET remoting。
.NET反射机制为创建对象和调用其方法提供了替代方案。利用反射机制编写应用会比较麻烦,为达到特定目的,使用反射机制比使用常规编程方法时的代码量多得多。然而,恰当地在应用中使用反射机制,将显著提高应用代码的灵活性。
.NET remoting是访问不同应用程序域(AppDomain)中对象的技术,无论该应用程序域位于本地还是远程。它为.NET组件提供的功能,类似于DCOM为COM组件提供的功能。和DCOM相比,在网络传输、消息定制以及调用发生后在远程服务器上创建对象这三个方面,.NET remoting可以提供更多的控制。另一方面,这种高度灵活性意味着我们要做更多的配置工作,要掌握更多的知识才能让.NET remoting正常工作。
4.2.1 .NET反射
为了更好地说明.NET反射的作用,先看两个殊途同归的例子:
BusinessObject bo =new BusinessObject();
bo.DoWork();
//The .NET reflection way to call an object.
Assembly assm = Assembly.Load("TestApp");
Type objType = assm.GetType("TestApp.BusinessObject");
object objInstance = Activator.CreateInstance(objType);
objType.InvokeMember("DoWork",BindingFlags.InvokeMethod, null, objInstance,null);
第一个代码段我们经常看到。第二个代码段和第一个代码段的效果完全相同:创建一个BusinessObject实例,然后调用它的DoWork()方法。
如果没有特殊原因(稍后我将举一个特殊原因的例子),你应该尽量避免使用反射机制来创建对象和调用方法。当然,为了向同事们炫耀技术才华,而使用一些不必要的复杂花哨的代码,不应成为上述特殊原因之一。
使用反射机制有几个缺点。除了让程序变得更加晦涩难懂外,其最大缺点就是语言编译器无法对应用中的对象进行类型检查。例如,如果DoWork()方法被重命名为Dowork(),而你调用了bo.DoWork(),那么代码编译将失败。这样,你就能尽早地发现程序中的bug或问题。然而,如果你使用了前例所示的反射机制来创建对象并调用方法,编译器将无法发现这个错误,因为对象和方法的名字都被视为字符串类型了。这样,即使你键入了错误的类名或方法名,你的程序也会被编译器编译通过。直到第一次运行程序时,收到了讨厌的运行时错误,你才知道源程序的问题。在使用反射机制时,如果你用的是Visual Studio.NET(并且你应该使用),你会发现它的智能感知(Intellisense)功能也不管用了;因为你在应用中没有引用强类型对象(strongly typed object),所以你也不会看到显示对象方法和数据成员的下拉列表;你必须手工输入多得多的代码,这些代码还可能有错误,于是不可避免地导致额外的编码和排错工作量。
尽管存在这些缺点,但还是有些使用反射机制的特殊原因。使用反射机制的好处之一是,它提供了一种手段,将指定具体类推迟到了运行时刻。我们注意到:在上面第二个代码段中,所有的类名和方法名都是字符串。所以,你可以使用在运行时刻赋值的字符串变量,来代替那些硬编码(hard-coded)的字符串。这样,就避免了应用直接依赖于特定的具体类。
在讨论反射代码为什么是上面的例子那样之前,先来研究一下当一个.NET应用中的对象被创建时,幕后发生了什么。图4-1显示了应用程序域、程序集(assembly)、类、类实例以及它们之间的关系。

图4-1 应用程序域中的组件
当我们运行名为TestApp.exe的应用程序时,.NET CLR(通用语言运行时)会首先创建一个应用程序域来容纳应用程序TestApp.exe。接着,它将该应用引用的程序集加载到该应用程序域中。MSCorLib.dll是一个程序集,它包含了很多System命名空间及其子命名空间(child name space)中的类,例如System、System.Text和System.IO等。该程序集会自动被加载到每个应用程序域中。然后,CLR加载正在运行的应用程序(即TestApp.exe)所属的程序集。我们利用Visual Studio .NET的“增加引用(Add Reference)”功能,增加到应用程序中的那些程序集,也将被加载到该应用程序域中。前面例子中的BusinessObject.dll就是这样一个程序集。该应用程序开始运行之后,它可能会创建很多对象,并调用它们的方法。CLR将为应用程序创建指定类类型的对象,并通过检查应用程序的清单(manifest)来确定要加载的程序集,以保证此应用程序的正常运行。你可以使用ildasm(中间语言反汇编程序)查看应用程序的清单,这个工具提供应用程序的类、方法、数据成员和应用程序依赖关系四种信息。.NET Framework SDK提供了Ildasm.exe,位于Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin。图4-2显示了TestApp.exe的清单信息。

图4-2 TestApp.exe的清单数据
通过检查应用程序的清单,CLR可以知道有哪些正被应用程序使用的额外程序集需要加载。图4-2清单中的文本“.assembly extern xxxxxxx”显示了该应用程序依赖的额外程序集,注意,其中并没有那些程序集的文件路径,CLR会在运行时刻根据预定义的程序集探查规则(assembly probing rule)来寻找程序集文件,这一点稍后讨论。引起CLR加载程序集的另外一个途径是通过反射机制。下面,让我们回到反射机制的四行代码,深入研究每一行代码的含义。
通过反射机制来调用一个方法,包括以下四步:
- 加载程序集。
- 获取类的类型。
- 创建该类的实例。
- 调用该实例中的方法。
System.Reflection.Assembly类有两个静态方法:Assembly.Load(string assemblyname)和Assembly.LoadFrom(string filename) 。通常用这两个方法把程序集加载到应用程序域中。
程序集名称是一个不包含扩展名的文件名。例如,TestApp.exe和BusinessObjectLibrary.dll的程序集名称分别是TestApp和BusinessObject。如果希望CLR根据指定的程序集名称找到程序集,应该使用Assembly.Load()方法。但是如果该程序集被强命名(strongly named),你必须同时提供程序集名称和公钥(public key token),以向CLR说明你的意图是:首先试图从GAC(Global Assembly Cache,全局程序集缓存)加载程序集,如果失败,继续在应用所在目录和私有路径目录中寻找并且加载程序集。
| 注释:如果CLR在GAC中不能找到那个强命名程序集,它将尝试在配置文件/configuration/runtime/asm:assemblyBinding/asm:dependentAssembly元素声明的位置中寻找。如果不是强命名程序集,你可以通过往/configuration/runtime/asm:assemblyBinding/asm:probing元素中增加额外的文件夹名称来命令CLR在应用程序目录下的其他文件夹中寻找。默认情况下,CLR只会先后在该应用程序所在目录和其中的\bin子目录下寻找私有程序集。 |
另一种加载程序集的途径是通过Assembly.LoadFrom(string filename)。如果你希望加载的程序集超出了CLR的预定探查范围,你可以用这种办法直接从一个文件位置加载程序集。
反射代码的第一行是Assembly assm = Assembly.Load("TestApp");,如果TestApp程序集已经被加载,则它返回现存TestApp程序集的引用。否则,CLR将加载该程序集,然后返回程序集的引用。
接下来的一行是Type objType = assm.GetType ("BusinessObjectLibrary.Business- Object");,它返回程序集(本例即assm)中的类类型信息。GetType()方法会重载(overload) 多次。在这个例子中,我希望用指定类型名创建一个Type对象,该指定类型名采用命名空间加上类名的形式(如果此类在GAC中,你还必须提供公钥,而版本号为可选参数,可根据情况决定是否提供)。从代表BusinessObject类的Type实例objType中,你能得到很多有用的信息,例如objType.GetMethods()返回BusinessObject类中所有方法的信息,而objType.GetProperties()返回所有属性的信息。
你可以通过调用Activator.GetInstance(Type type)方法,命令CLR为指定类类型创建对象。在我们的例子中,objInstance = Activator.CreateInstance(objType);相当于bo = new BusinessObject();。
最后一步是调用DoWork()方法。objType.InvokeMember()和bo.DoWork()完成同样的工作。InvokeMember()方法支持object[]类型的参数,它代表目标方法的输入参数数组。
在类工厂中,Activator.GetInstance()和Activator.GetObject()是两个对我们特别有用的方法,因为它们为我们提供了一种途径来创建指定类型的对象,而无须在设计时了解类的任何情况。这两个方法的另外一个有用之处是,它们也支持通过.NET remoting来创建remote对象,这些稍后会讨论。利用这个功能,我们能够建立产生远程对象的类工厂服务,这些远程对象对调用它们的开发者来说是位置透明的。
4.2.2 .NET Remoting
SAF.ClassFactory的目标之一是支持对开发者位置透明的远程对象创建。本节将讨论.NET remoting技术,它提供了访问.NET远程对象的途径。
希望使用远程调用代替本地调用的原因有很多,例如使应用具有更大的可伸缩性(scalable)等等。通过把领域逻辑分散到不同服务器的不同类中,可以为应用中需要资源的部分分配更多资源。例如,我们可以把一些执行处理密集型任务的类移到单独服务器上,然后为这些服务器增加额外的处理能力;或者把这些类部署到多个服务器上以达到“服务器农场效应(server farm effect)”。我们还可以利用远程对象来降低应用的网络负载。许多应用要与远程集中式数据库服务器交互,如果它们直接与数据库交互,可能会有大量数据来来往往在网络上传输。但是,如果我们把频繁与数据库交互的类部署到数据库服务器上,或部署到与数据库服务器有快速连接的系统中,就可以将频繁交互限制在一个高速且分离的网段,从而减少额外的网络负载。当然,还有很多其他的情况,仅仅是因为我们并不“拥有”那些业务对象——更不用说把它们部署到哪儿了,所以不得不使用远程对象。
| 注释:如果要采用远程调用来降低网络负载,我们必须首先考虑,往应用中增加远程对象可以降低多少网络负载。这是因为,增加远程对象本身也会带来一定量的网络负载。这样,我们就可以搞清楚,究竟远程对象的引入是增加还是减少了网络负载。 |
调用远程对象已经有相当长的历史了。不管技术如何变化,这种调用总是遵循相同的remoting模型,其中包括一个客户端存根(client stub)和一个服务器代理(server proxy)。本地调用和远程调用的最大不同是方法的调用方式。当一个对象调用另一个本机对象时,前者持有一个描述后者存储位置的对象引用或指针;换言之,调用者清楚地知道在哪能找到被调用者。然而,如果两者不在同一台机器上时,情况就不同了;由于每台机器为对象分配的内存不尽相同,调用者根本无法在另外一台机器上找到被调用者,因为它没有被调用者所在机器的“内存映像图”。
通用remoting模型简介
为解决上述问题,remoting模型使用“中间人”将诸如对象的内存位置等特定机器相关信息,转换成调用者和被调用者都遵守的预定义通用协议或规则。图4-3高度抽象地描述了该remoting模型。

图4-3 对象的remoting模型
图4-3显示了remoting模型是如何工作的。客户端保存一个存根,该存根是一个代表远程对象的对象。因为该存根具有和远程对象相同的方法签名(method signature),所以客户程序可以像调用远程对象一样来调用存根的方法。存根接到调用之后,将方法调用转换成预定义的、能被服务器应用所理解的消息。接着,存根把这个消息通过网络发送给服务器,然后等待服务器的响应。
在服务器端,有一个代理在监听网络端口上的任何“调用”消息。它一收到存根发送来的消息,就解析该消息,并找出消息发送者以及发送者希望调用的对象和方法。尽管存根是代表客户程序向服务器传递请求的,服务器代理对象却不知道进行方法调用的真正客户。对于服务器代理对象而言,那个存根就是客户。
现在,服务器端的代理已经知道了消息希望调用的对象和方法,于是,它将加载被请求的类创建它的一个实例,然后调用它的方法。因为代理和服务器对象都在同一机器上,所以代理可以通过本地调用来使用服务器对象。服务器对象响应方法调用,如果该方法不是void类型还会返回一个值。服务器对象把代理看作它的客户,它并不知道网络另一端真正的客户和存根。
代理从服务器对象接收到返回值之后,它将执行类似存根做过的事情。代理把“方法返回值”转换成消息,并转发给网络另一端的存根。
当存根收到了消息中的返回值时,它必须把消息转换成方法调用的返回值类型。从来自客户端的方法调用开始,它一直被阻塞;一旦存根转换好了“方法返回值”,它立即将返回值传递给客户;客户紧接着就会解除阻塞,执行下一行代码。
客户对象和服务器对象并不直接交互,理解这一点很重要。它们是通过各自的中间人来完成交互的。这就使得客户调用远程对象时,获得了我们所期望的位置透明性。另外,也达到了将客户和服务器解耦的目的,从而可以在不影响现存应用代码的情况下,替换用于实现传输和转换消息的实际技术。
.NET Remoting模型
图4-3显示了一个高度抽象的remoting模型。现在,让我们稍微深入地讨论在.NET环境下,参与远程对象调用的组件。其中一些术语可能不同,但其蕴含的remoting概念是相同的。
图4-4显示了可能参与.NET remoting的组件。

图4-4 .NET remoting处理流程
图4-4中并没有太多新术语,下面分别简要介绍它们:
- 透明代理(Transparent proxy):义如其名,透明代理对象支持远程调用的透明性。这意味着透明代理拥有和远程对象相同的方法签名,使得透明代理从“表面”上看起来和远程对象相同。客户对象会保存一个透明代理的引用,并使用它进行方法调用,这样就好像它是真正的业务对象一样。当客户调用透明代理的方法时,透明代理将创建一个封装了当前调用的类型、方法名和参数信息的消息对象(即IMessage),并将该消息对象发送给实际代理。
- 实际代理(Real proxy):真正负责“远程”工作的是实际代理。根据应用配置文件remoting部分的设置,实际代理将透明代理传递过来的消息对象转发到消息处理单元链(chain of message-processing units)中。消息处理单元也叫消息接收器(message sink)。消息处理单元链提供如下功能:将方法调用请求序列化(serialize)成预定义格式,对消息进行某些用户定义的处理,最终建立与服务器的连接并发送消息。
- 消息接收器(Message sink):消息接收器是一些单独的消息处理单元。你也可以建立自己的消息接收器,并把它们插入到消息处理单元链中,从而定制消息传入传出时的处理方式。.NET本身提供一组格式转换接收器,例如二进制格式转换接收器、SOAP格式转换接收器以及HTTP和TCP传输接收器等。你还可以通过修改配置文件的remoting段,为remoting建立自己的接收器。
- 堆栈构造者接收器(StackBuilder sink):这是一个特殊的消息接收器,它将消息转换成目标对象堆栈中的方法调用消息。把一个“方法调用”消息压入对象的堆栈,相当于从本地的另一个对象中调用那个对象。
图4-4显示的处理流程是从透明代理开始的,但是,如何首先获得代表远程对象的透明代理呢?最终需要另一个对象ObjRef参与.NET remoting过程。
ObjRef是一个可序列化的对象,它包含了客户程序所需的创建透明代理的所有信息。最终,客户程序将利用这个对象的某些属性——例如Url和TypeInfo,向远程服务器发送方法调用消息。ObjRef对象是在服务器端创建的,创建的方法有二:在配置文件的remoting段指定远程对象,或者直接编程创建它。第一种方法中,在运行时刻会为你的远程对象自动创建ObjRef对象,并在.NET remoting中注册。至于第二种方法,你可以通过调用RemotingServices.Marshal()来手工创建和注册ObjRef对象。例如:
ObjRef objRef = RemotingServices.Marshal(bo, someUrl);
ObjRef对象的创建和序列化发生在服务端;然后,当客户程序通过调用new操作符、Activator.GetObject()或Activator.CreateInstance()来实例化远程对象时,ObjRef对象会被传送到客户端。这个把对象引用转换成序列化形式的过程,叫作列集(marshaling)。当客户程序从远程服务器接收到ObjRef对象时,它可以通过调用RemotingServices.Unmarshal(objRef)来获得(透明)代理对象。这个把序列化形式转换回对象引用的过程,叫作散集(unmarshaling)。注意,当你调用new关键字、Activator.GetObject()或Activator.CreateInstance()在客户端创建远程对象时,散集是在幕后进行的,所以你无须在代码中直接调用RemotingServices. Unmarshal()方法。
| 注释:一个对象必须继承名为System.MarshalByRefObject的具体类,才能成为可远程化(remotable)对象。CreateObjRef()是System.MarshalByRefObject的一个方法,在运行时刻,该方法将被CLR调用,以生成发送到客户程序的ObjRef对象。换言之,合法的可远程化对象必须知道如何为自己生成一个ObjRef对象。如果你忘记了让你的类继承MarshalByRefObject,从远程客户程序调用它时,则会收到一个“无效强制类型转换(invalid cast)”的异常。 |
当客户程序获得了一个透明代理的引用之后,它将继续按照图4-4所示的过程,进行透明代理对象上的方法调用。
了解了.NET remoting模型之后,接下来讨论一些实例,看看如何在我们的应用中使用.NET remoting技术。
4.2.3 .NET Remoting 实例
.NET远程对象的激活方式有两种:服务器激活(server activation)和客户端激活(client activation)。两者的不同之处在于,服务器激活远程对象的生命周期是由服务器来管理的,而客户端激活远程对象的生命周期由客户端管理。
上述两种激活模型的区别,很类似如下两种客户服务模型的区别:电话方式的客户服务和在线聊天室方式的客户服务。当你拨打客户服务电话时,会有一个客户服务代表为你服务;然后,你可以慢慢问你的问题。不管你的问题是长还是短,客户服务代表为你服务的时候,不能为其他客户服务。在线聊天室方式的客户服务则不同,客户服务代表能够通过回答多个在线客户问题的方式,同时为多个客户服务。客户端激活模型类似电话方式的客户服务:客户程序创建一个远程对象并保存它直到不再使用它为止,其间,其他客户程序不能和这个远程对象交互;换言之,该远程对象只为单个客户程序服务。服务器激活模型——是个单体(singleton)——类似在线聊天室方式的客户服务:一个远程对象可以同时为多个客户程序服务。
服务器激活又可分为两种:单体(singleton)和单一调用(single call)。义如其名,单体保证一个类在特定时间仅有一个实例存在于服务器中。如果远程对象被标为“单体方式服务器激活(server activation through singleton)”,那么在服务器端只须创建一个远程对象来为多个客户的所有请求服务。如果远程对象被标为“单一调用”,那么CLR将为每一个来自客户端的方法调用创建一个远程对象,并在调用结束后销毁。如果客户程序对它持有的同一个透明代理发出了另一个方法调用,远程服务器上的CLR将创建一个新远程对象来为之服务,并在服务结束后销毁。在这个例子中,虽然远程对象只为一个客户程序服务(实际上,它只为一个方法调用服务),但客户程序不能维护远程对象跨方法调用的状态;然而,这在客户端激活情况下是可以的,客户端只要持有远程对象代理就可以对同一远程对象进行多次调用。对于客户端激活模型来说,并不存在单体和单一调用之分。实际上,客户端激活只有一种:只要需要,客户端可以一直保持那个远程对象。
每个远程对象都能够通过配置文件或RemotingService类来配置,从而程序化地设置远程对象。让我们来具体看一下这些配置方式。
使用配置文件的服务器激活
客户端应用代码
下列代码位于客户端,它加载一个远程配置,然后使用new关键字创建一个新对象。
BusinessObject bo = new BusinessObject();
客户端配置文件
在客户端代码中用关键字new来实例化一个远程对象时,必须以某种方式告诉CLR我们希望创建的对象不是本地对象。CLR需要知道这个情况,因为如果是远程创建对象,CLR要请求远程服务器上的ObjRef对象,并让它动态创建一个透明代理。我们在应用配置文件的<system.runtime.remoting>段,告诉CLR哪些类型的对象是远程对象,如下所示:
<system.runtime.remoting>
<application name="Client">
<client>
<wellknown url="http://localhost:8989/BusinessObjectA.rem"
type="BusinessObjectLibrary.BusinessObject, BusinessObjectLibrary" />
</client>
</application>
</system.runtime.remoting>
</configuration>
wellknown对象是服务器激活对象,且<wellknown>元素为CLR提供了足够的信息,说明在哪请求ObjRef对象,以及客户应用要使用哪种类型的对象。现在我们来看一下服务器为了支持远程对象,需要做些什么。
服务器端应用代码
在服务器端,我们需要创建一个应用来容纳远程对象。我用一个简单的控制台应用程序作为例子,但理想而言,应该创建一个windows服务,或者用IIS来容纳服务器端的.NET远程对象。
Console.WriteLine("Press enter to exit the application");
Console.ReadLine();
服务器应用的代码很少。所有需要做的就是加载配置文件,让CLR自动注册所有.NET远程对象和传输通道——例如HTTP通道或TCP通道。通道(channel)负责跨越remoting边界传输消息数据。注意,在本例中,为了让服务器应用能够持续为客户服务,你必须用Console.ReadLine()来保持应用处于运行状态。
服务器配置文件
在下面这个例子中,.NET远程对象被配置成单体。通过在<wellknown>元素中声明mode="SingleCall",可以方便地将远程对象配置改为单一调用。
<system.runtime.remoting>
<application>
<service>
<wellknown
mode="Singleton"
type="BusinessObjectLibrary.BusinessObject,BusinessObjectLibrary"
objectUri="BusinessObject.rem" />
</service>
<channels>
<channel ref="http" port="8989"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
使用配置文件的客户端激活
要把一个.NET远程对象从服务器激活改变成客户端激活,我们只需在配置文件中修改两行。下面对照说明修改前后的区别:
客户端配置的区别如下:
服务器激活(客户端)
<wellknown url="http://localhost:8989/BusinessObjectA.rem"
type="BusinessObjectLibrary.BusinessObject,BusinessObjectLibrary" />
</client>
客户端激活(客户端)
<activated type="BusinessObjectLibrary.BusinessObject ,
BusinessObjectLibrary" />
</client>
服务器端配置的区别如下:
服务器激活(服务器端)
<wellknown
mode="Singleton"
type="BusinessObjectLibrary.BusinessObject,BusinessObjectLibrary"
objectUri="BusinessObject.rem" />
</service>
客户端激活(服务器端)
<activated type="BusinessObjectLibrary.BusinessObject,
BusinessObjectLibrary" />
</service>
总结一下它们的区别:客户端激活用<activated>元素描述远程对象,而服务器激活是用<wellknown>元素。还有,客户端激活并不需要url或objectUri属性,而服务器激活对象需要。
把远程对象信息放入配置文件,使我们能在不改变代码的情况下来改变远程对象的行为。然而,有时我们希望根据某个业务规则动态配置.NET remoting。例如,为了达到负载平衡目的,我们可能希望改变对象的URL,或者在不通过重启应用来刷新配置的情况下,注册一些新的远程对象。下面让我们来看一下,如何利用程序来影响配置文件的作用。
无须配置文件的服务器激活
客户端应用代码
我们可以通过调用Activator.GetObject()方法来创建远程对象。System.Activator是一个类,这个类包含了创建本地或远程对象的方法。GetObject()是静态方法,以类
型和目标对象的位置为参数。例如,在不依靠配置文件的情况下,创建远程对象BusinessObject的代码如下:
typeof(BusinessObject),
"http://localhost:8989/BusinessObject.rem");
bo.DoWork();
用GetObject()方法创建远程对象非常简单,但也有缺点。首先,GetObject()只支持服务器激活远程对象。从上面的代码中可以看出,必须为远程对象提供一个URL,这是服务器激活的一个必要条件。其次,GetObject()只能通过默认构造器来实例化远程对象。如果你需要创建客户端激活的远程对象,或使用非默认构造器创建远程对象,你应使用Activator.CreateInstance()方法。
服务器端应用代码
在服务器端设置远程对象时需要更多代码,主要分为两部分:注册通道和注册远程对象类型。代码如下所示:
HttpChannel channel = new HttpChannel(8989);
ChannelServices.RegisterChannel(channel);
//register the remote object type.
WellKnownServiceTypeEntry wste= new
WellKnownServiceTypeEntry(typeof(BusinessObject),
"BusinessObjectLibrary",WellKnownObjectMode.SingleCall));
RemotingConfiguration.ApplicationName = "BusinessObject";
RemotingConfiguration.RegisterWellKnownServiceType(wste);
上面的代码用来在服务器端注册服务器激活对象。你也可以使用ActivatedServiceTypeEntry和RemotingConfiguration.RegisterActivatedServiceType()在服务器端注册客户端激活对象。记住下面的对应关系,将有助于你搞清那些易混淆的方法名:Activated对应客户端激活;Wellknown对应服务器激活;ServiceType对应服务器端;ClientType对应客户端。
| 注释:当调用GetObject()或CreateInstance()时,你无须在客户端注册远程对象类型(通过调用Remoting-Configuration.RegisterXXXXClientType()方法)。然而,如果你希望用关键字new实例化远程对象,方法有二:创建XXXXClientTypeEntry对象并用RemotingConfiguration.RegisterXXXXClientType()注册它;或者在配置文件中指明这个远程对象。 |
我们已经讨论了.NET反射和.NET remoting技术,从而了解了建立SAF.ClassFactory服务需要的所有技术。本章其余部分将具体讨论SAF.ClassFactory,分析它的代码,并讨论它用到的工厂设计模式(factory design pattern)。





