• 沒有找到結果。

Spring Integration and the Java Message Service

在文檔中 IN ACTION (頁 186-197)

For many Java developers, the first thing that comes to mind when they hear “mes-saging” is the Java Message Service (JMS). That’s understandable considering it’s the predominant Java-based API for messaging and sits among the standards of the Java Enterprise Edition (JEE). The JMS specification was designed to provide a general abstraction over message-oriented middleware (MOM). Most of the well-known ven-dor products for messaging can be accessed and used through the JMSAPI. A num-ber of open source JMS implementations are also available, one of which is ActiveMQ, a pure Java implementation of the JMSAPI. We use ActiveMQ in some of the examples in this chapter because it’s easy to configure as an embedded broker.

We don’t go into any specific ActiveMQ details, though. If you want to learn more about it, please refer to ActiveMQ in Action by Bruce Snyder, Dejan Bosanac, and Rob Davies (Manning, 2011).

This chapter covers

How Spring Integration and JMS fit together

Sending and receiving JMS messages with Spring

JMS gateways and channel adapters

156 CHAPTER 9 Spring Integration and the Java Message Service

Hopefully, by this point in the book, you realize that messaging and event-driven architectures don’t necessarily require the use of such systems. We’ve discussed mes-saging in several chapters thus far without having to dive into the details of JMS, which reveals that Spring Integration can stand alone as a framework for building messaging solutions. In a simple application with no external integration requirements, pro-ducer and consumer components may be decoupled by message channels so that they communicate only with messages rather than direct invocation of methods with argu-ments. Messaging is really a paradigm; the same underlying principles apply whether messaging occurs between components running within the same process or between components running under different processes on disparate systems.

Nevertheless, by supporting JMS, Spring Integration provides a bridge between its simple, lightweight intraprocess messaging and the interprocess messaging that JMS enables across many MOM providers. In this chapter, you learn how to map between Spring Integration messages and JMS messages. You also learn about several options for integrating with JMS messaging destinations. Spring Integration provides channel adapters and gateways as well as message channel implementations that are backed by JMS destinations. In many cases, the configuration of these elements is straightfor-ward. But, to get the most benefit from the available features, such as transactions, requires a thorough understanding of the underlying JMS behavior as dictated by the specification. Therefore, in this chapter, we alternate between the Spring Integration role and the specific JMS details as necessary.

9.1 The relationship between Spring Integration and JMS

Spring Integration provides a consistent model for intraprocess and interprocess mes-saging. The primary role of channel adapters and messaging gateways is to connect a local channel to some external system without impacting the producer or consumer components’ code. Another benefit the adapters provide is the separation of the mes-saging concerns from the underlying transports and protocols. They enable true document-style messaging whether the particular adapter implementation is sending requests over HTTP, interacting with a filesystem, or mapping to another messaging API. The JMS-based channel adapters and messaging gateways fall into that last cate-gory and are therefore excellent choices when external system integration is required.

Given that the same general messaging paradigm is followed by Spring Integration and JMS, we can conceptualize the intraprocess and interprocess components as belonging to two layers but with a consistent model. See figure 9.1.

Even though we tend to focus on external system integration when discussing the roles of JMS, there are also benefits to using JMS internally within an application. JMS may be useful between producers and consumers running in the same process because a JMS provider can support persistence, transactions, load balancing, and failover. For this reason, Spring Integration provides a message channel implementa-tion that delegates to JMS behind the scenes. That channel looks like any other chan-nel as far as the message-producing and message-consuming components are concerned, so it can be used as an alternative at any point within a message flow as shown in figure 9.2.

157 The relationship between Spring Integration and JMS

Even for messaging within a single process, the use of a JMS-backed channel provides several benefits. Consider a message channel backed by a simple in-memory queue, as occurs when using the <queue> subelement within a <channel> without referencing any MessageStore. By default, such a channel doesn’t persist messages to a transac-tional resource. Instead, the messages are only stored in a volatile queue such that they can be lost in the case of a system failure. They’ll even be lost if the process is shut down intentionally before those messages are drained from the queue by a consumer.

