You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1006 lines
55 KiB
1006 lines
55 KiB
<?xml version="1.0" encoding="UTF-8"?> |
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" |
|
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> |
|
|
|
<chapter id="remoting"> |
|
<title>Remoting and web services using Spring</title> |
|
|
|
<section id="remoting-introduction"> |
|
<title>Introduction</title> |
|
<para>Spring features integration classes for remoting support using various |
|
technologies. The remoting support eases the development of remote-enabled |
|
services, implemented by your usual (Spring) POJOs. Currently, Spring supports |
|
four remoting technologies: |
|
<itemizedlist> |
|
<listitem> |
|
<para><emphasis>Remote Method Invocation (RMI)</emphasis>. Through the use |
|
of the <classname>RmiProxyFactoryBean</classname> and the |
|
<classname>RmiServiceExporter</classname> Spring supports both traditional |
|
RMI (with <interfacename>java.rmi.Remote</interfacename> interfaces and |
|
<exceptionname>java.rmi.RemoteException</exceptionname>) and |
|
transparent remoting via RMI invokers (with any Java interface).</para> |
|
</listitem> |
|
<listitem> |
|
<para><emphasis>Spring's HTTP invoker</emphasis>. Spring provides a special |
|
remoting strategy which allows for Java serialization via HTTP, |
|
supporting any Java interface (just like the RMI invoker). The corresponding |
|
support classes are <classname>HttpInvokerProxyFactoryBean</classname> and |
|
<classname>HttpInvokerServiceExporter</classname>.</para> |
|
</listitem> |
|
<listitem> |
|
<para><emphasis>Hessian</emphasis>. By using Spring's |
|
<classname>HessianProxyFactoryBean</classname> and the |
|
<classname>HessianServiceExporter</classname> you can transparently |
|
expose your services using the lightweight binary HTTP-based protocol |
|
provided by Caucho.</para> |
|
</listitem> |
|
<listitem> |
|
<para><emphasis>Burlap</emphasis>. Burlap is Caucho's XML-based |
|
alternative to Hessian. Spring provides support classes such |
|
as <classname>BurlapProxyFactoryBean</classname> and |
|
<classname>BurlapServiceExporter</classname>.</para> |
|
</listitem> |
|
<listitem> |
|
<para><emphasis>JAX-RPC</emphasis>. Spring provides remoting support |
|
for web services via JAX-RPC (J2EE 1.4's web service API).</para> |
|
</listitem> |
|
<listitem> |
|
<para><emphasis>JAX-WS</emphasis>. Spring provides remoting support |
|
for web services via JAX-WS (the successor of JAX-RPC, as introduced |
|
in Java EE 5 and Java 6).</para> |
|
</listitem> |
|
<listitem> |
|
<para><emphasis>JMS</emphasis>. Remoting using JMS as the underlying protocol |
|
is supported via the <classname>JmsInvokerServiceExporter</classname> and |
|
<classname>JmsInvokerProxyFactoryBean</classname> classes.</para> |
|
</listitem> |
|
</itemizedlist></para> |
|
<para>While discussing the remoting capabilities of Spring, we'll use the following domain |
|
model and corresponding services:</para> |
|
<programlisting language="java"><![CDATA[public class Account implements Serializable{ |
|
|
|
private String name; |
|
|
|
public String getName(); |
|
|
|
public void setName(String name) { |
|
this.name = name; |
|
} |
|
}]]></programlisting> |
|
<programlisting language="java"><![CDATA[public interface AccountService { |
|
|
|
public void insertAccount(Account account); |
|
|
|
public List getAccounts(String name); |
|
}]]></programlisting> |
|
<programlisting language="java"><![CDATA[public interface RemoteAccountService extends Remote { |
|
|
|
public void insertAccount(Account account) throws RemoteException; |
|
|
|
public List getAccounts(String name) throws RemoteException; |
|
}]]></programlisting> |
|
<programlisting language="java"><lineannotation>// the implementation doing nothing at the moment</lineannotation><![CDATA[ |
|
public class AccountServiceImpl implements AccountService { |
|
|
|
public void insertAccount(Account acc) { |
|
]]><lineannotation>// do something...</lineannotation><![CDATA[ |
|
} |
|
|
|
public List getAccounts(String name) { |
|
]]><lineannotation>// do something...</lineannotation><![CDATA[ |
|
} |
|
}]]></programlisting> |
|
<para>We will start exposing the service to a remote client by using RMI and |
|
talk a bit about the drawbacks of using RMI. We'll then continue to show |
|
an example using Hessian as the protocol.</para> |
|
</section> |
|
|
|
<section id="remoting-rmi"> |
|
<title>Exposing services using RMI</title> |
|
<para>Using Spring's support for RMI, you can transparently expose your services through |
|
the RMI infrastructure. After having this set up, you basically have a configuration |
|
similar to remote EJBs, except for the fact that there is no standard support for |
|
security context propagation or remote transaction propagation. Spring does provide |
|
hooks for such additional invocation context when using the RMI invoker, so you can |
|
for example plug in security frameworks or custom security credentials here.</para> |
|
|
|
<section id="remoting-rmi-server"> |
|
<title>Exporting the service using the <classname>RmiServiceExporter</classname></title> |
|
<para>Using the <classname>RmiServiceExporter</classname>, we can expose the interface |
|
of our AccountService object as RMI object. The interface can be accessed by using |
|
<classname>RmiProxyFactoryBean</classname>, or via plain RMI in case of a traditional |
|
RMI service. The <classname>RmiServiceExporter</classname> explicitly supports the |
|
exposing of any non-RMI services via RMI invokers. |
|
</para> |
|
<para>Of course, we first have to set up our service in the Spring container:</para> |
|
<programlisting language="xml"><![CDATA[<bean id="accountService" class="example.AccountServiceImpl"> |
|
]]><lineannotation><!-- any additional properties, maybe a DAO? --></lineannotation><![CDATA[ |
|
</bean>]]></programlisting> |
|
<para>Next we'll have to expose our service using the <classname>RmiServiceExporter</classname>:</para> |
|
<programlisting language="xml"><![CDATA[<bean class="org.springframework.remoting.rmi.RmiServiceExporter"> |
|
]]><lineannotation><!-- does not necessarily have to be the same name as the bean to be exported --></lineannotation><![CDATA[ |
|
<property name="serviceName" value="AccountService"/> |
|
<property name="service" ref="accountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
]]><lineannotation><!-- defaults to <literal>1099</literal> --></lineannotation><![CDATA[ |
|
<property name="registryPort" value="1199"/> |
|
</bean>]]></programlisting> |
|
<para>As you can see, we're overriding the port for the RMI registry. Often, |
|
your application server also maintains an RMI registry and it is wise |
|
to not interfere with that one. Furthermore, the service name is used to bind the |
|
service under. So right now, the service will be bound at |
|
<literal>'rmi://HOST:1199/AccountService'</literal>. We'll use the URL later on to |
|
link in the service at the client side.</para> |
|
<note> |
|
<para>The <literal>servicePort</literal> property has been omitted (it defaults to 0). |
|
This means that an anonymous port will be used to communicate with the service.</para> |
|
</note> |
|
</section> |
|
|
|
<section id="remoting-rmi-client"> |
|
<title>Linking in the service at the client</title> |
|
<para>Our client is a simple object using the <interfacename>AccountService</interfacename> |
|
to manage accounts:</para> |
|
<programlisting language="java"><![CDATA[public class SimpleObject { |
|
|
|
private AccountService accountService; |
|
|
|
public void setAccountService(AccountService accountService) { |
|
this.accountService = accountService; |
|
} |
|
}]]></programlisting> |
|
<para>To link in the service on the client, we'll create a separate Spring container, |
|
containing the simple object and the service linking configuration bits:</para> |
|
<programlisting language="xml"><![CDATA[<bean class="example.SimpleObject"> |
|
<property name="accountService" ref="accountService"/> |
|
</bean> |
|
|
|
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> |
|
<property name="serviceUrl" value="rmi://HOST:1199/AccountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
</bean>]]></programlisting> |
|
<para>That's all we need to do to support the remote account service on the client. |
|
Spring will transparently create an invoker and remotely enable the account |
|
service through the <classname>RmiServiceExporter</classname>. At the client |
|
we're linking it in using the <classname>RmiProxyFactoryBean</classname>.</para> |
|
</section> |
|
</section> |
|
|
|
<section id="remoting-caucho-protocols"> |
|
<title>Using Hessian or Burlap to remotely call services via HTTP</title> |
|
<para>Hessian offers a binary HTTP-based remoting protocol. It is developed by |
|
Caucho and more information about Hessian itself can be found at |
|
<ulink url="http://www.caucho.com"/>.</para> |
|
|
|
<section id="remoting-caucho-protocols-hessian"> |
|
<title>Wiring up the <classname>DispatcherServlet</classname> for Hessian and co.</title> |
|
<para>Hessian communicates via HTTP and does so using a custom servlet. |
|
Using Spring's <classname>DispatcherServlet</classname> principles, as known |
|
from Spring Web MVC usage, you can easily wire up such a servlet exposing |
|
your services. First we'll have to create a new servlet in your application |
|
(this an excerpt from <filename>'web.xml'</filename>):</para> |
|
<programlisting language="xml"><![CDATA[<servlet> |
|
<servlet-name>remoting</servlet-name> |
|
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> |
|
<load-on-startup>1</load-on-startup> |
|
</servlet> |
|
|
|
<servlet-mapping> |
|
<servlet-name>remoting</servlet-name> |
|
<url-pattern>/remoting/*</url-pattern> |
|
</servlet-mapping>]]></programlisting> |
|
<para>You're probably familiar with Spring's <classname>DispatcherServlet</classname> |
|
principles and if so, you know that now you'll have to create a Spring container |
|
configuration resource named <filename>'remoting-servlet.xml'</filename> (after |
|
the name of your servlet) in the <filename class="directory">'WEB-INF'</filename> |
|
directory. The application context will be used in the next section.</para> |
|
<para>Alternatively, consider the use of Spring's simpler |
|
<classname>HttpRequestHandlerServlet</classname>. |
|
This allows you to embed the remote exporter definitions in your root application |
|
context (by default in <filename>'WEB-INF/applicationContext.xml'</filename>), |
|
with individual servlet definitions pointing to specific exporter beans. |
|
Each servlet name needs to match the bean name of its target exporter in this case.</para> |
|
</section> |
|
|
|
<section id="remoting-caucho-protocols-hessian-server"> |
|
<title>Exposing your beans by using the <classname>HessianServiceExporter</classname></title> |
|
<para>In the newly created application context called <literal>remoting-servlet.xml</literal>, |
|
we'll create a <classname>HessianServiceExporter</classname> exporting your services:</para> |
|
<programlisting language="xml"><![CDATA[<bean id="accountService" class="example.AccountServiceImpl"> |
|
]]><lineannotation><!-- any additional properties, maybe a DAO? --></lineannotation><![CDATA[ |
|
</bean> |
|
|
|
<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter"> |
|
<property name="service" ref="accountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
</bean>]]></programlisting> |
|
<para>Now we're ready to link in the service at the client. No explicit handler mapping |
|
is specified, mapping request URLs onto services, so <classname>BeanNameUrlHandlerMapping</classname> |
|
will be used: Hence, the service will be exported at the URL indicated through |
|
its bean name within the containing <classname>DispatcherServlet</classname>'s |
|
mapping (as defined above): <literal>'http://HOST:8080/remoting/AccountService'</literal>. |
|
</para> |
|
<para>Alternatively, create a <classname>HessianServiceExporter</classname> in your |
|
root application context (e.g. in <filename>'WEB-INF/applicationContext.xml'</filename>):</para> |
|
<programlisting language="xml"><![CDATA[<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter"> |
|
<property name="service" ref="accountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
</bean>]]></programlisting> |
|
<para>In the latter case, define a corresponding servlet for this exporter |
|
in <filename>'web.xml'</filename>, with the same end result: The exporter |
|
getting mapped to the request path <literal>/remoting/AccountService</literal>. |
|
Note that the servlet name needs to match the bean name of the target exporter.</para> |
|
<programlisting language="xml"><![CDATA[<servlet> |
|
<servlet-name>accountExporter</servlet-name> |
|
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> |
|
</servlet> |
|
|
|
<servlet-mapping> |
|
<servlet-name>accountExporter</servlet-name> |
|
<url-pattern>/remoting/AccountService</url-pattern> |
|
</servlet-mapping>]]></programlisting> |
|
</section> |
|
|
|
<section id="remoting-caucho-protocols-hessian-client"> |
|
<title>Linking in the service on the client</title> |
|
<para>Using the <classname></classname> we can link in the service |
|
at the client. The same principles apply as with the RMI example. We'll create |
|
a separate bean factory or application context and mention the following beans |
|
where the <classname>SimpleObject</classname> is using the |
|
<interfacename>AccountService</interfacename> to manage accounts:</para> |
|
<programlisting language="xml"><![CDATA[<bean class="example.SimpleObject"> |
|
<property name="accountService" ref="accountService"/> |
|
</bean> |
|
|
|
<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"> |
|
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
</bean>]]></programlisting> |
|
</section> |
|
|
|
<section id="remoting-caucho-protocols-burlap"> |
|
<title>Using Burlap</title> |
|
<para>We won't discuss Burlap, the XML-based equivalent of Hessian, in detail here, |
|
since it is configured and set up in exactly the same way as the Hessian |
|
variant explained above. Just replace the word <literal>Hessian</literal> |
|
with <literal>Burlap</literal> and you're all set to go.</para> |
|
</section> |
|
|
|
<section id="remoting-caucho-protocols-security"> |
|
<title>Applying HTTP basic authentication to a service exposed through Hessian or Burlap</title> |
|
<para>One of the advantages of Hessian and Burlap is that we can easily apply HTTP basic |
|
authentication, because both protocols are HTTP-based. Your normal HTTP server security |
|
mechanism can easily be applied through using the <literal>web.xml</literal> security |
|
features, for example. Usually, you don't use per-user security credentials here, but |
|
rather shared credentials defined at the <literal>Hessian/BurlapProxyFactoryBean</literal> level |
|
(similar to a JDBC <interfacename>DataSource</interfacename>).</para> |
|
<programlisting language="xml"><![CDATA[<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> |
|
<property name="interceptors" ref="authorizationInterceptor"/> |
|
</bean> |
|
|
|
<bean id="authorizationInterceptor" |
|
class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor"> |
|
<property name="authorizedRoles" value="administrator,operator"/> |
|
</bean>]]></programlisting> |
|
<para>This an example where we explicitly mention the <classname>BeanNameUrlHandlerMapping</classname> |
|
and set an interceptor allowing only administrators and operators to call |
|
the beans mentioned in this application context.</para> |
|
<note> |
|
<para>Of course, this example doesn't show a flexible kind of security |
|
infrastructure. For more options as far as security is concerned, |
|
have a look at the Acegi Security System for Spring, to be found at |
|
<ulink url="http://acegisecurity.sourceforge.net"/>.</para> |
|
</note> |
|
</section> |
|
</section> |
|
|
|
<section id="remoting-httpinvoker"> |
|
<title>Exposing services using HTTP invokers</title> |
|
<para>As opposed to Burlap and Hessian, which are both lightweight protocols using their |
|
own slim serialization mechanisms, Spring Http invokers use the standard |
|
Java serialization mechanism to expose services through HTTP. This has a huge |
|
advantage if your arguments and return types are complex types that cannot be |
|
serialized using the serialization mechanisms Hessian and Burlap use (refer to the |
|
next section for more considerations when choosing a remoting technology).</para> |
|
<para>Under the hood, Spring uses either the standard facilities provided by J2SE to |
|
perform HTTP calls or Commons <classname>HttpClient</classname>. Use the latter if you need more advanced |
|
and easy-to-use functionality. Refer to |
|
<ulink url="http://jakarta.apache.org/commons/httpclient">jakarta.apache.org/commons/httpclient</ulink> |
|
for more info.</para> |
|
|
|
<section id="remoting-httpinvoker-server"> |
|
<title>Exposing the service object</title> |
|
<para>Setting up the HTTP invoker infrastructure for a service objects much resembles |
|
the way you would do using Hessian or Burlap. Just as Hessian support provides |
|
the <classname>HessianServiceExporter</classname>, Spring's HttpInvoker support provides |
|
the <classname>org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter</classname>. |
|
</para> |
|
<para>To expose the <literal>AccountService</literal> (mentioned above) within a |
|
Spring Web MVC <classname>DispatcherServlet</classname>, the following configuration |
|
needs to be in place in the dispatcher's application context:</para> |
|
<programlisting language="xml"><![CDATA[<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> |
|
<property name="service" ref="accountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
</bean> |
|
]]></programlisting> |
|
<para>Such an exporter definition will be exposed through the |
|
<classname>DispatcherServlet</classname>'s standard mapping facilities, |
|
as explained in the section on Hessian.</para> |
|
<para>Alternatively, create an <classname>HttpInvokerServiceExporter</classname> in your |
|
root application context (e.g. in <filename>'WEB-INF/applicationContext.xml'</filename>):</para> |
|
<programlisting language="xml"><![CDATA[<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> |
|
<property name="service" ref="accountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
</bean>]]></programlisting> |
|
<para>In addition, define a corresponding servlet for this exporter in |
|
<filename>'web.xml'</filename>, with the servlet name matching the bean |
|
name of the target exporter:</para> |
|
<programlisting language="xml"><![CDATA[<servlet> |
|
<servlet-name>accountExporter</servlet-name> |
|
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> |
|
</servlet> |
|
|
|
<servlet-mapping> |
|
<servlet-name>accountExporter</servlet-name> |
|
<url-pattern>/remoting/AccountService</url-pattern> |
|
</servlet-mapping>]]></programlisting> |
|
</section> |
|
|
|
<section id="remoting-httpinvoker-client"> |
|
<title>Linking in the service at the client</title> |
|
<para>Again, linking in the service from the client much resembles the way you would |
|
do it when using Hessian or Burlap. Using a proxy, Spring will be able to |
|
translate your calls to HTTP POST requests to the URL pointing to the exported |
|
service.</para> |
|
<programlisting language="xml"><![CDATA[<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> |
|
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
</bean> |
|
]]></programlisting> |
|
<para>As mentioned before, you can choose what HTTP client you want to use. |
|
By default, the <classname>HttpInvokerProxy</classname> uses the J2SE HTTP functionality, but |
|
you can also use the Commons <classname>HttpClient</classname> by setting the |
|
<literal>httpInvokerRequestExecutor</literal> property:</para> |
|
<programlisting language="xml"><![CDATA[<property name="httpInvokerRequestExecutor"> |
|
<bean class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"/> |
|
</property> |
|
]]></programlisting> |
|
</section> |
|
</section> |
|
|
|
<section id="remoting-web-services"> |
|
<title>Web services</title> |
|
<para>Spring provides full support for standard Java web services APIs:</para> |
|
<itemizedlist> |
|
<listitem><para>Exposing web services using JAX-RPC</para></listitem> |
|
<listitem><para>Accessing web services using JAX-RPC</para></listitem> |
|
<listitem><para>Exposing web services using JAX-WS</para></listitem> |
|
<listitem><para>Accessing web services using JAX-WS</para></listitem> |
|
</itemizedlist> |
|
<note> |
|
<para>Why two standard Java web services APIs?</para> |
|
<para>JAX-RPC 1.1 is the standard web service API in J2EE 1.4. |
|
As its name indicates, it focuses on on RPC bindings, which became |
|
less and less popular in the past couple of years. As a consequence, |
|
it has been superseded by JAX-WS 2.0 in Java EE 5, being more flexible |
|
in terms of bindings but also being heavily annotation-based. JAX-WS 2.1 |
|
is also included in Java 6 (or more specifically, in Sun's JDK 1.6.0_04 |
|
and above; previous Sun JDK 1.6.0 releases included JAX-WS 2.0), |
|
integrated with the JDK's built-in HTTP server.</para> |
|
<para>Spring can work with both standard Java web services APIs. |
|
The choice is effectively dependent on the runtime platform: |
|
On JDK 1.4 / J2EE 1.4, the only option is JAX-RPC. On Java EE 5 / Java 6, |
|
the obvious choice is JAX-WS. On J2EE 1.4 environments that run on Java 5, |
|
you might have the option to plug in a JAX-WS provider; check your J2EE |
|
server's documentation.</para> |
|
</note> |
|
<para>In addition to stock support for JAX-RPC and JAX-WS in Spring Core, the Spring portfolio also |
|
features <ulink url="http://www.springframework.org/spring-ws">Spring Web Services</ulink>, a solution |
|
for contract-first, document-driven web services - highly recommended for building modern, future-proof |
|
web services. Last but not least, <ulink url="http://xfire.codehaus.org">XFire</ulink> also allows |
|
you to export Spring-managed beans as a web service, through built-in Spring support.</para> |
|
|
|
<section id="remoting-web-services-jaxrpc-export"> |
|
<title>Exposing servlet-based web services using JAX-RPC</title> |
|
<para>Spring provides a convenience base class for JAX-RPC servlet endpoint implementations - |
|
<classname>ServletEndpointSupport</classname>. To expose our |
|
<interfacename>AccountService</interfacename> we extend Spring's |
|
<classname>ServletEndpointSupport</classname> class and implement our business |
|
logic here, usually delegating the call to the business layer.</para> |
|
<programlisting language="java"><lineannotation>/** |
|
* JAX-RPC compliant RemoteAccountService implementation that simply delegates |
|
* to the AccountService implementation in the root web application context. |
|
* |
|
* This wrapper class is necessary because JAX-RPC requires working with dedicated |
|
* endpoint classes. If an existing service needs to be exported, a wrapper that |
|
* extends ServletEndpointSupport for simple application context access is |
|
* the simplest JAX-RPC compliant way. |
|
* |
|
* This is the class registered with the server-side JAX-RPC implementation. |
|
* In the case of Axis, this happens in "server-config.wsdd" respectively via |
|
* deployment calls. The web service engine manages the lifecycle of instances |
|
* of this class: A Spring application context can just be accessed here. |
|
*/</lineannotation><![CDATA[import org.springframework.remoting.jaxrpc.ServletEndpointSupport; |
|
|
|
public class AccountServiceEndpoint extends ServletEndpointSupport implements RemoteAccountService { |
|
|
|
private AccountService biz; |
|
|
|
protected void onInit() { |
|
this.biz = (AccountService) getWebApplicationContext().getBean("accountService"); |
|
} |
|
|
|
public void insertAccount(Account acc) throws RemoteException { |
|
biz.insertAccount(acc); |
|
} |
|
|
|
public Account[] getAccounts(String name) throws RemoteException { |
|
return biz.getAccounts(name); |
|
} |
|
}]]></programlisting> |
|
<para>Our <classname>AccountServletEndpoint</classname> needs to run in the same web |
|
application as the Spring context to allow for access to Spring's facilities. In case of |
|
Axis, copy the <classname>AxisServlet</classname> definition into your |
|
<filename>'web.xml'</filename>, and set up the endpoint in |
|
<filename>'server-config.wsdd'</filename> (or use the deploy tool). See the sample |
|
application JPetStore where the <interfacename>OrderService</interfacename> is exposed as |
|
a web service using Axis.</para> |
|
</section> |
|
|
|
<section id="remoting-web-services-jaxrpc-access"> |
|
<title>Accessing web services using JAX-RPC</title> |
|
<para>Spring provides two factory beans to create JAX-RPC web service proxies, |
|
namely <classname>LocalJaxRpcServiceFactoryBean</classname> and |
|
<classname>JaxRpcPortProxyFactoryBean</classname>. The former can only return a JAX-RPC |
|
service class for us to work with. The latter is the full-fledged version that can return |
|
a proxy that implements our business service interface. In this example we use the latter |
|
to create a proxy for the <interfacename>AccountService</interfacename> endpoint we exposed |
|
in the previous section. You will see that Spring has great support for web services |
|
requiring little coding efforts - most of the setup is done in the Spring configuration |
|
file as usual:</para> |
|
<programlisting language="xml"><![CDATA[<bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean"> |
|
<property name="serviceInterface" value="example.RemoteAccountService"/> |
|
<property name="wsdlDocumentUrl" value="http://localhost:8080/account/services/accountService?WSDL"/> |
|
<property name="namespaceUri" value="http://localhost:8080/account/services/accountService"/> |
|
<property name="serviceName" value="AccountService"/> |
|
<property name="portName" value="AccountPort"/> |
|
</bean>]]></programlisting> |
|
<para>Where <literal>serviceInterface</literal> is our remote business interface the clients will use. |
|
<literal>wsdlDocumentUrl</literal> is the URL for the WSDL file. Spring needs this a startup time to create the JAX-RPC Service. |
|
<literal>namespaceUri</literal> corresponds to the targetNamespace in the .wsdl file. |
|
<literal>serviceName</literal> corresponds to the service name in the .wsdl file. |
|
<literal>portName</literal> corresponds to the port name in the .wsdl file. |
|
</para> |
|
<para>Accessing the web service is now very easy as we have a bean factory for it that will expose it |
|
as <literal>RemoteAccountService</literal> interface. We can wire this up in Spring:</para> |
|
<programlisting language="xml"><![CDATA[<bean id="client" class="example.AccountClientImpl"> |
|
... |
|
<property name="service" ref="accountWebService"/> |
|
</bean>]]></programlisting> |
|
<para>From the client code we can access the web service just as if it |
|
was a normal class, except that it throws <exceptionname>RemoteException</exceptionname>.</para> |
|
<programlisting language="java"><![CDATA[public class AccountClientImpl { |
|
|
|
private RemoteAccountService service; |
|
|
|
public void setService(RemoteAccountService service) { |
|
this.service = service; |
|
} |
|
|
|
public void foo() { |
|
try { |
|
service.insertAccount(...); |
|
} |
|
catch (RemoteException ex) { |
|
]]><lineannotation>// ouch</lineannotation><![CDATA[ |
|
} |
|
} |
|
} |
|
]]></programlisting> |
|
<para>We can get rid of the checked <exceptionname>RemoteException</exceptionname> since |
|
Spring supports automatic conversion to its corresponding unchecked |
|
<exceptionname>RemoteException</exceptionname>. This requires that we provide a non-RMI |
|
interface also. Our configuration is now:</para> |
|
<programlisting language="xml"><![CDATA[<bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean"> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
<property name="portInterface" value="example.RemoteAccountService"/> |
|
</bean>]]></programlisting> |
|
<para>Where <literal>serviceInterface</literal> is changed to our non RMI interface. Our RMI |
|
interface is now defined using the property <literal>portInterface</literal>. Our client |
|
code can now avoid handling <exceptionname>java.rmi.RemoteException</exceptionname>:</para> |
|
<programlisting language="java"><![CDATA[public class AccountClientImpl { |
|
|
|
private AccountService service; |
|
|
|
public void setService(AccountService service) { |
|
this.service = service; |
|
} |
|
|
|
public void foo() { |
|
service.insertAccount(...); |
|
} |
|
}]]></programlisting> |
|
<para>Note that you can also drop the "portInterface" part and specify a plain |
|
business interface as "serviceInterface". In this case, |
|
<classname>JaxRpcPortProxyFactoryBean</classname> will automatically switch |
|
to the JAX-RPC "Dynamic Invocation Interface", performing dynamic invocations |
|
without a fixed port stub. The advantage is that you don't even need to have |
|
an RMI-compliant Java port interface around (e.g. in case of a non-Java target |
|
web service); all you need is a matching business interface. Check out |
|
<classname>JaxRpcPortProxyFactoryBean</classname>'s javadoc for details |
|
on the runtime implications.</para> |
|
</section> |
|
|
|
<section id="remoting-web-services-jaxrpc-mapping-registration"> |
|
<title>Registering JAX-RPC Bean Mappings</title> |
|
<para>To transfer complex objects over the wire such as <classname>Account</classname> we must |
|
register bean mappings on the client side.</para> |
|
<note> |
|
<para>On the server side using Axis registering bean mappings is usually done in |
|
the <filename>'server-config.wsdd'</filename> file.</para> |
|
</note> |
|
<para>We will use Axis to register bean mappings on the client side. To do this we need to |
|
register the bean mappings programmatically:</para> |
|
<programlisting language="java"><![CDATA[public class AxisPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean { |
|
|
|
protected void postProcessJaxRpcService(Service service) { |
|
TypeMappingRegistry registry = service.getTypeMappingRegistry(); |
|
TypeMapping mapping = registry.createTypeMapping(); |
|
registerBeanMapping(mapping, Account.class, "Account"); |
|
registry.register("http://schemas.xmlsoap.org/soap/encoding/", mapping); |
|
} |
|
|
|
protected void registerBeanMapping(TypeMapping mapping, Class type, String name) { |
|
QName qName = new QName("http://localhost:8080/account/services/accountService", name); |
|
mapping.register(type, qName, |
|
new BeanSerializerFactory(type, qName), |
|
new BeanDeserializerFactory(type, qName)); |
|
} |
|
}]]></programlisting> |
|
</section> |
|
|
|
<section id="remoting-web-services-jaxrpc-handler-registration"> |
|
<title>Registering your own JAX-RPC Handler</title> |
|
<para>In this section we will register our own |
|
<interfacename>javax.rpc.xml.handler.Handler</interfacename> to the web service proxy |
|
where we can do custom code before the SOAP message is sent over the wire. |
|
The <interfacename>Handler</interfacename> is a callback interface. There is a convenience |
|
base class provided in <filename class="libraryfile">jaxrpc.jar</filename>, namely |
|
<classname>javax.rpc.xml.handler.GenericHandler</classname> that we will extend:</para> |
|
<programlisting language="java"><![CDATA[public class AccountHandler extends GenericHandler { |
|
|
|
public QName[] getHeaders() { |
|
return null; |
|
} |
|
|
|
public boolean handleRequest(MessageContext context) { |
|
SOAPMessageContext smc = (SOAPMessageContext) context; |
|
SOAPMessage msg = smc.getMessage(); |
|
try { |
|
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope(); |
|
SOAPHeader header = envelope.getHeader(); |
|
... |
|
} |
|
catch (SOAPException ex) { |
|
throw new JAXRPCException(ex); |
|
} |
|
return true; |
|
} |
|
}]]></programlisting> |
|
<para>What we need to do now is to register our AccountHandler to JAX-RPC Service so it would |
|
invoke <methodname>handleRequest(..)</methodname> before the message is sent over the wire. |
|
Spring has at this time of writing no declarative support for registering handlers, so we must |
|
use the programmatic approach. However Spring has made it very easy for us to do this as we can |
|
override the <methodname>postProcessJaxRpcService(..)</methodname> method that is designed for |
|
this:</para> |
|
<programlisting language="java"><![CDATA[public class AccountHandlerJaxRpcPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean { |
|
|
|
protected void postProcessJaxRpcService(Service service) { |
|
QName port = new QName(this.getNamespaceUri(), this.getPortName()); |
|
List list = service.getHandlerRegistry().getHandlerChain(port); |
|
list.add(new HandlerInfo(AccountHandler.class, null, null)); |
|
logger.info("Registered JAX-RPC AccountHandler on port " + port); |
|
} |
|
}]]></programlisting> |
|
<para>The last thing we must remember to do is to change the Spring configuration to use our |
|
factory bean:</para> |
|
<programlisting language="xml"><![CDATA[<bean id="accountWebService" class="example.AccountHandlerJaxRpcPortProxyFactoryBean"> |
|
... |
|
</bean>]]></programlisting> |
|
</section> |
|
|
|
<section id="remoting-web-services-jaxws-export-servlet"> |
|
<title>Exposing servlet-based web services using JAX-WS</title> |
|
<para>Spring provides a convenient base class for JAX-WS servlet endpoint implementations - |
|
<classname>SpringBeanAutowiringSupport</classname>. To expose our |
|
<interfacename>AccountService</interfacename> we extend Spring's |
|
<classname>SpringBeanAutowiringSupport</classname> class and implement our business |
|
logic here, usually delegating the call to the business layer. |
|
We'll simply use Spring 2.5's <literal>@Autowired</literal> |
|
annotation for expressing such dependencies on Spring-managed beans.</para> |
|
<programlisting language="java"><lineannotation>/** |
|
* JAX-WS compliant AccountService implementation that simply delegates |
|
* to the AccountService implementation in the root web application context. |
|
* |
|
* This wrapper class is necessary because JAX-WS requires working with dedicated |
|
* endpoint classes. If an existing service needs to be exported, a wrapper that |
|
* extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through |
|
* the @Autowired annotation) is the simplest JAX-WS compliant way. |
|
* |
|
* This is the class registered with the server-side JAX-WS implementation. |
|
* In the case of a Java EE 5 server, this would simply be defined as a servlet |
|
* in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting |
|
* accordingly. The servlet name usually needs to match the specified WS service name. |
|
* |
|
* The web service engine manages the lifecycle of instances of this class. |
|
* Spring bean references will just be wired in here. |
|
*/</lineannotation><![CDATA[import org.springframework.web.context.support.SpringBeanAutowiringSupport; |
|
|
|
@WebService(serviceName="AccountService") |
|
public class AccountServiceEndpoint extends SpringBeanAutowiringSupport { |
|
|
|
@Autowired |
|
private AccountService biz; |
|
|
|
@WebMethod |
|
public void insertAccount(Account acc) { |
|
biz.insertAccount(acc); |
|
} |
|
|
|
@WebMethod |
|
public Account[] getAccounts(String name) { |
|
return biz.getAccounts(name); |
|
} |
|
}]]></programlisting> |
|
<para>Our <classname>AccountServletEndpoint</classname> needs to run in the same web |
|
application as the Spring context to allow for access to Spring's facilities. This is |
|
the case by default in Java EE 5 environments, using the standard contract for JAX-WS |
|
servlet endpoint deployment. See Java EE 5 web service tutorials for details.</para> |
|
</section> |
|
|
|
<section id="remoting-web-services-jaxws-export-standalone"> |
|
<title>Exporting standalone web services using JAX-WS</title> |
|
<para>The built-in JAX-WS provider that comes with Sun's JDK 1.6 supports exposure |
|
of web services using the built-in HTTP server that's included in JDK 1.6 as well. |
|
Spring's <classname>SimpleJaxWsServiceExporter</classname> detects all |
|
<literal>@WebService</literal> annotated beans in the Spring application context, |
|
exporting them through the default JAX-WS server (the JDK 1.6 HTTP server).</para> |
|
<para>In this scenario, the endpoint instances are defined and managed as Spring beans |
|
themselves; they will be registered with the JAX-WS engine but their lifecycle |
|
will be up to the Spring application context. This means that Spring functionality |
|
like explicit dependency injection may be applied to the endpoint instances. |
|
Of course, annotation-driven injection through <literal>@Autowired</literal> |
|
will work as well. |
|
</para> |
|
<programlisting language="xml"><![CDATA[<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter"> |
|
<property name="baseAddress" value="http://localhost:9999/"/> |
|
</bean> |
|
|
|
<bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint"> |
|
... |
|
</bean> |
|
|
|
...]]></programlisting> |
|
<para>The <classname>AccountServiceEndpoint</classname> may derive from |
|
Spring's <classname>SpringBeanAutowiringSupport</classname> but doesn't |
|
have to since the endpoint is a fully Spring-managed bean here. |
|
This means that the endpoint implementation may look like as follows, |
|
without any superclass declared - and Spring's <literal>@Autowired</literal> |
|
configuration annotation still being honored:</para> |
|
<programlisting language="java"><![CDATA[@WebService(serviceName="AccountService") |
|
public class AccountServiceEndpoint { |
|
|
|
@Autowired |
|
private AccountService biz; |
|
|
|
@WebMethod |
|
public void insertAccount(Account acc) { |
|
biz.insertAccount(acc); |
|
} |
|
|
|
@WebMethod |
|
public Account[] getAccounts(String name) { |
|
return biz.getAccounts(name); |
|
} |
|
}]]></programlisting> |
|
</section> |
|
|
|
<section id="remoting-web-services-jaxws-export-ri"> |
|
<title>Exporting web services using the JAX-WS RI's Spring support</title> |
|
<para>Sun's JAX-WS RI, developed as part of the GlassFish project, ships Spring support |
|
as part of its JAX-WS Commons project. This allows for defining JAX-WS endpoints as |
|
Spring-managed beans, similar to the standalone mode discussed in the previous section |
|
- but this time in a Servlet environment. <emphasis>Note that this is not portable |
|
in a Java EE 5 environment; it is mainly intended for non-EE environments such as Tomcat, |
|
embedding the JAX-WS RI as part of the web application.</emphasis></para> |
|
<para>The difference to the standard style of exporting servlet-based endpoints is |
|
that the lifecycle of the endpoint instances themselves will be managed by Spring here, |
|
and that there will be only one JAX-WS servlet defined in <literal>web.xml</literal>. |
|
With the standard Java EE 5 style (as illustrated above), you'll have one servlet |
|
definition per service endpoint, with each endpoint typically delegating to Spring |
|
beans (through the use of <literal>@Autowired</literal>, as shown above).</para> |
|
<para>Check out |
|
<ulink url="https://jax-ws-commons.dev.java.net/spring/">https://jax-ws-commons.dev.java.net/spring/</ulink> |
|
for the details on setup and usage style.</para> |
|
</section> |
|
|
|
<section id="remoting-web-services-jaxws-access"> |
|
<title>Accessing web services using JAX-WS</title> |
|
<para>Analogous to the JAX-RPC support, Spring provides two factory beans |
|
to create JAX-WS web service proxies, namely <classname>LocalJaxWsServiceFactoryBean</classname> and |
|
<classname>JaxWsPortProxyFactoryBean</classname>. The former can only return a JAX-WS |
|
service class for us to work with. The latter is the full-fledged version that can return |
|
a proxy that implements our business service interface. In this example we use the latter |
|
to create a proxy for the <interfacename>AccountService</interfacename> endpoint (again): |
|
</para> |
|
<programlisting language="xml"><![CDATA[<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"> |
|
<property name="serviceInterface" value="example.AccountService"/> |
|
<property name="wsdlDocumentUrl" value="http://localhost:8080/account/services/accountService?WSDL"/> |
|
<property name="namespaceUri" value="http://localhost:8080/account/services/accountService"/> |
|
<property name="serviceName" value="AccountService"/> |
|
<property name="portName" value="AccountPort"/> |
|
</bean>]]></programlisting> |
|
<para>Where <literal>serviceInterface</literal> is our business interface the clients will use. |
|
<literal>wsdlDocumentUrl</literal> is the URL for the WSDL file. Spring needs this a startup time to create the JAX-WS Service. |
|
<literal>namespaceUri</literal> corresponds to the targetNamespace in the .wsdl file. |
|
<literal>serviceName</literal> corresponds to the service name in the .wsdl file. |
|
<literal>portName</literal> corresponds to the port name in the .wsdl file. |
|
</para> |
|
<para>Accessing the web service is now very easy as we have a bean factory for it that will expose it |
|
as <literal>AccountService</literal> interface. We can wire this up in Spring:</para> |
|
<programlisting language="xml"><![CDATA[<bean id="client" class="example.AccountClientImpl"> |
|
... |
|
<property name="service" ref="accountWebService"/> |
|
</bean>]]></programlisting> |
|
<para>From the client code we can access the web service just as if it |
|
was a normal class:</para> |
|
<programlisting language="java"><![CDATA[public class AccountClientImpl { |
|
|
|
private AccountService service; |
|
|
|
public void setService(AccountService service) { |
|
this.service = service; |
|
} |
|
|
|
public void foo() { |
|
service.insertAccount(...); |
|
} |
|
}]]></programlisting> |
|
<para><emphasis>NOTE:</emphasis> The above is slightly simplified in that JAX-WS |
|
requires endpoint interfaces and implementation classes to be annotated with |
|
<literal>@WebService</literal>, <literal>@SOAPBinding</literal> etc annotations. |
|
This means that you cannot (easily) use plain Java interfaces and implementation |
|
classes as JAX-WS endpoint artifacts; you need to annotate them accordingly first. |
|
Check the JAX-WS documentation for details on those requirements.</para> |
|
</section> |
|
|
|
<section id="remoting-web-services-xfire"> |
|
<title>Exposing web services using XFire</title> |
|
<para>XFire is a lightweight SOAP library, hosted by Codehaus. Exposing XFire is done using a |
|
XFire context that shipping with XFire itself in combination with a RemoteExporter-style bean |
|
you have to add to your <interfacename>WebApplicationContext</interfacename>. As with all |
|
methods that allow you to expose service, you have to create a |
|
<classname>DispatcherServlet</classname> with a corresponding |
|
<interfacename>WebApplicationContext</interfacename> containing the services you will be |
|
exposing:</para> |
|
<programlisting language="xml"><![CDATA[<servlet> |
|
<servlet-name>xfire</servlet-name> |
|
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> |
|
</servlet>]]></programlisting> |
|
<para>You also have to link in the XFire configuration. This is done by adding a context |
|
file to the <literal>contextConfigLocations</literal> context parameter picked up by the |
|
<classname>ContextLoaderListener</classname> (or <classname>ContextLoaderServlet</classname> |
|
for that matter).</para> |
|
<programlisting language="xml"><![CDATA[<context-param> |
|
<param-name>contextConfigLocation</param-name> |
|
<param-value>classpath:org/codehaus/xfire/spring/xfire.xml</param-value> |
|
</context-param> |
|
|
|
<listener> |
|
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> |
|
</listener>]]></programlisting> |
|
|
|
<para>After you added a servlet mapping (mapping <literal>/*</literal> to the XFire servlet |
|
declared above) you only have to add one extra bean to expose the service using XFire. |
|
Add for example the following configuration in your <filename>'xfire-servlet.xml'</filename> |
|
file:</para> |
|
<programlisting language="xml"><![CDATA[<beans> |
|
|
|
<bean name="/Echo" class="org.codehaus.xfire.spring.remoting.XFireExporter"> |
|
<property name="serviceInterface" value="org.codehaus.xfire.spring.Echo"/> |
|
<property name="serviceBean"> |
|
<bean class="org.codehaus.xfire.spring.EchoImpl"/> |
|
</property> |
|
]]><lineannotation><!-- the XFire bean is defined in the <filename>xfire.xml</filename> file --></lineannotation><![CDATA[ |
|
<property name="xfire" ref="xfire"/> |
|
</bean> |
|
|
|
</beans>]]></programlisting> |
|
<para>XFire handles the rest. It introspects your service interface and generates a WSDL from it. |
|
Parts of this documentation have been taken from the XFire site; for more detailed information |
|
on XFire Spring integration, navigate to |
|
<ulink url="http://docs.codehaus.org/display/XFIRE/Spring">http://docs.codehaus.org/display/XFIRE/Spring</ulink>.</para> |
|
</section> |
|
</section> |
|
|
|
<section id="remoting-jms"> |
|
<title>JMS</title> |
|
<para>It is also possible to expose services transparently using JMS as the underlying |
|
communication protocol. The JMS remoting support in the Spring Framework is pretty basic - |
|
it sends and receives on the <interfacename>same thread</interfacename> and in the |
|
<emphasis>same non-transactional</emphasis> <interfacename>Session</interfacename>, and as |
|
such throughput will be very implementation dependent.</para> |
|
<para>The following interface is used on both the server and the client side.</para> |
|
<programlisting language="java"><![CDATA[package com.foo; |
|
|
|
public interface CheckingAccountService { |
|
|
|
public void cancelAccount(Long accountId); |
|
}]]></programlisting> |
|
<para>The following simple implementation of the above interface is used on the server-side.</para> |
|
<programlisting language="java"><![CDATA[package com.foo; |
|
|
|
public class SimpleCheckingAccountService implements CheckingAccountService { |
|
|
|
public void cancelAccount(Long accountId) { |
|
System.out.println("Cancelling account [" + accountId + "]"); |
|
} |
|
}]]></programlisting> |
|
<para>This configuration file contains the JMS-infrastructure beans that are shared on both |
|
the client and server.</para> |
|
<programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?> |
|
<beans xmlns="http://www.springframework.org/schema/beans" |
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|
xsi:schemaLocation="http://www.springframework.org/schema/beans |
|
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> |
|
|
|
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> |
|
<property name="brokerURL" value="tcp://ep-t43:61616"/> |
|
</bean> |
|
|
|
<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue"> |
|
<constructor-arg value="mmm"/> |
|
</bean> |
|
|
|
</beans>]]></programlisting> |
|
|
|
<section id="remoting-jms-server"> |
|
<title>Server-side configuration</title> |
|
<para>On the server, you just need to expose the service object using the |
|
<classname>JmsInvokerServiceExporter</classname>.</para> |
|
<programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?> |
|
<beans xmlns="http://www.springframework.org/schema/beans" |
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|
xsi:schemaLocation="http://www.springframework.org/schema/beans |
|
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> |
|
|
|
<bean id="checkingAccountService" |
|
class="org.springframework.jms.remoting.JmsInvokerServiceExporter"> |
|
<property name="serviceInterface" value="com.foo.CheckingAccountService"/> |
|
<property name="service"> |
|
<bean class="com.foo.SimpleCheckingAccountService"/> |
|
</property> |
|
</bean> |
|
|
|
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer"> |
|
<property name="connectionFactory" ref="connectionFactory"/> |
|
<property name="destination" ref="queue"/> |
|
<property name="concurrentConsumers" value="3"/> |
|
<property name="messageListener" ref="checkingAccountService"/> |
|
</bean> |
|
|
|
</beans>]]></programlisting> |
|
<programlisting language="java"><![CDATA[package com.foo; |
|
|
|
import org.springframework.context.support.ClassPathXmlApplicationContext; |
|
|
|
public class Server { |
|
|
|
public static void main(String[] args) throws Exception { |
|
new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"}); |
|
} |
|
}]]></programlisting> |
|
</section> |
|
|
|
<section id="remoting-jms-client"> |
|
<title>Client-side configuration</title> |
|
<para>The client merely needs to create a client-side proxy that will implement the agreed |
|
upon interface (<interfacename>CheckingAccountService</interfacename>). The resulting |
|
object created off the back of the following bean definition can be injected into other |
|
client side objects, and the proxy will take care of forwarding the call to the |
|
server-side object via JMS.</para> |
|
<programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?> |
|
<beans xmlns="http://www.springframework.org/schema/beans" |
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|
xsi:schemaLocation="http://www.springframework.org/schema/beans |
|
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> |
|
|
|
<bean id="checkingAccountService" |
|
class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean"> |
|
<property name="serviceInterface" value="com.foo.CheckingAccountService"/> |
|
<property name="connectionFactory" ref="connectionFactory"/> |
|
<property name="queue" ref="queue"/> |
|
</bean> |
|
|
|
</beans>]]></programlisting> |
|
<programlisting language="java"><![CDATA[package com.foo; |
|
|
|
import org.springframework.context.ApplicationContext; |
|
import org.springframework.context.support.ClassPathXmlApplicationContext; |
|
|
|
public class Client { |
|
|
|
public static void main(String[] args) throws Exception { |
|
ApplicationContext ctx = new ClassPathXmlApplicationContext( |
|
new String[] {"com/foo/client.xml", "com/foo/jms.xml"}); |
|
CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService"); |
|
service.cancelAccount(new Long(10)); |
|
} |
|
}]]></programlisting> |
|
</section> |
|
|
|
<para>You may also wish to investigate the support provided by the |
|
<ulink url="http://lingo.codehaus.org/">Lingo</ulink> project, which (to quote the |
|
homepage blurb) <quote><emphasis>... is a lightweight POJO based remoting and messaging library |
|
based on the Spring Framework's remoting libraries which extends it to support JMS.</emphasis></quote></para> |
|
</section> |
|
|
|
<section id="remoting-autodection-remote-interfaces"> |
|
<title>Auto-detection is not implemented for remote interfaces</title> |
|
<para>The main reason why auto-detection of implemented interfaces does not occur for |
|
remote interfaces is to avoid opening too many doors to remote callers. The target |
|
object might implement internal callback interfaces like <interfacename>InitializingBean</interfacename> or |
|
<interfacename>DisposableBean</interfacename> which one would not want to expose to callers. |
|
</para> |
|
<para>Offering a proxy with all interfaces implemented by the target usually does not |
|
matter in the local case. But when exporting a remote service, you should expose |
|
a specific service interface, with specific operations intended for remote usage. |
|
Besides internal callback interfaces, the target might implement multiple business |
|
interfaces, with just one of them intended for remote exposure. For these reasons, |
|
we <emphasis>require</emphasis> such a service interface to be specified. |
|
</para> |
|
<para>This is a trade-off between configuration convenience and the risk of accidental |
|
exposure of internal methods. Always specifying a service interface is not too much |
|
effort, and puts you on the safe side regarding controlled exposure of specific methods. |
|
</para> |
|
</section> |
|
|
|
<section id="remoting-considerations"> |
|
<title>Considerations when choosing a technology</title> |
|
<para>Each and every technology presented here has its drawbacks. You should carefully |
|
consider you needs, the services your exposing and the objects you'll be sending |
|
over the wire when choosing a technology. |
|
</para> |
|
<para>When using RMI, it's not possible to access the objects through the HTTP protocol, |
|
unless you're tunneling the RMI traffic. RMI is a fairly heavy-weight protocol |
|
in that it support full-object serialization which is important when using a |
|
complex data model that needs serialization over the wire. However, RMI-JRMP |
|
is tied to Java clients: It is a Java-to-Java remoting solution. |
|
</para> |
|
<para>Spring's HTTP invoker is a good choice if you need HTTP-based remoting but also |
|
rely on Java serialization. It shares the basic infrastructure with RMI invokers, |
|
just using HTTP as transport. Note that HTTP invokers are not only limited to |
|
Java-to-Java remoting but also to Spring on both the client and server side. |
|
(The latter also applies to Spring's RMI invoker for non-RMI interfaces.) |
|
</para> |
|
<para>Hessian and/or Burlap might provide significant value when operating in a |
|
heterogeneous environment, because they explicitly allow for non-Java clients. |
|
However, non-Java support is still limited. Known issues include the serialization |
|
of Hibernate objects in combination with lazily-initialized collections. If you |
|
have such a data model, consider using RMI or HTTP invokers instead of Hessian. |
|
</para> |
|
<para>JMS can be useful for providing clusters of services and allowing the JMS broker |
|
to take care of load balancing, discovery and auto-failover. |
|
By default: Java serialization is used when using JMS remoting but |
|
the JMS provider could use a different mechanism for the wire formatting, |
|
such as XStream to allow servers to be implemented in other technologies. |
|
</para> |
|
<para>Last but not least, EJB has an advantage over RMI in that it supports standard |
|
role-based authentication and authorization and remote transaction propagation. |
|
It is possible to get RMI invokers or HTTP invokers to support security context |
|
propagation as well, although this is not provided by core Spring: There are |
|
just appropriate hooks for plugging in third-party or custom solutions here. |
|
</para> |
|
</section> |
|
|
|
</chapter>
|
|
|