Now that you’ve seen the similarities between Spring Integration messages and JMS messages and learned about the core Spring support for JMS, you’re well prepared to look at the process of sending JMS messages from a Spring Integration application. As with most adapters in Spring Integration, a unidirectional channel adapter and a request/reply gateway are available. Because it’s considerably simpler, we begin the discussion with the unidirectional channel adapter.
Spring Integration’s outbound JMS channel adapter is a JMS message publisher encapsulated in an implementation of Spring Integration’s MessageHandler inter-face. That means it can be connected to any MessageChannel so that any Spring Inte-gration Messages sent to that channel are converted into JMS Messages and then sent to a JMS Destination. The JMS Destination may be a Queue or a Topic, but from the perspective of this adapter implementation, that’s a configuration detail.
The main class involved in the one-way outbound JMS adapter is called JmsSending-MessageHandler. If you look at its implementation, you’ll see that it builds completely on the JMS support of the underlying Spring Framework. The most important respon-sibilities are handled internally by an instance of Spring’s JmsTemplate. Most of the code in Spring Integration’s adapter handles the various configuration options, of
167 Sending JMS messages from a Spring Integration application
which there are many. As far as most users are concerned, even those configuration options are handled by the XML namespace support. In most cases, only a small subset of those options would be explicitly configured, but there are many options for han-dling more nuanced usage requirements. We walk through several of these in a moment, but first let’s look at a typical configuration for one of these adapters:
<int-jms:outbound-channel-adapter channel="toJMS"
destination-name="samples.queue.fromSI" />
That looks simple enough, right? Hopefully so, and if you can rely on the defaults, then that’s all you need to configure. It’s literally adapting the Spring Integration toJMS channel so that it converts the messages into JMS Messages and then sends them to the JMS Queue named samples.queue.fromSI. If you want to use a Topic instead of a Queue, be sure to provide the pub-sub-domain attribute with a value of true, as in the following example:
<int-jms:outbound-channel-adapter channel="toJMS"
destination-name="samples.topic.fromSI"
pub-sub-domain="true" />
Sometimes it’s not practical to rely on just the name of the JMS destination. In fact, it’s common that the queues and topics are administered objects that developers should always access via JNDI lookups. Fortunately, you can rely on the Spring Framework’s ability to handle that. Instead of using the destination-name attribute, you could pro-vide a destination attribute whose name is a reference to another object being man-aged by Spring. That other object could then be a result of a JNDI lookup. For handling that lookup, Spring provides a FactoryBean implementation called the JndiObjectFactoryBean. Although it’s perfectly acceptable to define that Factory-Bean instance as a low-level bean element, there’s XML namespace support for much more concise configuration options, as shown here:
<int-jms:outbound-channel-adapter channel="toJMS" destination="fromSI"/>
<jee:jndi-lookup id="fromSI" jndi-name="jms/queue.fromSI"/>
The functionality would be exactly the same. The difference is limited to the configu-ration. Access by name is often sufficient in development and testing environments, but JNDI lookups might be required for a production system. In those cases, you can manage the configuration excerpts appropriately by using import elements in the con-figuration or other similar techniques. The important factor is that you don’t need to modify any code to handle those different approaches for resolving JMS destinations.
Fortunately, the configuration of the ConnectionFactory and Destinations can be shared across both the sending and receiving sides. Likewise, for commonly config-ured references, such as these destinations, there is consistency between the inbound and outbound adapters. In the next section, we focus on the receiving side. We begin with the inbound channel adapter that serves as a polling consumer.
168 CHAPTER 9 Spring Integration and the Java Message Service
9.5 Receiving JMS messages
in a Spring Integration application
When receiving JMS messages in a unidirectional way, there are two options. You can define an inbound channel adapter that acts a polling consumer or one that acts as an event-driven consumer. The polling option is configured with an <inbound-channel-adapter> element as defined in the JMS schema. It accepts a destination-name attri-bute for the JMS Queue or Topic. Its default DestinationResolver looks up the desti-nation accordingly, and if you need to customize that behavior for some reason, you can provide a destination-resolver attribute with the bean name reference of your own implementation. The <inbound-channel-adapter> element also requires a poller unless you’re relying on a default context-wide poller element. Here’s a simple example of an inbound channel adapter that polls for a JMS message every three seconds:
<int-jms:inbound-channel-adapter id="pollingJmsInboundAdapter"
channel="jmsMessages" destination-name="myQueue">
<int:poller fixed-delay="3000" max-messages-per-poll="1"/>
</int-jms:inbound-channel-adapter>
Like the outbound version, if you’re specifying a topic name rather than a queue name, you should also provide the pub-sub-domain attribute with a value of true. If instead you want to reference a Queue or Topic instance, you can use the destination attribute in place of destination-name. This is a common practice when defining this adapter alongside Spring’s JNDI support, as shown previously in the outbound chan-nel adapter examples. The following is an example of the corresponding inbound configuration:
<int-jms:inbound-channel-adapter id="pollingJmsInboundAdapter"
channel="jmsMessages" destination="myQueue">
<int:poller fixed-delay="3000" max-messages-per-poll="1"/>
</int-jms:inbound-channel-adapter>
<jee:jndi-lookup id="myQueue" jndi-name="jms/someQueue"/>
As mentioned earlier, polling is rarely the best choice when building a JMS-based solu-tion. Considering that the underlying JMS support in the Spring Framework enables asynchronous invocation of a MessageListener as soon as a JMS message arrives, that’s almost always the better option. The only exceptions might be when you want to con-figure a poller to run infrequently or only at certain times of the day. If the poller is limited to run at certain times of the day, you’d most likely use the cron attribute on a poller element. Other than in those rare situations, the responsiveness will be better and the configuration will be simpler if you stick with Spring Integration’s message-driven-channel-adapter. The basic configuration will look the same, but there’s no longer a need to define a poller:
<int-jms:message-driven-channel-adapter id="messageDrivenAdapter"
channel="jmsMessages" destination-name="myQueue"/>
It may seem odd that, unlike most adapters you’ve seen, the element doesn’t include inbound in its name. Considering it’s message-driven, it should be relatively clear
169 Request-reply messaging
that this channel adapter is reacting to inbound JMS messages that arrive at the given queue or topic. It sends those messages to the channel referenced by the channel attribute.
9.6 Request-reply messaging
The discussion and examples in this chapter have thus far focused on unidirectional channel adapters. On the sending side, we haven’t yet discussed the case where we might be expecting a reply, and on the receiving side, we haven’t yet discussed the case where we might be expected to send a reply. We saw that Spring Integration’s inbound JMS channel adapters can receive messages with either polling or message-driven behavior. On the other hand, the outbound channel adapter can be used to send messages to a JMS destination, be it a queue or a topic. In both cases, the Spring Integration message is mapped to or from the JMS message so that the payload as well as the headers can correspond to the JMS message’s body and properties respectively.
This section introduces Spring Integration’s bidirectional gateways for utilizing JMS in a request-reply model of interaction. Much of the functionality, such as map-ping between the JMS and Spring Integration message representations, is the same.
The difference is that these request-reply gateways are responsible for mapping in both directions. As with the unidirectional channel adapter discussions, we begin with the outbound side. Whereas earlier we could describe the outbound behavior as solely responsible for sending messages, in the gateway case, there is a receive as well, assum-ing that the JMS reply message arrives as expected. The simplest way to think of the outbound gateway is as a send-and-receive adapter.
9.6.1 The outbound gateway
In the simplest case, the outbound configuration will look similar to the outbound-channel-adapter configuration we saw earlier:
<int-jms:outbound-gateway request-channel="toJMS"
reply-channel="jmsReplies"
request-destination-name="examples.gateway.queue"/>
The request-channel and reply-channel attributes refer to Spring Integration MessageChannel instances. Any Spring Integration message that’s sent to the request channel will be converted into a JMS message and sent to the gateway’s request desti-nation (in this context, destidesti-nation always refers to a JMS component, and channel is the Spring Integration message channel). In this example, the destination is a queue. If it were a topic, the request-pub-sub-domain attribute would need to be provided with a value of true. Because the gateway must manage sending and receiving separately, many of its attributes are qualified as affiliated with either the request or the reply.
The reply-channel is where any JMS reply messages are sent after they’re converted to Spring Integration messages.
You may have noticed that we didn’t provide a reply-destination-name. That attribute is optional, but it’s common to leave it out. The gateway implementation