In certain cases, when dealing with real-time event data that doesn’t require persis-tence, the loss of those event messages upon process termination might not be a prob-lem. It may be well worth the trade-off for asynchronous delivery that allows the producer and consumer to operate within their own threads, on their own schedules.

With these message channels backed by a JMS Destination, though, you can have the best of both worlds. If persistence and transactions are important, but asynchronous delivery is also a desired feature, then these channels offer a good choice even if they’re only being used by producers and consumers in the same process space.

The main point here is that even though we often refer to JMS as an option for messaging between a number of individual processes, that’s not the only time to con-sider JMS or other interprocess broker-based messaging solutions, such as Advanced Message Queuing Protocol (AMQP), as an option. When multiple processes are involved, the other advantages become more evident. First among these is the natural load balancing that occurs when multiple consuming processes are pulling messages from a shared destination. Unlike producer-side load balancing, the consumers can

JVM-1 JVM-2

Figure 9.1 The top configuration shows interprocess integration using JMS. The bottom

configuration shows intraprocess integration using Spring Integration.

Which type of integration is appropriate depends on the

Figure 9.2 Design of the destination-backed channel of Spring Integration. It benefits from the guarantees supported by a JMS implementation, but it hides the JMS API behind a channel abstraction.

158 CHAPTER 9 Spring Integration and the Java Message Service

naturally distribute the load on the basis of their own capabilities. For example, some processes may be running on slower machines or the processing of certain messages may require more resources, but the consumers only ask for more messages when they can handle them rather than forcing some upstream dispatcher to make the decisions.

The second, related benefit is increased scalability. Message-producing processes might be sending more messages than a single consuming process can handle without creating a backlog, resulting in a constantly increasing latency. By adding enough con-suming processes to handle the load, the throughput can increase to the point that a backlog no longer exists, or exists only within acceptable limits in rarely achieved high-load situations that occur during bursts of activity from producers.

The third benefit is increased availability. If a consuming process crashes, messages can still be processed as long as one or more other processes are still running. Even if all processes crash, the mediating broker can store messages until those processes come back online. Likewise, on the producing side, processes may come and go with-out directly affecting any processes on the consuming side. This is nothing more than the benefit of loose coupling inherent in any messaging system, applied not only across producers and consumers themselves but the processes in which they run.

Keep in mind when we discuss these scenarios where processes come and go, we’re not merely talking about unforeseen system outages. It’s increasingly common for modern applications to have “zero downtime” requirements. Such an application must have a distributed architecture with no tight coupling between components in order to accommodate planned downtime of individual processes, one at a time, for system migrations and rolling upgrades.

One last topic we should address briefly here is transactions. We revisit transactions in greater detail near the end of this chapter, but one quick point is relevant to the dis-cussion at hand. In the scenario described previously, where a consuming process crashes or is taken offline while responsible for an in-flight message, transactions play an important role. If the consumer reads a message from a destination but then fails to process it, such as might occur when its host process crashes, then the message might be lost depending on how the system is configured. In JMS, a variety of options correspond to different points on the spectrum of guaranteed delivery. One configu-ration option is to require an explicit acknowledgment from the consumer. It might be that a consumer acknowledges each message after it successfully stores it on disk. A more robust option is to enable transactions. The consumer would commit the trans-action only upon successful processing of the message, and it would roll back the transaction in case of a known failure. When this functionality is enabled, not only do the multiple consuming processes share the load, they can even cover for each other in the event of failures. One consumer may fail while a message is in flight, but its transaction rolls back. The message is then made available to another consuming pro-cess rather than being lost.

Table 9.1 provides a quick overview of the benefits of using JMS with Spring Integration.

159 The relationship between Spring Integration and JMS

It’s worth pointing out that the benefits listed in table 9.1 aren’t limited to JMS. Any broker that provides support for reliable messaging across distributed producer and consumer processes would provide the same benefits. For example, Spring Integra-tion 2.1 adds support for RabbitMQ, which implements the AMQP protocol. Using the AMQP adapters would offer the same benefits. Likewise, although not as sophisticated, even using Spring Integration’s queue-backed channels along with a MessageStore can provide the same benefits because that too enables multiple processes to share the work. For now, let’s get back to the discussion at hand and explore the mapping of Spring Integration message payloads and headers to and from JMS message instances.

