编辑27.1. 简介
本章讨论Spring.NET中Remoting基础框架的用法。相关的类型定义在Spring.Services程序集的 Spring.Services.Remoting命名空间下。在服务端,将.NET对象导出为CAO或SAO的主要策略是使用CaoExporter或 SaoExporter类,在客户端则使用CaoFactoryObject或SaoFactoryObject获取它们的引用。本章的例子假设读者熟悉.NET Remoting的基本概念。如果没有接触过.NET Remoting,可以参考本章末尾列出的资源。
顶部编辑27.2. Remoting示例程序
本例和Spring.NET的其它例子一样,刻意做的非常简单。我们会在例子中定义一个能够远程访问的简单计算器类,并将其导出为不同模式的远程对象(CAO,SAO-SingleCall,SAO-Singleton),同时,也将讨论如何向SAO添加AOP通知。
本章的示例程序位于examples\Spring\Spring.Examples.Calculator目录下,其中包含多个项目。
 Remoting示例程序 |
Spring.Calculator.Contract项目包括两个接口,ICalculator接口定义了计算器的基本操作, IAdvancedCalculator接口则在此基础上加入了将计算结果保存至内存的功能。(哈,功能强大,HP-12C可要当心了(按:HP-12C 是惠普出品的高级金融计算器))。Spring.Calculator.Services项目中包含这两个接口的实现类,Calculator和 AdvancedCalculator。AdvancedCalculator类的目的是为了向读者说明如何设置SAO-Singleton对象的状态。注意这些实现类都没有继承MarshalByRefObject。Spring.Calculator.ClientApp项目是客户端应用程序。 Spring.Calculator.RemoteApp则是一个控制台应用程序,作为AdvanceCalculator远程对象的宿主程序。 Spring.Calculator.Aspects项目定义了一些日志通知,用来说明如何向远程SAO应用AOP通知。 Spring.Calculator.RegisterComonentServices和Spring.Calculator.Web分别用于导出企业服务和Web服务,与本章内容无关。下面是ICalculator接口的代码:
public interface ICalculator
{
int Add(int n1, int n2);
int Substract(int n1, int n2);
DivisionResult Divide(int n1, int n2);
int Multiply(int n1, int n2);
}
[Serializable]
public class DivisionResult
{
private int _quotient = 0;
private int _rest = 0;
public int Quotient
{
get { return _quotient; }
set { _quotient = value; }
}
public int Rest
{
get { return _rest; }
set { _rest = value; }
}
}
下面是IAdvancedCalculator接口的代码,它扩展了ICalculator接口,加入了保存计算结果的功能:
public interface IAdvancedCalculator : ICalculator
{
int GetMemory();
void SetMemory(int memoryValue);
void MemoryClear();
void MemoryAdd(int num);
}
在这个解决方案中,我们用接口在.NET Remoting客户端和服务器间共享信息。这样做的优点是客户端不需要引用包含实现类的程序集。不让客户端引用实现类的原因有很多。一个是为安全性考虑,因为IL代码很容易被反编译,所以客户端就有可能获取实现的源代码。更重要的是,客户端和服务端是解耦的,服务端可以更换接口的实现而不致对客户端造成影响,即客户端不需要修改代码。另外,抛开.NET Remoting不谈,用接口来发布服务合同也是很好的OO设计。客户端完全可以采用一个跟Remoting无关的实现,比如实现了同一接口的本地类、测试类或Web服务等。使用Spring.NET最大的好处,就是能将“基于接口编程”的开销降为最低。这样,.NET Remoting所提倡的最佳编程方式正好和Spring.NET建议的开发方式吻合。OK,现在已经不存在OO设计上的障碍了,我们来进一步看看如何实现。
顶部
编辑27.3. 实现
Spring.Calculator.Services项目中的Calculator类都相当简单。惟一值得一提的是其中处理内存存储的方法,我们用构造器参数注入来显式配置内存存储的状态。实现类的部分代码如下所示:
public class Calculator : ICalculator
{
public int Add(int n1, int n2)
{
return n1 + n2;
}
public int Substract(int n1, int n2)
{
return n1 - n2;
}
public DivisionResult Divide(int n1, int n2)
{
DivisionResult result = new DivisionResult();
result.Quotient = n1 / n2;
result.Rest = n1 % n2;
return result;
}
public int Multiply(int n1, int n2)
{
return n1 * n2;
}
}
public class AdvancedCalculator : Calculator, IAdvancedCalculator
{
private int memoryStore = 0;
public AdvancedCalculator()
{}
public AdvancedCalculator(int initalMemory)
{
memoryStore = initalMemory;
}
public int GetMemory()
{
return memoryStore;
}
// other methods omitted in this listing...
}
Spring.Calculator.RemotedApp项目用一个控制台应用程序作为远程对象的宿主。代码也是非常简单的,如下所示:
public static void Main(string[] args)
{
try
{
// initialization of Spring.NET's IoC container
IApplicationContext ctx = ContextRegistry.GetContext();
Console.Out.WriteLine("Server listening...");
}
catch (Exception e)
{
Console.Out.WriteLine(e);
}
finally
{
Console.Out.WriteLine("--- Press <return> to quit ---");
Console.ReadLine();
}
}
.NET Remoting的通道在配置文件(App.config)中用标准的system.runtime.remoting节点配置。在本例中,我们使用tcp通道,8005端口:
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="8005" />
</channels>
</application>
</system.runtime.remoting>
Spring.NET的应用程序上下文配置如下。其中引用的各个资源文件将对象发布为不同的远程对象。本例使用的AOP通知是一个简单的、使用log4net记录日志的环绕通知。
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="parsers" type="Spring.Context.Support.ConfigParsersSectionHandler, Spring.Core" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<spring>
<parsers>
<parser namespace="http://www.springframework.net/remoting"
type="Spring.Remoting.RemotingConfigParser, Spring.Services"
schemaLocation="assembly://Spring.Services/Spring.Remoting/spring-remoting.xsd" />
</parsers>
<context>
<resource uri="config://spring/objects" />
<resource uri="assembly://RemoteServer/RemoteServer.Config/cao.xml" />
<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall.xml" />
<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall-aop.xml" />
<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton.xml" />
<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton-aop.xml" />
</context>
<objects xmlns="http://www.springframework.net">
<description>Definitions of objects to be exported.</description>
<object type="Spring.Remoting.RemotingConfigurer, Spring.Services">
<property name="Filename" value="Spring.Calculator.RemoteApp.exe.config" />
</object>
<object id="Log4NetLoggingAroundAdvice"
type="Spring.Aspects.Logging.Log4NetLoggingAroundAdvice, Spring.Aspects">
<property name="Level" value="Debug" />
</object>
<object id="singletonCalculator"
type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services">
<constructor-arg type="int" value="217"/>
</object>
<object id="singletonCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
<property name="target" ref="singletonCalculator"/>
<property name="interceptorNames">
<list>
<value>Log4NetLoggingAroundAdvice</value>
</list>
</property>
</object>
<object id="prototypeCalculator"
type="Spring.Calculator.Services.AdvancedCalculator,Spring.Calculator.Services" singleton="false">
<constructor-arg type="int" value="217"/>
</object>
<object id="prototypeCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
<property name="targetSource">
<object type="Spring.Aop.Target.PrototypeTargetSource, Spring.Aop">
<property name="TargetObjectName" value="prototypeCalculator"/>
</object>
</property>
<property name="interceptorNames">
<list>
<value>Log4NetLoggingAroundAdvice</value>
</list>
</property>
</object>
</objects>
</spring>
其中计算器类的对象定义(比如说singletonCalculator)和普通对象定义的配置方法一样。在将这些对象发布为远程对象的时候,可以用Spring.Remoting.CaoExporter将其发布为CAO,或用Spring.Remoting.SaoExporter将其发布为 SAO。这两个导出类的对象定义都需要用TargetName属性引用目标对象的名称。如果要导出SingleCall模式的SAO或CAO,要将目标对象定义为“prototype“(即singleton=false)。SaoExporter类的ServiceName属性值是远程对象的名称,会包含在远程对象的URL中供客户端访问。另外,如果需要将远程对象的租赁期设为无限,可以将Infinite的属性值设为true。
导出Singleton模式SAO的配置如下:
<objects
xmlns="http://www.springframework.net"
xmlns:r="http://www.springframework.net/remoting">
<description>Registers the calculator service as a SAO in 'Singleton' mode.</description>
<r:saoExporter
targetName="singletonCalculator"
serviceName="RemotedSaoSingletonCalculator" />
</objects>
这是用Spring.NET的Remoting Schema配置的,当然也可以用标准的对象定义,如下:
<object name="saoSingletonCalculator" type="Spring.Remoting.SaoExporter, Spring.Services">
<property name="TargetName" value="singletonCalculator" />
<property name="ServiceName" value="RemotedSaoSingletonCalculator" />
</object>
这样就可以发布一个URL为tcp://localhost:8005/RemotedSaoSingletonCalculator的远程对象。使用SaoExporter和CaoExporter发布其它类型远程对象的方法与此类似,可以参考RemoteServer项目的配置文件。
客户端应用程序会连接到远程计算器服务,请求它的当前内存值,该值被预设为217,然后执行一个简单的加法。在服务端,已经用.NET Remoting的标准配置将通道配置好了,如下:
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp"/>
</channels>
</application>
</system.runtime.remoting>
客户端实现的代码如下:
public static void Main(string[] args)
{
try
{
Pause();
IApplicationContext ctx = ContextRegistry.GetContext();
Console.Out.WriteLine("Get Calculator...");
IAdvancedCalculator firstCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService");
Console.WriteLine("Divide(11, 2) : " + firstCalc.Divide(11, 2));
Console.Out.WriteLine("Memory = " + firstCalc.GetMemory());
firstCalc.MemoryAdd(2);
Console.Out.WriteLine("Memory + 2 = " + firstCalc.GetMemory());
Console.Out.WriteLine("Get Calculator...");
IAdvancedCalculator secondCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService");
Console.Out.WriteLine("Memory = " + secondCalc.GetMemory());
}
catch (Exception e)
{
Console.Out.WriteLine(e);
}
finally
{
Pause();
}
}
注意,客户端代码并不知道自己使用的是一个远程对象。其中的pause()方法只是让客户端程序等待用户按下回车键再开始运行,以免在服务端初始化之前就请求远程对象。.NET Remoting基础框架要在Spring.NET的IoC容器之前初始化。在客户端的配置中,可以很方便的更换从容器获取的 calculatorService引用。在复杂点的应用中,计算器服务可能会依赖程序中的其它对象,比如说工作流程处理层中的对象。下面的代码是客户端使用本地实现和远程实现的配置。同一个计算器对象还可以发布为web服务或服务组件(企业服务),本章不讨论这些内容。
<spring>
<context>
<resource uri="config://spring/objects" />
<!-- Only one at a time ! -->
<!-- ================================== -->
<!-- In process (local) implementations -->
<!-- ================================== -->
<resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.InProcess/inProcess.xml" />
<!-- ======================== -->
<!-- Remoting implementations -->
<!-- ======================== -->
<!-- Make sure 'RemoteApp' console application is running and listening. -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao.xml" /> -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao-ctor.xml" /> -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton.xml" /> -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton-aop.xml" /> -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall.xml" /> -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall-aop.xml" /> -->
<!-- =========================== -->
<!-- Web Service implementations -->
<!-- =========================== -->
<!-- Make sure 'http://localhost/SpringCalculator/' web application is running -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices.xml" /> -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices-aop.xml" /> -->
<!-- ================================= -->
<!-- EnterpriseService implementations -->
<!-- ================================= -->
<!-- Make sure you register components with 'RegisterComponentServices' console application. -->
<!-- <resource
uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.EnterpriseServices/enterpriseServices.xml" /> -->
</context>
</spring>
inProcess.xml配置文件直接定义了一个AdvancedCalculator对象:
<objects xmlns="http://www.springframework.net">
<description>inProcess</description>
<object id="calculatorService"
type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" />
</objects>
在定义远程对象的客户端引用时,我们使用了工厂对象。在引用SAO时,使用SaoFactoryObject类;引用CAO对象则使用CaoFactoryObject类。下面的配置用来获取前面导出的Singleton-SAO的客户端引用:
<objects xmlns="http://www.springframework.net">
<description>saoSingleton</description>
<object id="calculatorService" type="Spring.Remoting.SaoFactoryObject, Spring.Services">
<property name="ServiceInterface"
value="Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract" />
<property name="ServiceUrl" value="tcp://localhost:8005/RemotedSaoSingletonCalculator" />
</object>
</objects>
TODO: Show use of custom .net remoting schema.
在SaoFactoryObject的定义中,必须指定ServiceInterface和ServiceUrl属性。在此可以利用 Spring.NET的属性值替换功能(按:请参考第四章),利用环境变量、标准.NET配置节点或外部配置文件来简化URL的配置。这样也很容易为测试、QA和产品等不同阶段切换不同的环境。比如说,可以用下面这种方式来定义ServiceUrl的值...
<property name="ServiceUrl" value="${protocol}://${host}:${port}/RemotedSaoSingletonCalculator" />
其中的具体值是在其它地方定义的,关于这方面的内容可以参考4.9.1,PropertyPlaceholderConfigurer。前面提到过,使用配置文件的灵活性就在于,仅修改配置文件就可以更换实现了同一接口的不同的对象(远程或本地的)。
在客户端,可用以下配置获取前面发布的CAO的引用:
<objects xmlns="http://www.springframework.net">
<description>cao</description>
<object id="calculatorService" type="Spring.Remoting.CaoFactoryObject, Spring.Services">
<property name="RemoteTargetName" value="prototypeCalculator" />
<property name="ServiceUrl" value="tcp://localhost:8005" />
</object>
</objects>
顶部
编辑27.4. 运行程序
我们已经把示例程序的完整实现看了一遍,现在可以运行它了。注意可以设置VS.NET让它同时运行两个程序,如下:
 运行程序 |
