Files
spring-net/doc/reference/src/remoting.xml
sbohlen 9788fd3580 SPRNET-1407
Updated copyright notices in docs.
2010-12-11 20:35:49 +00:00

487 lines
25 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!--
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<chapter xml:id="remoting" xmlns="http://docbook.org/ns/docbook" version="5">
<title>.NET Remoting</title>
<section xml:id="remoting-introduction">
<title>Introduction</title>
<para>Spring's .NET Remoting support allows you to export a 'plain CLR
object' as a .NET Remoted object. By "plain CLR object" we mean classes
that do not inherit from a specific infrastructure base class such as
MarshalByRefObject. On the server side, Spring's .NET Remoting exporters
will automatically create a proxy that implements MarshalByRefObject. You
register SAO types as either SingleCall or Singleton and also configure on
a per-object basis lifetime and leasing parameters. On the client side you
can obtain CAO references to server proxy objects in a manner that
promotes interface based design best practices when developing .NET
remoting applications. The current implementation requires that your plain
.NET objects implements a business service interface. Additionally you can
add AOP advice to both SAO and CAO objects.</para>
<para>You can leverage the IoC container to configure the exporter and
service endpoints. A remoting specific xml-schema is provided to simplify
the remoting configuration but you can still use the standard
reflection-like property based configuration schema. You may also opt to
not use the IoC container to configure the objects and use Spring's .NET
Remoting classes Programatically, as you would with any third party
library.</para>
<para>A sample application, often referred to in this documentation, is in
the distribution under the directory "examples\Spring\Spring.Calculator"
and may also be found via the start menu by selecting the 'Calculator'
item.</para>
</section>
<section xml:id="remoting-publishsao">
<title>Publishing SAOs on the Server</title>
<para>Exposing a Singleton SAO service can be done in two ways. The first
is through programmatic or administrative type registration that makes
calls to
<literal>RemotingConfiguration.RegisterWellKnownServiceType</literal>.
This method has the limitation that you must use a default constructor and
you can not easily configure the singleton state at runtime since it is
created on demand. The second way is to publish an object instance using
<literal>RemotingServices.Marshal</literal>. This method overcomes the
limitations of the first method. Example server side code for publishing
an SAO singleton object with a predefined state is shown below
<programlisting language="csharp">AdvancedMBRCalculator calc = new AdvancedMBRCalculator(217);
RemotingServices.Marshal(calc, "MyRemotedCalculator");</programlisting></para>
<para>The class AdvancedMBRCalculator used above inherits from
MarshalByRefObject.</para>
<para>If your design calls for configuring a singleton SAO, or using a
non-default constructor, you can use the Spring IoC container to create
the SAO instance, configure it, and register it with the .NET remoting
infrastructure. The <literal>SaoExporter</literal> class performs this
task and most importantly, will automatically create a proxy class that
inherits from MarshalbyRefObject if your business object does not already
do so. The following XML taken from the <link
linkend="remoting-quickstart">Remoting QuickStart</link> demonstrates its
usage to an SAO Singleton object</para>
<section xml:id="sao-singleton">
<title>SAO Singleton</title>
</section>
<programlisting language="myxml">&lt;object id="singletonCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"&gt;
&lt;constructor-arg type="int" value="217"/&gt;
&lt;/object&gt;
&lt;!-- Registers the calculator service as a SAO in 'Singleton' mode. --&gt;
&lt;object name="saoSingletonCalculator" type="Spring.Remoting.SaoExporter, Spring.Services"&gt;
&lt;property name="TargetName" value="singletonCalculator" /&gt;
&lt;property name="ServiceName" value="RemotedSaoSingletonCalculator" /&gt;
&lt;/object&gt;</programlisting>
<para>This XML fragment shows how an existing object "singletonCalculator"
defined in the Spring context is exposed under the url-path name
"RemotedSaoSingletonCalculator". (The fully qualified url is
tcp://localhost:8005/RemotedSaoSingleCallCalculator using the standard
.NET channel configuration shown further below.)
<literal>AdvancedCalculator</literal> class implements the business
interface <literal>IAdvancedCalculator</literal>. The current proxy
implementation requires that your business objects implement an interface.
The interfaces' methods will be the ones exposed in the generated .NET
remoting proxy. The initial memory of the calculator is set to 217 via the
constructor. The class <literal>AdvancedCalculator</literal>
<emphasis>does not</emphasis> inherit from
<literal>MarshalByRefObject</literal>. Also note that the exporter
sets the lifetime of the SAO Singleton to infinite so that the singleton
will not be garbage collected after 5 minutes (the .NET default lease
time). If you would like to vary the lifetime properties, they are
InitialLeaseTime, RenewOnCallTime, and SponsorshipTimeout.</para>
<para>A custom schema is provided to make the object declaration even
easier and with intellisense support for the attributes. This is shown
below<programlisting language="myxml">&lt;objects xmlns="http://www.springframework.net"
xmlns:r="http://www.springframework.net/remoting"&gt;
&lt;r:saoExporter targetName="singletonCalculator"
serviceName="RemotedSaoSingletonCalculator" /&gt;
... other object definitions
&lt;/objects&gt;</programlisting>Refer to the end of this chapter for more
information on Spring's .NET custom schema.</para>
<section xml:id="sao-singlecall">
<title>SAO SingleCall</title>
</section>
<para>The following XML fragment shows how to expose the calculator
service in SAO 'SingleCall' mode. <programlisting language="myxml">&lt;object id="prototypeCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"
singleton="false"&gt;
&lt;constructor-arg type="int" value="217"/&gt;
&lt;/object&gt;
&lt;object name="saoSingleCallCalculator" type="Spring.Remoting.SaoExporter, Spring.Services"&gt;
&lt;property name="TargetName" value="prototypeCalculator" /&gt;
&lt;property name="ServiceName" value="RemotedSaoSingleCallCalculator" /&gt;
&lt;/object&gt;</programlisting></para>
<para>Note that we change the singleton attribute of the plain CLR object
as configured by Spring in the &lt;object&gt; definition and not an
attribute on the SaoExporter. The object referred to in the
<literal>TargetName</literal> parameter can be an AOP proxy to a business
object. For example, if we were to apply some simple logging advice to the
singleton calculator, the following standard AOP configuration is used to
create the target for the SaoExporter</para>
<programlisting language="myxml">&lt;object id="singletonCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"&gt;
&lt;property name="target" ref="singletonCalculator"/&gt;
&lt;property name="interceptorNames"&gt;
&lt;list&gt;
&lt;value&gt;Log4NetLoggingAroundAdvice&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/object&gt;
&lt;object name="saoSingletonCalculatorWeaved" type="Spring.Remoting.SaoExporter, Spring.Services"&gt;
&lt;property name="TargetName" value="singletonCalculatorWeaved" /&gt;
&lt;property name="ServiceName" value="RemotedSaoSingletonCalculatorWeaved" /&gt;
&lt;/object&gt;</programlisting>
<note>As generally required with a .NET Remoting application, the
arguments to your service methods should be Serializable.</note>
<sect2 xml:id="remoting-configuration">
<title>Console Application Configuration</title>
<para>When using <literal>SaoExporter</literal> you can still use
the standard remoting administration section in the application
configuration file to register the channel.
<literal>ChannelServices</literal> as shown below</para>
<programlisting language="myxml">&lt;system.runtime.remoting&gt;
&lt;application&gt;
&lt;channels&gt;
&lt;channel ref="tcp" port="8005" /&gt;
&lt;/channels&gt;
&lt;/application&gt;
&lt;/system.runtime.remoting&gt;</programlisting>
<para>A console application that will host this Remoted object needs to
initialize the .NET Remoting infrastructure with a call to
RemotingConfiguration (since we are using the .config file for channel
registration) and then start the Spring application context. This is
shown below <programlisting language="csharp">RemotingConfiguration.Configure("RemoteApp.exe.config");
IApplicationContext ctx = ContextRegistry.GetContext();
Console.Out.WriteLine("Server listening...");
Console.ReadLine();
</programlisting></para>
<para>You can also put in the configuration file an instance of the
object <literal>Spring.Remoting.RemotingConfigurer</literal> to make
the RemotingConfiguration call show above on your behalf during
initialization of the IoC container. The
<literal>RemotingConfigurer</literal> implements the
<literal>IObjectFactoryPostProcessor</literal> interface,
which gets called after all object definitions have been loaded but
before they have been instantiated, (See<xref
linkend="objects-factory-customizing-factory-postprocessors" /> for more
information). The RemotingConfigurer has two properties you can
configure. <literal>Filename</literal>, that specifies the filename
to load the .NET remoting configuration from (if null the default file
name is used) and <literal>EnsureSecurity</literal> which makes sure
the channel in encrypted (available only on .NET 2.0). As a convenience,
the custom Spring remoting schema can be used to define an instance of
this class as shown below, taken from the <link
linkend="remoting-quickstart">Remoting QuickStart</link>
<programlisting language="myxml">&lt;objects xmlns="http://www.springframework.net"
xmlns:r="http://www.springframework.net/remoting"&gt;
&lt;r:configurer filename="Spring.Calculator.RemoteApp.exe.config" /&gt;
&lt;/objects&gt;</programlisting></para>
<para>The ReadLine prevents the console application from exiting. You
can refer to the code in RemoteApp in the <link
linkend="remoting-quickstart">Remoting QuickStart</link> to see this
code in action.</para>
</sect2>
<section xml:id="iis-application">
<title>IIS Application Configuration</title>
<para>If you are deploying a .NET remoting application inside IIS there
is a <ulink
url="http://forum.springframework.net/showthread.php?t=469">sample
project </ulink> that demonstrates the necessary configuration using
Spring.Web.</para>
<para>Spring.Web ensures the application context is initialized, but if
you don't use Spring.Web the idea is to start the initialization of the
Spring IoC container inside the application start method defined in
Global.asax, as shown below</para>
<programlisting language="csharp"> void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
// Ensure Spring has loaded configuration registering context
Spring.Context.IApplicationContext ctx = new Spring.Context.Support.XmlApplicationContext(
HttpContext.Current.Server.MapPath("Spring.Config"));
Spring.Context.Support.ContextRegistry.RegisterContext(ctx);
}</programlisting>
<para>In this example, the Spring configuration file is named
Spring.Config. Inside Web.config you add a standard
&lt;system.runtime.remoting&gt; section. Note that you do not need to
specify the port number of your channels as they will use the port
number of your web site. Ambiguous results have been reported if you do
specify the port number. Also, in order for IIS to recognize the
remoting request, you should add the suffix '.rem' or '.soap' to the
target name of your exported remote object so that the correct IIS
handler can be invoked.</para>
</section>
</section>
<section xml:id="remoting-clientsao">
<title>Accessing a SAO on the Client</title>
<para>Administrative type registration on the client side lets you easily
obtain a reference to a SAO object. When a type is registered on the
client, using the new operator or using the reflection API will return a
proxy to the remote object instead of a local reference. Administrative
type registration on the client for a SAO object is performed using the
<literal>wellknown</literal> element in the client configuration section.
However, this approach requires that you expose the implementation of the
class on the client side. Practically speaking this would mean linking in
the server assembly to the client application, a generally recognized bad
practice. This dependency can be removed by developing remote services
based on a business interface. Aside from remoting considerations, the
separation of interface and implementation is considered a good practice
when designing OO systems. In the context of remoting, this means that the
client can obtain a proxy to a specific implementation with
<emphasis>only</emphasis> a reference to the interface assembly. To
achieve the decoupling of client and server, a separate assembly
containing the interface definitions is created and shared between the
client and server applications.</para>
<para>There is a simple means for following this design when the remote
object is a SAO object. A call to <literal>Activator.GetObject</literal>
will instantiate a SAO proxy on the client. For CAO objects another
mechanism is used and is discussed later. The code to obtain the SAO proxy
is shown below <programlisting language="csharp">ICalculator calc = (ICalculator)Activator.GetObject (
typeof (ICalculator),
"tcp://localhost:8005/MyRemotedCalculator");</programlisting></para>
<para>To obtain a reference to a SAO proxy within the IoC container, you
can use the object factory <literal>SaoFactoryObject</literal> in the
Spring configuration file. The following XML taken from the <link
linkend="remoting-quickstart"> Remoting QuickStart</link> demonstrates its
usage.</para>
<programlisting language="myxml">&lt;object id="calculatorService" type="Spring.Remoting.SaoFactoryObject, Spring.Services"&gt;
&lt;property name="ServiceInterface" value="Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract" /&gt;
&lt;property name="ServiceUrl" value="tcp://localhost:8005/RemotedSaoSingletonCalculator" /&gt;
&lt;/object&gt;</programlisting>
<para>The ServiceInterface property specifies the type of proxy to create
while the ServiceUrl property creates a proxy bound to the specified
server and published object name.</para>
<para>Other objects in the IoC container that depend on an implementation
of the interface <literal>ICalculator</literal> can now refer to the
object "calculatorService", thereby using a remote implementation of this
interface. The exposure of dependencies among objects within the IoC
container lets you easily switch the implementation of
<literal>ICalculator</literal>. By using the IoC container changing
the application to use a local instead of remote implementation is a
configuration file change, not a code change. By promoting interface based
programing, the ability to switch implementation makes it easier to unit
test the client application, since unit testing can be done with a mock
implementation of the interface. Similarly, development of the client can
proceed independent of the server implementation. This increases
productivity when there are separate client and server development teams.
The two teams agree on interfaces before starting development. The client
team can quickly create a simple, but functional implementation and then
integrate with the server implementation when it is ready.</para>
</section>
<section xml:id="remoting-cao-introduction">
<title>CAO best practices</title>
<para>Creating a client activated object (CAO) is typically done by
administrative type registration, either Programatically or via the
standard .NET remoting configuration section. The registration process
allows you to use the 'new' operator to create the remote object and
requires that the implementation of the object be distributed to the
client. As mentioned before, this is not a desirable approach to
developing distributed systems. The best practice approach that avoids
this problem is to create an SAO based factory class on the server that
will return CAO references to the client. In a manner similar to how
Spring's generic object factory can be used as a replacement creating a
factory per class, we can create a generic SAO object factory to return
CAO references to objects defined in Spring's application context. This
functionality is encapsulated in Spring's
<literal>CaoExporter</literal> class. On the client side a reference
is obtained using <literal>CaoFactoryObject</literal>. The client side
factory object supports creation of the CAO object using constructor
arguments. In addition to reducing the clutter and tedium around creating
factory classes specific to each object type you wish to expose in this
manner, this approach has the additional benefit of not requiring any type
registration on the client or server side. This is because the act of
returning an instance of a class that inherits from MarshalByRefObject
across a remoting boundary automatically returns a CAO object reference.
For more information on this best-practice, refer to the last section,
<xref linkend="remoting-additional" />, for some links to additional
resources.</para>
</section>
<section xml:id="remoting-publishcao">
<title>Registering a CAO object on the Server</title>
<para>To expose an object as a CAO on the server you should declare an
object in the standard Spring configuration that is a 'prototype', that is
the singleton property is set to false. This results in a new object being
created each time it is retrieved from Spring's IoC container. An
implementation of <literal>ICaoRemoteFactory</literal> is what
is exported via a call to RemotingServices.Marshal. This implementation
uses Spring's IoC container to create objects and then dynamically create
a .NET remoting proxy for the retrieved object. Note that the default
lifetime of the remote object is set to infinite (null is returned from
the implementation of InitializeLifetimeService()).</para>
<para>This is best shown using an example from the Remoting Quickstart
application. Here is the definition of a simple calculator object,</para>
<para><programlisting language="myxml">&lt;object id="prototypeCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"
singleton="false"&gt;
&lt;constructor-arg type="int" value="217" /&gt;
&lt;/object&gt;</programlisting>To export this as a CAO object we can declare
the <literal>CaoExporter</literal> object directly in the server's XML
configuration file, as shown below</para>
<programlisting language="myxml">&lt;object id="caoCalculator" type="Spring.Remoting.CaoExporter, Spring.Services"&gt;
&lt;property name="TargetName" value="prototypeCalculator" /&gt;
&lt;property name="Infinite" value="false" /&gt;
&lt;property name="InitialLeaseTime" value="2m" /&gt;
&lt;property name="RenewOnCallTime" value="1m" /&gt;
&lt;/object&gt;</programlisting>
<para>Note the property 'TargetName' is set to the name, not the
reference, of the non-singleton declaration of the 'AdvancedCalculator'
class.</para>
<para>Alternatively, you can use the remoting schema and declare the CAO
object as shown below</para>
<programlisting language="myxml">&lt;r:caoExporter targetName="prototypeCalculator" infinite="false"&gt;
&lt;r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m" /&gt;
&lt;/r:caoExporter&gt;</programlisting>
<para></para>
<section>
<title>Applying AOP advice to exported CAO objects</title>
<para>Applying AOP advice to exported CAO objects is done by referencing
the adviced object name to the CAO exporter. Again, taking an example
from the Remoting QuickStart, a calculator with logging around advice is
defined as shown below.</para>
<programlisting language="myxml">&lt;object id="prototypeCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"&gt;
&lt;property name="targetSource"&gt;
&lt;object type="Spring.Aop.Target.PrototypeTargetSource, Spring.Aop"&gt;
&lt;property name="TargetObjectName" value="prototypeCalculator" /&gt;
&lt;/object&gt;
&lt;/property&gt;
&lt;property name="interceptorNames"&gt;
&lt;list&gt;
&lt;value&gt;ConsoleLoggingAroundAdvice&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/object&gt;</programlisting>
<para>If this declaration is unfamiliar to you, please refer to <xref
linkend="aop" /> for more information. The CAO exporter then references
with the name 'prototypeCalculatorWeaved' as shown below.</para>
<programlisting language="myxml">&lt;r:caoExporter targetName="prototypeCalculatorWeaved" infinite="false"&gt;
&lt;r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m" /&gt;
&lt;/r:caoExporter&gt;</programlisting>
</section>
</section>
<section xml:id="remoting-clientcao">
<title>Accessing a CAO on the Client</title>
<para>On the client side a CAO reference is obtained by using the
<literal>CaoFactoryObject</literal> as shown below</para>
<programlisting language="myxml">&lt;object id="calculatorService" type="Spring.Remoting.CaoFactoryObject, Spring.Services"&gt;
&lt;property name="RemoteTargetName" value="prototypeCalculator" /&gt;
&lt;property name="ServiceUrl" value="tcp://localhost:8005" /&gt;
&lt;/object&gt;</programlisting>
<para>This definition corresponds to the exported calculator from the
previous section. The property 'RemoteTargetName' identifies the object on
the server side. Using this approach the client can obtain an reference
though standard DI techniques to a remote object that implements the
<literal>IAdvancedCalculator</literal> interface. (As always,
that doesn't mean the client should treat the object as if it was an
in-process object).</para>
<para>Alternatively, you can use the Remoting schema to shorten this
definition and provide intellisense code completion</para>
<programlisting language="myxml">&lt;r:caoFactory id="calculatorService"
remoteTargetName="prototypeCalculator"
serviceUrl="tcp://localhost:8005" /&gt;</programlisting>
<section>
<title>Applying AOP advice to client side CAO objects.</title>
<para>Applying AOP advice to a client side CAO object is done just like
any other object. Simply use the id of the object created by the
<literal>CaoFactoryObject</literal> as the AOP target, i.e.
'calculatorService' in the previous example.</para>
</section>
</section>
<section xml:id="remoting-schema">
<title>XML Schema for configuration</title>
<para>Please install the XSD schemas into VS.NET as described in <xref
linkend="vsnet" />. XML intellisense for the attributes of the
saoExporter, caoExporter and caoFactory should be self explanatory as they
mimic the standard property names used to configure .NET remote
objects.</para>
</section>
<section xml:id="remoting-additional">
<title>Additional Resources</title>
<para>Two articles that describe the process of creating a standard SAO
factory for returning CAO objects are <ulink
url="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpatterns/html/ImpBrokerClient.asp">Implementing
Broker with .NET Remoting Using Client-Activated Objects</ulink> on MSDN
and <ulink
url="http://www.glacialcomponents.com/ArticleDetail/CAOGuide.aspx">Step by
Step guide to CAO creation through SAO class factories</ulink> on Glacial
Components website.</para>
</section>
</chapter>