9.1.1 Mapping between JMS and Spring Integration messages

When considering interprocess messaging from the perspective of Spring Integration, the primary role of channel adapters is to handle all of the communication details so that the component on the other side of the message channel has no idea that an external system is involved. That means the channel adapter not only handles the communication via the particular transport and protocol being used but also must provide a Messaging Mapper (http://mng.bz/Fl0P) so that whatever data representa-tion is used by the external system is converted to and from simple Spring Integrarepresenta-tion messages. Some of that data might map to the payload of such a message, whereas other parts of the data might map to the headers. That decision should be based on the role of the particular pieces of data, keeping in mind that the headers are typically used by the messaging infrastructure, and the payload is usually the business data that has some meaning within the domain of the application. Thinking of a message as ful-filling the document message pattern from Hohpe and Woolf’s Enterprise Integration Pat-terns (Addison-Wesley, 2003), the payload represents the document, and the headers contain additional metadata, such as a timestamp or some information about the orig-inating system.

It so happens that the construction of a JMS message, according to the JMS specifi-cation, is similar to the construction of a Spring Integration message. This shouldn’t surprise you given that the function of the message is the same in both cases. It does mean that the messaging mapper implementation used by the JMS adapters has a sim-ple role. We’ll go into the details in a later section, but for now it’s sufficient to point

Table 9.1 Benefits of using JMS with Spring Integration

Benefit Description

Load balancing Multiple consumers in separate virtual machine processes pull messages from a shared destination at a rate determined by their own capabilities.

Scalability Adding enough consumer processes to avoid a backlog increases throughput and decreases response time.

Availability With multiple consumer processes, the overall system can remain operational even if one or more individual processes fail. Likewise, consumer processes can be redeployed one at a time to support a rolling upgrade.

160 CHAPTER 9 Spring Integration and the Java Message Service

out that there are merely some differences in naming. In JMS, the message has a body, which is the counterpart of a payload in Spring Integration. Likewise, a JMS message’s properties correspond to a Spring Integration message’s headers. See figure 9.3.

9.1.2 Comparing JMS destinations and Spring Integration message channels

By now you’re familiar with the various message channel types available in Spring Integration. One of the most important distinctions we covered is the difference between point-to-point channels and publish-subscribe channels. You saw that when it comes to configuration, the default type for a channel element in XML is point-to-point, and the publish-subscribe channel is clearly labeled as such. The JMS specifica-tion uses destinaspecifica-tion instead of message channel, but it makes a similar distincspecifica-tion. The two types of JMS Destination are Queues and Topics. A JMS queue provides point-to-point semantics, and a topic supports publish-subscribe interaction. When you use a queue, each message is received by a single consumer, but when you use a topic, the same message can be received by multiple consumers. See table 9.2 for the side-by-side comparison.

Now that we’ve discussed the relationship between Spring Integration and JMS at a high level, we’re almost ready to jump into the details of Spring Integration’s JMS adapters. First, it’s probably a good idea to take a brief detour through the JMS sup-port in the core Spring Framework. For one thing, the Spring Integration supsup-port for JMS builds directly on top of Spring Framework components such as the JmsTemplate and the MessageListener container. Additionally, the general design of Spring Inte-gration messaging endpoints is largely modeled after the Spring JMS support. You should be able to see the similarities as we quickly walk through the main components and some configuration examples in the next section.

Message Message

Headers

Payload

Properties

Body

Spring Integration JMS

Figure 9.3 Spring Integration and JMS messages in a side-by-side comparison. The terminology is different, but the structure is the same.

EIP JMS

Message Channel Destination Point-to-point channel Queue

Publish-subscribe channel Topic Table 9.2 Comparing enterprise integration patterns (EIP) to JMS

161 JMS support in the Spring Framework

9.2 JMS support in the Spring Framework

The logical starting point for any discussion of the Spring Framework’s JMS support is the JmsTemplate. This is a convenience class for interacting with the JMSAPI at a high level. Those familiar with Spring are probably already aware of other templates, such as the JdbcTemplate and the TransactionTemplate. These components are all real-izations of the Template pattern described in the Gang of Four's Design Patterns: Ele-ments of Reusable Object-Oriented Software (Gamma et al., Addison-Wesley, 1994). Each of these Spring-provided templates satisfies the common goal of simplifying usage of a particular API. One quick example should be sufficient to express this idea. First, we look at code that doesn’t use the JmsTemplate but instead performs all actions directly with the JMSAPI. Note that even a simple operation such as sending a text-based mes-sage involves a considerable amount of boilerplate code. Here’s a simple send-and-receive echo example:

public class DirectJmsDemo {

public static void main(String[] args) { try {

throw new RuntimeException("problem occurred in JMS code", e);

} } }

This code is about as simple as it can get when using the JMSAPI directly. ActiveMQ enables running an embedded broker (as you can see from the "vm://localhost"

URL provided to the ConnectionFactory). Many JMS providers would be configured within the Java Naming and Directory Interface (JNDI) registry, and that would require additional code to look up the ConnectionFactory and Queue. Now, let’s see how the same task may be performed using Spring’s JmsTemplate:

162 CHAPTER 9 Spring Integration and the Java Message Service

public class JmsTemplateDemo {

public static void main(String[] args) { ConnectionFactory connectionFactory =

new ActiveMQConnectionFactory("vm://localhost");

JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);

jmsTemplate.setDefaultDestination(new ActiveMQQueue("siia.queue"));

jmsTemplate.convertAndSend("hello world");

System.out.println("received: " + jmsTemplate.receiveAndConvert());

} }