程序运行以后,服务端的输出如下... TO BE DONE...
顶部
编辑27.5. Remoting Schema
Doc目录下的spring-remoting.xsd文件可允许开发人员用较简短的语法配置Remoting的对象定义。可以用Doc目录下的NAnt脚本“install-shcema”将该文件安装到VS.NET环境中去。具体步骤可以参考第二十四章。
RemoteApp和ClientApp项目下的配置文件都使用了Remoting schema来定义远程对象。下面的代码节选自配置文件,您会发现使用这种Schema定义远程对象感觉还是不错的。
{{{{
<!-- Calculator definitions -->
<object id="singletonCalculator"
type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services">
<constructor-arg type="int" value="217" />
</object>
<object id="prototypeCalculator"
type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" singleton="false">
<constructor-arg type="int" value="217" />
</object>
<!-- CAO object -->
<r:caoExporter targetName="prototypeCalculator" infinite="false">
<r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m"/>
</r:caoExporter>
<!-- SAO Single Call -->
<r:saoExporter
targetName="prototypeCalculator"
serviceName="RemotedSaoSingleCallCalculator"/>
<!-- SAO Singleton -->
<r:saoExporter
targetName="singletonCalculator"
serviceName="RemotedSaoSingletonCalculator" />
注意,远程对象的Singleton模式是由目标对象定义决定的。上例中,因为“prototypeCalculator”的singleton属性为false,所以用它发布的SAO对象就是一个Single-Call的SAO,每次调用远程对象的方法时,都会创建新的对象。
TODO: Use Document X! to document the schema.
顶部编辑27.6. 参考资源
.NET Remoting是一个很庞大的课题。MSDN上可以找到些介绍性的文章。在这方面,Ingo Rammer是一个权威,由其维护的Remoting参考资源相当丰富:
顶部