For reference see: https://jira.springsource.org/browse/INT-2882 * Verify spacing * Ensure all source code samples are typed: e.g. <programlisting language="xml"> * Ensure source code fits space in PDF format
410 lines
25 KiB
XML
410 lines
25 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="gateway"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Messaging Gateways</title>
|
||
<para>
|
||
The primary purpose of a Gateway is to hide the messaging API provided by
|
||
Spring Integration. It allows your application's business logic to be completely
|
||
unaware of the Spring Integration API and using a generic Gateway, your code
|
||
interacts instead with a simple interface, only.
|
||
</para>
|
||
<section id="gateway-proxy">
|
||
<title>Enter the GatewayProxyFactoryBean</title>
|
||
<para>
|
||
As mentioned above, it would be great to have no dependency on the Spring
|
||
Integration API at all - including the gateway class. For that reason, Spring
|
||
Integration provides the <classname>GatewayProxyFactoryBean</classname> that
|
||
generates a proxy for any interface and internally invokes the gateway
|
||
methods shown below. Using dependency injection you can then expose the interface
|
||
to your business methods.
|
||
</para>
|
||
<para>
|
||
Here is an example of an interface that can be used to interact with Spring
|
||
Integration:
|
||
</para>
|
||
|
||
<programlisting language="java"><![CDATA[package org.cafeteria;
|
||
|
||
public interface Cafe {
|
||
|
||
void placeOrder(Order order);
|
||
|
||
}]]></programlisting>
|
||
|
||
</section>
|
||
<section id="gateway-namespace">
|
||
<title>Gateway XML Namespace Support</title>
|
||
<para>
|
||
Namespace support is also
|
||
provided which allows you to configure such an interface as a service as demonstrated by the following example.
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[<int:gateway id="cafeService"
|
||
service-interface="org.cafeteria.Cafe"
|
||
default-request-channel="requestChannel"
|
||
default-reply-channel="replyChannel"/>]]></programlisting>
|
||
<para>
|
||
With this configuration defined, the "cafeService" can now be injected into other beans, and the code that invokes the methods on that
|
||
proxied instance of the Cafe interface has no awareness of the Spring Integration API. The general
|
||
approach is similar to that of Spring Remoting (RMI, HttpInvoker, etc.). See the "Samples" Appendix for
|
||
an example that uses this "gateway" element (in the Cafe demo).
|
||
</para>
|
||
</section>
|
||
<section id="gateway-default-reply-channel">
|
||
<title>Setting the Default Reply Channel</title>
|
||
<para>
|
||
Typically you don't have to specify the <code>default-reply-channel</code>,
|
||
since a Gateway will auto-create a temporary, anonymous reply channel,
|
||
where it will listen for the reply. However, there are some cases which
|
||
may prompt you to define a <code>default-reply-channel</code> (or <code>reply-channel</code>
|
||
with adapter gateways such as HTTP, JMS, etc.).
|
||
</para>
|
||
<para>
|
||
For some background, we'll quickly discuss some of the inner-workings of the Gateway.
|
||
A Gateway will create a temporary point-to-point reply channel which is anonymous and is added
|
||
to the Message Headers with the name <code>replyChannel</code>.
|
||
When providing an explicit <code>default-reply-channel</code> (<code>reply-channel</code> with remote adapter gateways),
|
||
you have the option to point to a publish-subscribe channel, which is so named because you can add more than one subscriber to it.
|
||
Internally Spring Integration will create a Bridge between the temporary <code>replyChannel</code> and the explicitly defined
|
||
<code>default-reply-channel</code>.
|
||
</para>
|
||
<para>
|
||
So let's say you want your reply to go not only to the gateway, but also to some other consumer. In this case you
|
||
would want two things: <emphasis>a) a named channel you can subscribe to and b) that channel is a publish-subscribe-channel.</emphasis>
|
||
The default strategy used by the gateway will not satisfy those needs, because the reply channel added to the header is anonymous and
|
||
point-to-point. This means that no other subscriber can get a handle to it and even if it could, the channel
|
||
has point-to-point behavior such that only one subscriber would get the Message. So by defining a <code>default-reply-channel</code>
|
||
you can point to a channel of your choosing, which in this case would be a <code>publish-subscribe-channel</code>.
|
||
The Gateway would create a bridge from it to the temporary, anonymous reply channel that is stored in the header.
|
||
</para>
|
||
<para>
|
||
Another case where you might want to provide a reply channel explicitly is for monitoring or auditing via an interceptor
|
||
(e.g., wiretap). You need a named channel in order to configure a Channel Interceptor.
|
||
</para>
|
||
</section>
|
||
<section id="gateway-configuration-annotations">
|
||
<title>Gateway Configuration with Annotations and/or XML</title>
|
||
<para>
|
||
The reason that the attributes on the 'gateway' element are named 'default-request-channel' and
|
||
'default-reply-channel' is that you may also provide per-method channel references by using the
|
||
<classname>@Gateway</classname> annotation.
|
||
</para>
|
||
|
||
<programlisting language="java"><![CDATA[public interface Cafe {
|
||
|
||
@Gateway(requestChannel="orders")
|
||
void placeOrder(Order order);
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
You may alternatively provide such content in <code>method</code> sub-elements if you prefer XML configuration (see the next paragraph).
|
||
</para>
|
||
<para>
|
||
It is also possible to pass values to be interpreted as Message headers on the Message
|
||
that is created and sent to the request channel by using the @Header annotation:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[public interface FileWriter {
|
||
|
||
@Gateway(requestChannel="filesOut")
|
||
void write(byte[] content, @Header(FileHeaders.FILENAME) String filename);
|
||
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
If you prefer the XML approach of configuring Gateway methods, you can provide <emphasis>method</emphasis> sub-elements
|
||
to the gateway configuration.
|
||
</para>
|
||
|
||
<programlisting language="xml"><![CDATA[<int:gateway id="myGateway" service-interface="org.foo.bar.TestGateway"
|
||
default-request-channel="inputC">
|
||
<int:method name="echo" request-channel="inputA" reply-timeout="2" request-timeout="200"/>
|
||
<int:method name="echoUpperCase" request-channel="inputB"/>
|
||
<int:method name="echoViaDefault"/>
|
||
</int:gateway>]]></programlisting>
|
||
|
||
<para>
|
||
You can also provide individual headers per method invocation via XML.
|
||
This could be very useful if the headers you want to set are static in nature and you don't want
|
||
to embed them in the gateway's method signature via <classname>@Header</classname> annotations.
|
||
For example, in the Loan Broker example we want to influence how aggregation of the Loan quotes
|
||
will be done based on what type of request was initiated (single quote or all quotes). Determining the
|
||
type of the request by evaluating what gateway method was invoked, although possible, would
|
||
violate the separation of concerns paradigm (the method is a java artifact), but expressing your
|
||
intention (meta information) via Message headers is natural in a Messaging architecture.
|
||
</para>
|
||
|
||
<programlisting language="xml"><![CDATA[<int:gateway id="loanBrokerGateway"
|
||
service-interface="org.springframework.integration.loanbroker.LoanBrokerGateway">
|
||
<int:method name="getLoanQuote" request-channel="loanBrokerPreProcessingChannel">
|
||
<int:header name="RESPONSE_TYPE" value="BEST"/>
|
||
</int:method>
|
||
<int:method name="getAllLoanQuotes" request-channel="loanBrokerPreProcessingChannel">
|
||
<int:header name="RESPONSE_TYPE" value="ALL"/>
|
||
</int:method>
|
||
</int:gateway>]]></programlisting>
|
||
<para>
|
||
In the above case you can clearly see how a different value will be set for the 'RESPONSE_TYPE'
|
||
header based on the gateway's method.
|
||
</para>
|
||
|
||
</section>
|
||
<section id="gateway-calling-no-argument-methods">
|
||
<title>Invoking No-Argument Methods</title>
|
||
<para>
|
||
When invoking methods on a Gateway interface that do not have any arguments,
|
||
the default behavior is to <emphasis>receive</emphasis> a <code>Message</code> from a
|
||
<interfacename>PollableChannel</interfacename>.
|
||
</para>
|
||
<para>
|
||
At times however, you may want to trigger no-argument methods so that
|
||
you can in fact interact with other components downstream that do not require
|
||
user-provided parameters, e.g. triggering no-argument SQL calls or Stored
|
||
Procedures.
|
||
</para>
|
||
<para>
|
||
In order to achieve <emphasis>send-and-receive</emphasis> semantics, you must provide a payload.
|
||
In order to generate a payload, method parameters on the interface are
|
||
not necessary. You can either use the <code>@Payload</code> annotation
|
||
or the <code>payload-expression</code> attribute in XML on the <code>method</code>
|
||
sub-element. Below please find a few examples of what the payloads could be:
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>a literal string</listitem>
|
||
<listitem>#method (for the method name)</listitem>
|
||
<listitem>new java.util.Date()</listitem>
|
||
<listitem>@someBean.someMethod()'s return value</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
Here is an example using the <code>@Payload</code> annotation:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[public interface Cafe {
|
||
|
||
@Payload("new java.util.Date()")
|
||
List<Order> retrieveOpenOrders();
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
If a method has no argument and no return value, but does contain a
|
||
payload expression, it will be treated as a <emphasis>send-only</emphasis>
|
||
operation.
|
||
</para>
|
||
</section>
|
||
|
||
<section id="gateway-error-handling">
|
||
<title>Error Handling </title>
|
||
<para>
|
||
|
||
Of course, the Gateway invocation might result in errors.
|
||
By default any error that has occurred downstream will be re-thrown as a
|
||
<classname>MessagingException</classname> (RuntimeException)
|
||
upon the Gateway's method invocation. However there are times when you may want
|
||
to simply log the error rather than propagating it, or you may want to treat an
|
||
Exception as a valid reply, by mapping it to a Message that will conform to some
|
||
"error message" contract that the caller understands. To accomplish this, our
|
||
Gateway provides support for a Message Channel dedicated to the errors via the
|
||
<emphasis>error-channel</emphasis> attribute. In the example below, you can see
|
||
that a 'transformer' is used to create a reply Message from the Exception.
|
||
</para>
|
||
|
||
<programlisting language="xml"><![CDATA[<int:gateway id="sampleGateway"
|
||
default-request-channel="gatewayChannel"
|
||
service-interface="foo.bar.SimpleGateway"
|
||
error-channel="exceptionTransformationChannel"/>
|
||
|
||
<int:transformer input-channel="exceptionTransformationChannel"
|
||
ref="exceptionTransformer" method="createErrorResponse"/>
|
||
]]></programlisting>
|
||
<para>
|
||
The <emphasis>exceptionTransformer</emphasis> could be a simple POJO that
|
||
knows how to create the expected error response objects. That would then be
|
||
the payload that is sent back to the caller. Obviously, you could do many
|
||
more elaborate things in such an "error flow" if necessary. It might involve
|
||
routers (including Spring Integration's ErrorMessageExceptionTypeRouter),
|
||
filters, and so on. Most of the time, a simple 'transformer' should be sufficient,
|
||
however.
|
||
</para>
|
||
<para>
|
||
Alternatively, you might want to only log the Exception (or send it somewhere
|
||
asynchronously). If you provide a one-way flow, then nothing would be sent
|
||
back to the caller. In the case that you want to completely suppress Exceptions,
|
||
you can provide a reference to the global "nullChannel" (essentially a /dev/null
|
||
approach). Finally, as mentioned above, if no "error-channel" is defined at all,
|
||
then the Exceptions will propagate as usual.
|
||
</para>
|
||
<para>
|
||
<important>
|
||
Exposing the messaging system via simple POJI Gateways obviously provides benefits, but "hiding" the reality
|
||
of the underlying messaging system does come at a price so there are certain things you should consider.
|
||
|
||
We want our Java method to return as quickly as possible and not hang for an indefinite amount of time while
|
||
the caller is waiting on it to return (void, return value, or a thrown Exception). When regular methods are
|
||
used as a proxies in front of the Messaging system, we have to take into account the potentially asynchronous
|
||
nature of the underlying messaging. This means that there might
|
||
be a chance that a Message that was initiated by a Gateway could be dropped by a Filter, thus never reaching a
|
||
component that is responsible for producing a reply. Some Service Activator method might result in an Exception,
|
||
thus providing no reply (as we don't generate Null messages). So as you can see there are multiple scenarios
|
||
where a reply message might not be coming. That is perfectly natural in messaging systems. However think about the
|
||
implication on the gateway method. The Gateway's method input arguments were incorporated into a Message and
|
||
sent downstream. The reply Message would be converted to a return value of the Gateway's method. So you might want
|
||
to ensure that for each Gateway call there will always be a reply Message.
|
||
Otherwise, your Gateway method might never return and will hang indefinitely.
|
||
One of the ways of handling this situation is via an Asynchronous Gateway (explained later in this section). Another way of handling it is to explicitly set the reply-timeout attribute. That way, the gateway will not hang any longer than the time specified by the reply-timeout and will return 'null' if that timeout does elapse. Finally, you might want to consider setting downstream flags such as 'requires-reply' on
|
||
a service-activator or 'throw-exceptions-on-rejection' on a filter. These options will be discussed in more detail in the final section
|
||
of this chapter.
|
||
</important>
|
||
</para>
|
||
</section>
|
||
<section id="async-gateway">
|
||
<title>Asynchronous Gateway</title>
|
||
<para>
|
||
As a pattern the Messaging Gateway is a very nice way to hide messaging-specific code while still exposing the full capabilities of the
|
||
messaging system. As you've seen, the <classname>GatewayProxyFactoryBean</classname> provides a convenient way to expose a Proxy over a service-interface
|
||
thus giving you POJO-based access to a messaging system (based on objects in your own domain, or primitives/Strings, etc). But when a
|
||
gateway is exposed via simple POJO methods which return values it does imply that for each Request message (generated when the method is invoked)
|
||
there must be a Reply message (generated when the method has returned). Since Messaging systems naturally are asynchronous you may not always be
|
||
able to guarantee the contract where <emphasis>"for each request there will always be be a reply"</emphasis>.
|
||
With Spring Integration 2.0 we are introducing support for an <emphasis>Asynchronous Gateway</emphasis> which is a convenient way to initiate
|
||
flows where you may not know if a reply is expected or how long will it take for replies to arrive.
|
||
</para>
|
||
<para>
|
||
A natural way to handle these types of scenarios in Java would be relying upon <emphasis>java.util.concurrent.Future</emphasis> instances, and
|
||
that is exactly what Spring Integration uses to support an <emphasis>Asynchronous Gateway</emphasis>.
|
||
</para>
|
||
<para>
|
||
From the XML configuration, there is nothing different and you still define <emphasis>Asynchronous Gateway</emphasis> the same way as a regular Gateway.
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[<int:gateway id="mathService"
|
||
service-interface="org.springframework.integration.sample.gateway.futures.MathServiceGateway"
|
||
default-request-channel="requestChannel"/>]]></programlisting>
|
||
<para>
|
||
However the Gateway Interface (service-interface) is a bit different.
|
||
</para>
|
||
<programlisting language="java">public interface MathServiceGateway {
|
||
Future<Integer> multiplyByTwo(int i);
|
||
}</programlisting>
|
||
|
||
<para>
|
||
As you can see from the example above the return type for the gateway method is a <classname>Future</classname>. When
|
||
<classname>GatewayProxyFactoryBean</classname> sees that the
|
||
return type of the gateway method is a <classname>Future</classname>, it immediately switches to the async mode by utilizing
|
||
an <classname>AsyncTaskExecutor</classname>. That is all. The call to such a method always returns immediately with a <classname>Future</classname> instance.
|
||
Then, you can interact with the <classname>Future</classname> at your own pace to get the result, cancel, etc. And, as with
|
||
any other use of Future instances, calling get() may reveal a timeout, an execution exception, and so on.
|
||
<programlisting language="java">MathServiceGateway mathService = ac.getBean("mathService", MathServiceGateway.class);
|
||
Future<Integer> result = mathService.multiplyByTwo(number);
|
||
// do something else here since the reply might take a moment
|
||
int finalResult = result.get(1000, TimeUnit.SECONDS);</programlisting>
|
||
For a more detailed example, please refer to the
|
||
<ulink url="https://github.com/SpringSource/spring-integration-samples/tree/master/intermediate/async-gateway">
|
||
<emphasis>async-gateway</emphasis></ulink> sample distributed within the Spring Integration samples.
|
||
</para>
|
||
|
||
<para><emphasis>Asynchronous Gateway and AsyncTaskExecutor</emphasis></para>
|
||
<para>
|
||
By default <classname>GatewayProxyFactoryBean</classname> uses <classname>org.springframework.core.task.SimpleAsyncTaskExecutor</classname>
|
||
when submitting internal <classname>AsyncInvocationTask</classname> instances for any gateway method whose
|
||
return type is <classname>Future.class</classname>. However the <literal>async-executor</literal> attribute in the
|
||
<literal><gateway/></literal> element's configuration allows you to provide a reference to any implementation of
|
||
<classname>java.util.concurrent.Executor</classname> available within the Spring application context.
|
||
</para>
|
||
</section>
|
||
<section>
|
||
<title>Gateway behavior when no response arrives</title>
|
||
<para>
|
||
As it was explained earlier, the Gateway provides a convenient way of interacting with a Messaging system via POJO method
|
||
invocations, but realizing that a typical method invocation, which is generally expected to always return (even with an Exception),
|
||
might not always map one-to-one to message exchanges (e.g., a reply message might not arrive - which is equivalent to a
|
||
method not returning). It is important to go over several scenarios especially in the Sync Gateway case and understand
|
||
the default behavior of the Gateway and how to deal with these scenarios to make the Sync Gateway behavior more
|
||
predictable regardless of the outcome of the message flow that was initialed from such Gateway.
|
||
</para>
|
||
<para>
|
||
There are certain attributes that could be configured to make Sync Gateway behavior more predictable,
|
||
but some of them might not always work as you might have expected. One of them is <emphasis>reply-timeout</emphasis>.
|
||
So, lets look at the <emphasis>reply-timeout</emphasis> attribute and see how it can/can't influence the behavior
|
||
of the Sync Gateway in various scenarios. We will look at single-threaded scenario
|
||
(all components downstream are connected via Direct Channel) and multi-threaded scenarios
|
||
(e.g., somewhere downstream you may have Pollable or Executor Channel which breaks single-thread boundary)
|
||
</para>
|
||
<para>
|
||
<emphasis>Long running process downstream</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream is still running (e.g., infinite loop or a very slow service), then setting a <emphasis>reply-timeout</emphasis>
|
||
has no effect and the Gateway method call will not return until such downstream service exits (via return or exception).
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis>.
|
||
If a component downstream is still running (e.g., infinite loop or a very slow service), in a multi-threaded message
|
||
flow setting the <emphasis>reply-timeout</emphasis> will have an effect by allowing gateway method invocation to
|
||
return once the timeout has been reached, since the <classname>GatewayProxyFactoryBean</classname> will simply
|
||
poll on the reply channel waiting for a message until the timeout expires. However it could result in a 'null' return
|
||
from the Gateway method if the timeout has been reached before the actual reply was produced. It is also important to understand that
|
||
the reply message (if produced) will be sent to a reply channel after the Gateway method invocation might have returned, so you must be aware of that and design your flow with this in mind.
|
||
</para>
|
||
<para>
|
||
<emphasis>Downstream component returns 'null'</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream returns 'null' and no <emphasis>reply-timeout</emphasis> has been configured, the Gateway
|
||
method call will hang indefinitely unless: a) a <emphasis>reply-timeout</emphasis> has been configured or b) the
|
||
<emphasis>requires-reply</emphasis> attribute has been set on the downstream component (e.g., service-activator)
|
||
that might return 'null'. In this case, an Exception would be thrown and propagated to the Gateway.
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis>. Behavior is the same as above.
|
||
</para>
|
||
<para>
|
||
<emphasis>Downstream component return signature is 'void' while Gateway method signature is non-void</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream returns 'void' and no <emphasis>reply-timeout</emphasis> has been configured,
|
||
the Gateway method call will hang indefinitely unless a <emphasis>reply-timeout</emphasis> has been configured
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis> Behavior is the same as above.
|
||
</para>
|
||
<para>
|
||
<emphasis>Downstream component results in Runtime Exception (regardless of the method signature)</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream throws a Runtime Exception, such exception will be propagated via an Error Message back to
|
||
the gateway and re-thrown.
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis> Behavior is the same as above.
|
||
</para>
|
||
<para>
|
||
<important>
|
||
It is also important to understand that by default <emphasis>reply-timeout</emphasis> is unbounded* which means that
|
||
if not explicitly set there are several scenarios (described above) where your Gateway method invocation might
|
||
hang indefinitely. So, make sure you analyze your flow and if there is even a remote possibility of one of these
|
||
scenarios to occur, set the <emphasis>reply-timeout</emphasis> attribute to a 'safe' value or, even better,
|
||
set the <emphasis>requires-reply</emphasis> attribute of the downstream component to 'true' to ensure a timely response
|
||
as produced by the throwing of an Exception as soon as that downstream component does return null internally.
|
||
But also, realize that there are some scenarios (see the very first one)
|
||
where <emphasis>reply-timeout</emphasis> will not help. That means it is also important to analyze your message
|
||
flow and decide when to use a Sync Gateway vs an Async Gateway. As you've seen the latter case is simply a matter of
|
||
defining Gateway methods that return Future instances. Then, you are guaranteed to receive that return value, and
|
||
you will have more granular control over the results of the invocation.
|
||
<para>
|
||
Also, when dealing with a Router you should remember that setting the <emphasis>resolution-required</emphasis> attribute to 'true'
|
||
will result in an Exception thrown by the router if it can not resolve a particular channel. Likewise, when dealing with a Filter,
|
||
you can set the <emphasis>throw-exception-on-rejection</emphasis> attribute. In both of these cases, the resulting flow will
|
||
behave like that containing a service-activator with the 'requires-reply' attribute. In other words, it will help to ensure
|
||
a timely response from the Gateway method invocation.
|
||
</para>
|
||
</important>
|
||
<note>
|
||
<para>
|
||
* <emphasis>reply-timeout</emphasis> is unbounded for <emphasis><gateway/></emphasis>
|
||
elements (created by the GatewayProxyFactoryBean). Inbound gateways for external integration
|
||
(ws, http, etc.) share many characteristics and attributes with these gateways. However,
|
||
for those inbound gateways, the default <emphasis>reply-timeout</emphasis> is 1000
|
||
milliseconds (1 second). If a downstream async handoff is made to another thread, you may need to
|
||
increase this attribute to allow enough time for the flow to complete before the
|
||
gateway times out.
|
||
</para>
|
||
</note>
|
||
</para>
|
||
</section>
|
||
|
||
</section>
|