The code is much simpler, and it also provides fewer chances for developer errors.

Any JMSExceptions are caught and converted into RuntimeExceptions in Spring’s JmsException hierarchy. The JMS resources, such as Connection and Session, are also acquired and released as appropriate. In fact, if a transaction is active when this send operation is invoked, and some upstream process has already acquired a JMS Session, this send operation is executed in the same transactional context. If you’ve ever worked with Spring’s transaction management for data access, this concept should be familiar to you. The idea is roughly the same. If one particular operation in the transaction throws an uncaught RuntimeException, all operations that occurred in that same transactional context are rolled back. If all operations are successful, the transaction is committed.

You probably also noticed that the template method invoked is called convertAnd-Send and that its argument is an instance of java.lang.String. There are also send() methods that accept a JMS Message you’ve created, but by using the convertAndSend versions, you can rely on the JmsTemplate to construct the Messages. The conversion itself is a pluggable strategy. The JmsTemplate delegates to an instance of the MessageConverter interface, and the default implementation (SimpleMessage-Converter) automatically performs the conversions shown in table 9.3.

The receiveAndConvert method performs symmetrical conversion from a JMS Message. When used on the receiving end, the SimpleMessageConverter extracts the JMS message’s body and produces a result with the same mappings shown in the table (for example, TextMessage to java.lang.String).

Sometimes the default conversion options aren’t a good fit for a particular applica-tion. That’s why the MessageConverter is a strategy interface that can be configured

Table 9.3 Default type conversions supported by SimpleMessageConverter

Type passed to SimpleMessageConverter JMS Message type

java.lang.String TextMessage

byte[] BytesMessage

java.util.Map MapMessage

java.io.Serializable ObjectMessage

163 Asynchronous JMS message reception with Spring

directly on the JmsTemplate. Spring provides an object-to-XML (OXM) marshalling version of the MessageConverter that supports any of the implementations of Spring’s Marshaller and Unmarshaller interfaces within its toMessage() and from-Message() methods respectively. For example, an application might be responsible for sending and receiving XML-based text messages over a JMS queue, but the applica-tion’s developers prefer to hide the XML marshalling and unmarshalling logic in the

directly on the JmsTemplate. Spring provides an object-to-XML (OXM) marshalling version of the MessageConverter that supports any of the implementations of Spring’s Marshaller and Unmarshaller interfaces within its toMessage() and from-Message() methods respectively. For example, an application might be responsible for sending and receiving XML-based text messages over a JMS queue, but the applica-tion’s developers prefer to hide the XML marshalling and unmarshalling logic in the

在文檔中 IN ACTION (頁 186-197)