RSS

导航







快速搜索

高级搜索 »

注意:此页面是Spring.NET框架 1.1 参考文档的一部分。


编辑

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示例程序

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参考资源相当丰富:
顶部

.NET 藏经阁 | | 版权所有 ©2008 entlib.net.cn