CHAPTER 5

image

Java Message Service

The enterprise integration technology explained in this chapter is the Java Message Service (JMS). It is different from the technologies discussed in previous chapters, because it is an inherently asynchronous technology for exchanging messages.

In the remoting, REST, and SOAP web services approaches, a client sends a request and waits for a server response. Waiting for a response can be implemented synchronously (the client thread is blocked until a response is received or times out) or asynchronously (the client registers callback logic, which is executed when the response arrives or the communication times out). But the HTTP communication between them remains open, at least until the client receives a response.

JMS is different, because it doesn’t follow this approach of request-response. Looking deeper, communication counterparts in JMS communication can’t be characterized as a client/server relationship at all. JMS is a technology whereby a producer sends a message to a consumer. But the message is not directly sent to the consumer via a socket connection.

A special middle component is used in JMS communication, called a destination. We can think of it as storage for messages. The producer sends a message to this destination and then doesn’t care about this message anymore. The message is stored in the destination and sits there until it gets attention from a consumer or consumers. When the consumer is ready, it reads the message from the destination. So the producer and consumer are highly decoupled from each other: they don’t need to know anything about the other side of this communication. The only important aspect is to know how to send/receive the message to/from destination.

Such communication is asynchronous, because the producer doesn’t wait for the consumer to receive the message. There is no guarantee that the message will be delivered at all. If there’s no application consuming messages from the destination, the message could sit in the destination theoretically forever.

Another important attribute of this type of communication is reliability. As we mentioned, the destination stores messages. It is obvious that storing messages requires a specialized server that can store them. This server is often called a message broker. It is specialized middleware, which can provide various destinations and can store messages in persistent storage if needed.

The Java Message Service is part of the Java Enterprise Edition standard, and therefore each JEE-compliant application server can also perform the role of a message broker. But you may wonder now, what about lightweight Spring applications? These applications are often hosted only on servlet containers without message broker capabilities. In these cases, projects focusing only on a stand-alone JMS implementation could be embedded into the Spring application if needed. But for most architectures, it makes sense to host the JMS parts as separate components of the enterprise system.

The following are main JMS providers:

  • Apache ActiveMQ
  • HornetQ (JBoss)
  • JBoss Messaging (JBoss)
  • WebSphere MQ (IBM)
  • WebSphere Application Server (IBM)
  • SonicMQ (Progress Software)
  • Oracle WebLogic
  • Oracle Advanced Queuing (AQ)
  • Open Message Queue (Oracle)
  • FFMQ
  • TIBCO Enterprise Message Service
  • OpenJMS

All these JMS providers are often referred to as message-oriented middleware (MOM). Some of them are full JEE-compliant application servers, and others implement only the JMS part of the JEE standard. So another significant difference in comparison to client/server communication is operational overhead for managing the message broker in the middle of the communication. If we are using a full JEE application server, this operational overhead is not huge, because the message broker is hosted with the JEE application server on the same server. But if we are using a lightweight solution (which is the case with most Spring applications), we can host a JMS message broker as a stand-alone server instance or embed it into the Spring application.

JMS messaging is often useful when we need to throttle messages between a producer and consumer. Also it is often used as temporary storage for streamed data, because we can easily replay all the messages if needed. Its reliability as a storage system for messages makes it a popular choice for caching data that enters our enterprise system.

When the producer and consumer are decoupled via a JMS destination, we can easily change the infrastructure routing between them without triggering any changes to either side of the communication. Moreover, when the consumer and receiver don’t know about each other, we can easily plug in some logic in between their communication channels—for example, filtering or transforming messages. All these aspects make JMS messaging an excellent choice for decoupling components in an enterprise system.

JMS Standard Overview

As part of Java Enterprise Edition, the Java Message Service provides a standardized API for message exchange. This is handy because it eliminates problems related to locking into a particular messaging middleware vendor and significantly decreases the cost of migrating to a different JMS provider. It also enables reuse of the developer’s knowledge across various JMS providers.

These two messaging paradigms are supported by JMS:

  • Point-to-point
    • The destination is in this case called the message queue.
    • Each message can have exactly one consumer.
    • When a message is successfully consumed from the message queue, it is discarded.
    • There are two types of point-to-point communication:
      • Unidirectional (sending only)—As JMS is highly decoupled, no special features are needed.
      • Bidirectional (request-reply)Is supported via correlation IDs, contained within the request and response messages.
  • Publish/subscribe
    • The destination is in this case called the message topic.
    • The message is consumed by all subscribers (consupmers).
    • The message is discarded from the JMS server only after all subscribers have successfully consumed the message.

These JMS messaging paradigms relate to the type of destination. But it is important to remember that there can be various producers sending messages as well as various consumers receiving messages. So it is a valid configuration to have various producers sending messages to a point-to-point queue. It is also valid to have various receivers consuming messages from a point-to-point queue, but a particular message typically would be consumed by only one of them randomly.

JMS messages can be consumed in two ways:

  • Asynchronously: The consumer registers a special JMS component called a listener to the destination and is able to consume messages as they are processed by the destination. Typically no delay is involved in the message reception. The reception of messages by the listener is done in a separate thread.
  • Synchronously: The consumer is responsible for initiating a read from the message queue or topic. When the consumers polls the destination for the next JMS message, there are two possible scenarios:
    • There is no message in the destination, and the consumer thread is blocked until a new message arrives. This waiting can be terminated by an optional time-out.
    • If the message arrives before the consumer reads it from the destination, it is read immediately. But this message has to wait to be consumed for some time on the destination.

In most cases, registering the listener makes much more sense than explicit synchronous reception, because it decreases delays in our enterprise systems.

Java JMS API

JMS is a Java API; therefore, the JMS standard contains Java types for JMS communication. This API was unchanged for a long time (since Java Enterprise Edition 1.4 was introduced in 2003) and got stuck on version JMS 1.1.

But a recent version of the Java Enterprise Edition 7 standard made a major revision of the JMS API and introduced version JMS 2.0. This version simplifies some highly verbose aspects of the JMS 1.1 API. But as JEE 7 is a relatively new standard, JMS 2.0 wasn’t implemented by all JMS providers so far (for example, ActiveMQ doesn’t support JMS 2.0 as of May 2015). The main concepts from JMS 1.1 remained unchanged in JMS 2.0, and therefore it makes sense to explain the components of both versions together.

JMS Message

The most fundamental piece of the JMS puzzle is of course abstraction for the JMS message. It is represented by the javax.jms.Message interface, which is composed of these three elements:

  • Message headers: A set of predefined types used for identification, routing, and the delivery and reply strategy of the message. These are meant to be used mostly by the JMS infrastructure, but sometimes also by the application code.
  • Message properties: Custom values stored in the message. They are often used as a compatibility layer between different messaging systems. Usage also includes filtering of messages by message selectors.
  • Message body: Contains the actual message.
  • Based on different body types, various JMS message subinterfaces are provided by the JMS API. All are located in the javax.jms package:
  • TextMessage: JMS wrapper for java.lang.String.
  • MapMessage: Name/value pairs, where names are String objects and values are Java primitive types. The order of names is not specified.
  • BytesMessage: Stream of bytes.
  • StreamMessage: Stream of Java primitive types, read sequentially.
  • ObjectMessage: Serializable Java object.

These interfaces are used to transfer messages of different payload types. The best interoperability is often achieved by sending text messages, especially in the most common transport formats such as JSON and XML. Therefore, TextMessage is most often used.

JMS Infrastructure Abstractions

We have mentioned various basic concepts for JMS communication, including the JMS destination, producer, and consumer. All these JMS communication parties need to have certain abstractions so the JMS client can communicate in pure Java to the JMS server.

Because the consumer and producer need to communicate with the JMS destination again and again, it makes sense to keep the connection between them open. When the connection is open, the consumer or producer can perform actions as a unit of work commonly referred to as a session. One connection can have various sessions open, so that we can run numerous units of work at the same time.

As JMS stands for Java Message Service, these abstractions have these Java representations:

  • javax.jms.Destination: Object that represents the JMS queue or topic.
  • javax.jms.MessageProducer: Abstraction representing the object responsible for sending JMS messages.
  • javax.jms.MessageConsumer: Abstraction responsible for a synchronous read of JMS messages.
  • javax.jms.JMSProducer: Enhanced version of MessageProducer introduced in JMS 2.0.
  • javax.jms.JMSConsumer: Enhanced version of MessageConsumer introduced in JMS 2.0.
  • javax.jms.MessageListener: Abstraction responsible for asynchronous reception of JMS messages.
  • javax.jms.Session: Single-threaded context abstraction responsible for sending and receiving messages.
  • javax.jms.Connection: Represents a virtual connection to the JMS provider.
  • javax.jms.JMSContext: JMS 2.0 syntactic sugar for reducing verbosity of JMS 1.1 API. It combines Connection and Session into a single object.
  • javax.jms.ConnectionFactory: Main configuration class of JMS that is responsible for creating connections to the JMS provider. It is usually created at the start of the JMS application.
  • So the usual workflow of a JMS 1.1 developer is to create JMS objects in this order: ConnectionFactory image Connection image Session image MessageProducer/MessageConsumer/Message. With JMS 2.0, the situation is a little enhanced: ConnectionFactory image JMSContext image JMSProducer/JMSConsumer/Message. In the case of a full-blown JEE container, ConnectionFactory would likely be injected.
  • As you may have noticed, this workflow of JMS objects doesn’t include Destination. This object is created or injected separately and is used to create Producer or Consumer. MessageListener is designed to be registered into the MessageConsumer object.

Image Note  This overview of the JMS API is limited, because this book focuses on Spring’s abstractions built on top of a plain JMS API. Therefore, the deeper aspects and additional features are beyond the scope of this book. The best place to start exploring it is the JEE 7 tutorial here: https://docs.oracle.com/javaee/7/tutorial/jms-concepts003.htm#BNCEH.

Java JMS Examples

The most common open source JMS provider is ActiveMQ. But at the time of writing, ActiveMQ doesn’t cover JMS 2.0. We chose HornetQ to show our plain Java JMS examples, because it already supports the new JMS 2.0 standard.

As mentioned, JMS providers are often hosted as separate instances to our application. This is the case for our Java JMS examples. We will be running the HornetQ instance server locally and assume that a recent Java version is installed on the target machine.

To download the HornetQ server, you need to download the ZIP or TAR.GZ bundle from http://hornetq.jboss.org/downloads.html and unpack it. Running the server is easy. Just run the bin/run.sh or bin/run.bat file from the command line. If the proper Java version is installed, the server should start without any problems. To stop the server, we need to execute bin/stop.sh or bin/stop.bat—an easy process.

For example purposes, we don’t need to tweak the default HornetQ configuration, because the example projects are using JMS resources configured by HornetQ out of the box. These examples were tested against HornetQ version 2.4.0.Final.

Synchronous JMS 1.1 Example

  • Our first Java JMS example uses an old JMS 1.1 API that was standardized in 2003. Listing 5-1 shows how to configure JMS 1.1 access.

The JmsConfiguration class encapsulates the JMS provider-specific configuration. It has three fields. The first one is of type InitialContext, which is the starting point for resolving JNDI names. HornetQ is a JBoss technology, which is part of the broader JBoss Application Server portfolio. Therefore, the easiest way to initialize stand-alone HornetQ access is to use JNDI naming abstractions.

The following two fields defined in the JmsConfiguration class are the JMS objects queue and connection. These are annotated by Lombok’s @Getter annotation, which generates getters for these two variables. Lombok is handy when we want to avoid Java verbosity so often highlighted by Java opponents. So they are exposed via getters, and other classes can use them for JMS messaging.

The first method is called init. As its name suggests, it initializes the JMS configuration. We define the map for storing JNDI naming values. The first two JNDI naming values are created based on HornetQ manual instructions in the section “JNDI configuration”: http://docs.jboss.org/hornetq/2.4.0.Final/docs/user-manual/html_single/index.html#d0e1265. The third value, with the key java.naming.provider.url, specifies the address of our HornetQ server. In this case, the server will be located on localhost and port 1099.

When we have the JNDI configuration in the map, we can create a JNDI InitialContext instance and use it to look up JMS resources. As previously mentioned, the default HornetQ configuration is used in our Java JMS examples, so we use the queue name queue/ExpiryQueue and the connection factory name /ConnectionFactory to create the queue and connectionFactory objects. connectionFactory is used to create a connection instance. A queue with the name queue/ExpiryQueue is configured by default in the HornetQ server; we are using it here for simplicity’s sake.

These two instances are already JMS abstractions, so the method init is the only place in our example containing a HornetQ-specific configuration.

The next method is called close. As its name suggests, it is used for closing resources that need to be closed. In this case, we need to close connection and initialContext. But this close method is special, because it belongs to the java.lang.AutoCloseable interface. This interface was introduced in Java 7 as part of a feature often referred to as try-with-resources. It adds syntactic sugar for closing resources after a try block. We will show its use later in this example.

Listing 5-2 shows how to send a message with the JMS 1.1 API.

This class is straightforward. The JMS Connection and Queue instances are passed into init and are used to create MessageProducer and Session instances. Notice that for session creation, AUTO_ACKNOWLEDGE mode is used. You’ll learn more about this later in the chapter, when you explore JMS transaction features. For now, you just need to know that when we read a message from the queue via this session, the message will be discarded from the queue. But in this case, the session is used for sending, which is not affected by this setting.

The second method is called sendMessage. It is obvious that it will be used for sending message, passed as a parameter into it. In this simple example, we are sending a String message, which is wrapped into the textMessage object. Notice that we need the session object to create this message. Finally, the messageProducer object is used to perform the send.

Notice the checked JMSException in the signature of both methods. Listing 5-3 shows the synchronous reception.

Similar to SimpleMessageSender, the SimpleMessageReader class also has an init method that takes connection and queue objects as parameters and creates a session in AUTO_ACKNOWLEDGE mode. After being read, the message is discarded from queue. The second object created in the init method is messageConsumer.

The readMessage method is used for explicit reception of messages. This means that we ask the queue if there is a message. This is done via the messageConsumer.receive call. If the queue doesn’t contain any unread messages, this call waits for the message to arrive to queue. Parameter 5000 specifies the maximum wait time-out in milliseconds. If we don’t specify a time-out, we would block the caller thread potentially forever. Calling messageConsumer.receive returns an object of type Message. To retrieve text from this message, we need to cast it into TextMessage. This is a criticized aspect of the JMS 1.1 API, because explicit casting is considered bad practice.

Another criticized aspect of the JMS API is the need for handling JMSExceptions. The last class shown in Listing 5-4 (belonging to the synchronous JMS 1.1 example) is the main class that uses classes from Listings 5-15-3.

Jms11JndiApplication is the main class of the synchronous JMS 1.1 example. It is annotated by Lombok’s annotation @Slf4j, which gives us a convenient way to define the SLF4J constant for logging. It will be used to output the received message.

The main method uses the Java 7 feature try-with-resources. This means that resources initialized in braces after the try block will be closed in the virtual finally block. This is nice syntactic sugar. The only requirement for this feature is that the resource needs to implement the AutoCloseable interface. The resource in this case is an instance of our class JmsConfiguration from Listing 5-1, and this class is implementing AutoCloseable to close JMS and JNDI resources.

In a try block, we first initialize the jmsConfiguration object and the retrieved connection and queue objects from it. Next we start the JMS connection.

The following phase is to create and initialize SimpleMessageSender and initialize it and send a simple text message. After that, we create a SimpleMessageReader instance, initialize it, and read the message. Finally, the message is written to the console. The output after running may look like Listing 5-5.

Synchronous JMS 2.0 Example

Our example of the JMS 2.0 API usage also relies on a HornetQ server running on localhost. Listing 5-6 shows the HornetQ JMS configuration.

This JMS 2.0 configuration is similar to the JMS 1.1 configuration in Listing 5-1. The only difference is that we create a JMSContext instance instead of a Connection instance. JMSContext is a new abstraction introduced in JMS 2.0. Listing 5-7 shows an example JMS 2.0 sender implementation.

In comparison to Listing 5-2, we don’t need to initialize Session nor MessageProducer instances. Therefore, it’s more suitable to accept jmsContext and queue via the constructor and store them into the class fields.

The sending message is also more concise, because we create JMSProducer via the jmsContext.createProducer call and use the queue object to chain the send call. Notice that the need to handle the checked JMSException disappeared with JMS 2.0 when sending the message. Listing 5-8 shows the implementation of the message reader.

In comparison to Listing 5-3, which uses the JMS 1.1 API, we again don’t need to initialize. Instead, we accept the jmsContext and queue objects via the constructor. When reading the message, we first create a JMSConsumer instance from jmsContext based on the queue object and then perform a potentially blocking call. It is potentially blocking because if the message is already sitting in the queue, we may not need to wait at all. Time-out 5000 is again an optional parameter. If we don’t specify it, we could wait forever.

Unlike with the JMS 1.1 API, there’s no need to cast a message to TextMessage. But it’s done anyway under the hood based on the String.class parameter we specify for the message.getBody() call. This call can throw a checked JMSException, so we are forced to handle it or bubble it up. In this case, the second option is used by the readMessage() method, so the caller of this method will need to take care of it also (see Listing 5-9).

Jms2JndiApplication is again similar to Jms11JndiApplication, but the JMS 2.0 API allowed us to reduce the init calls for messageSender and messageConsumer. Instead, we pass into their constructor the jmsContext and queue objects—nice improvements against the JMS 1.1 API. Listing 5-10 shows the output of this example after running this main class.

Asynchronous JMS Example

Our last pure Java example highlights how to receive JMS messages with an asynchronous listener. But before we dive into JMS constructs, we need to introduce the class that will be used across most examples in this chapter. It is shown in Listing 5-11.

Notice that this example is located in the common project 0500-jms-common. This is because we will use the same class in this pure Java example and in most of the Spring JMS examples. Its purpose is to be the class where we delegate JMS messages after asynchronous reception. It can then be used in integration test suite verification that the JMS message was received. As a responsible programmer, I want to make sure that code snippets in the book are working. But diving into JMS integration testing is beyond the scope of this book, so it’s up to you to explore the testing strategies used to make sure that JMS examples are working.

The SimpleService class is annotated with Lombok’s @Slf4j annotation, which is handy when we need to use the SLF4J logging façade. @Service may be confusing for a pure Java JMS example, but it will be used and scanned as a Spring bean in Spring JMS asynchronous examples. Implementation is trivial—the code just logs the received message to the console.

The next class explained in this example is the JMS listener implementation in Listing 5-12.

SimpleMessageListener expects an instance of SimpleService to be passed via the constructor.

Because it implements the JMS interface, MessageListener must implement the onMessage method. This method has a defined signature by JMS contract, where it has to accept a Message parameter. This is the method of the message listener that will be asynchronously executed when the JMS queue needs to send a new message. It is effectively an implementation of the observer pattern.

Because this method will be called in a separate thread, which is managed by a thread pool created by the HornetQ client library, it is good idea to handle all the exceptions. In this case, we log the stack trace. The message itself is delegated to the simpleService.processText() call after reception.

Listing 5-13 shows the HornetQ JMS configuration.

This JMS configuration is again similar to the configuration of the pure Java JMS 2.0 example in Listing 5-6. This JmsConfiguration expects an instance of SimpleService in the constructor. One additional piece is the creation and registration of the message listener.

First we need to create the JMSConsumer instance from the jmsContext object based on queue. To create the SimpleMessageListener instance, we need the SimpleService instance, as we already know from Listing 5-12. Next we register our listener into jmsConsumer, and the listener is registered. Listing 5-14 shows the main class of this example.

The main method creates a SimpleService instance, and within the try-with-resources block, it initializes the JMS configuration and sends the message. The difference between this main method and the synchronous JMS examples is a lack of explicit reading from queue. In this case, it’s not needed, because we registered the JMS listener against the queue.

The last piece used in this example is SimpleMessageSender, but its implementation is exactly the same as for synchronous JMS examples. Therefore, we skip this listing. When we execute this main class against running the HornetQ server, the message is sent to queue, received by SimpleMessageListener, and logged by SimpleService with output similar to Listing 5-15.

Notice that the logged thread is not the main thread, as with the synchronous examples. This thread is managed by the HornetQ client library thread pool.

Spring JMS

Spring JMS support aims to simplify use, creation, and release of JMS resources. Similar to other Spring abstractions, it decouples the application code from details of the underlying JMS infrastructure and an invasive JMS API. Spring JMS features can be divided into these main areas:

  • Support for creating or obtaining JMS resources
    • JNDI lookup of container-managed JMS resources via the <jee:jndi-lookup> XML element
    • Direct creation of javax.jms.ConnectionFactory and javax.jms.Destination objects
  • Support for sending and synchronous reception of JMS messages via the org.springframework.jms.core.JmsTemplate class
  • Support for asynchronous reception of JMS messages
    • @JmsListener annotation
    • <jms:listener-container> and <jms:listener> XML elements
    • Extraction of JMS message elements via the annotations @Payload, @Header, @Headers
    • To enable Spring JMS annotation configuration, Spring 4.1 introduced the @EnableJms annotation
  • Rich conversion of JMS messages—allows for easy conversion of text messages
    • From/to various transport formats such as JSON or XML
    • Custom JMS message conversion via the interface org.springframework.jms.support.converter.MessageConverter

One important feature of Spring JMS is common for most of the above-mentioned features. It translates the checked javax.jmsJMSException into the unchecked exception org.springframework.jms.JmsException. This allows the application developer to completely decouple the application code from the underlying JMS communication constructs.

As each of the mentioned Spring JMS areas is a broader topic, we will cover them separately with various examples.

Configuring Spring JMS

Spring provides various possibilities for configuring the technologies it supports. The Spring JMS module isn’t an exception.

When our application is hosted on the Java Enterprise Edition container, JMS resources such as ConnectionFactory or Destination typically are managed by the application server. It this case, the JEE standard provides a standard API to discover such managed JMS resources via the JNDI naming service. Spring enables integration with the underlying application service via JNDI by use of the <jee:jndi-lookup> XML element that belongs to the namespace www.springframework.org/schema/jee. Another option is to configure the JNDI service via registering the javax.naming.InitialContext instance.

If our application uses a lightweight container that doesn’t provide JMS resources, or our application is running as a stand-alone Java application, we can use standard dependency injection constructs to register JMS resources into the Spring container.

A relatively new option for creating JMS resources such as Destination and ConnectionFactory is to use the Spring Boot project. It enables autoconfiguration of JMS access via the @EnableAutoConfiguration annotation. Based on this annotation, the Spring container inspects classpath dependencies, and if there is an implementation of the ActiveMQ or HornetQ JMS client, it creates JMS resources with the default configuration.

When we have an implementation of a HornetQ or ActiveMQ server between our classpath dependencies, Spring Boot also configures the embedded JMS server, which can simulate a configuration similar to a full-blown JEE application server with a JMS broker.

Spring Boot 1.2 introduced the @SpringBootApplication annotation, which is a convenient shortcut for the combination of @Configuration, @ComponentScan, and @EnableAutoConfiguration annotations under one umbrella.

Another new configuration option introduced in Spring 4.1 is the @EnableJms annotation. It enables the @JmsListener annotation and also creates a listener container factory that is responsible for managing the asynchronous JMS message reception.

Spring JMS APIs often use plain string names for the JMS destination when sending or receiving messages. But this is not compatible with the JMS API, which requires all the queues or topics to implement the javax.jms.Destination interface. So how can a resolution based on names work? For name resolution of the JMS destination, Spring uses a special bean of type org.springframework.jms.support.destination.DestinationResolver. The signature of this interface is shown in Listing 5-16.

This interface is designed to resolve finding the JMS Destination instance based on the string name of the JMS destination and a given JMS session on the runtime. Spring provides various out-of-the-box implementations:

  • BeanFactoryDestinationResolver: An implementation based on Spring’s BeanFactory that resolves Spring-managed beans of type javax.jms.Destination based on their name.
  • DynamicDestinationResolver: Creates JMS destination instances dynamically, based on given names. This implementation is used by default if not explicitly specified.
  • JndiDestinationResolver: Resolved JMS destination based on given JNDI names.

All these features provide a lot of approaches and combinations for configuring JMS with Spring. Major approaches are covered in subsequent examples.

Image Note  Java examples in earlier sections of this chapter use HornetQ as a JMS provider, because it allowed us to also explore the JMS 2.0 API. To keep the JMS provider consistent, HornetQ is used in most of the following examples. We use ActiveMQ only when Spring Boot support does not easily provide certain features.

Spring JMS XML Configuration Example

The first Spring JMS configuration example uses XML to configure JMS resources, as shown in Listing 5-17.

This Spring XML configuration file uses three namespaces. bean is a standard dependency injection namespace, which is the backbone of any Spring XML configuration file. The jee namespace is meant for integration with Java Enterprise Edition application containers. The namespace util is used for various nonstandard XML configuration constructs.

In this case, we are using the util namespace to define the map of JNDI properties for the HornetQ configuration. We use the same values that are suggested by the HornetQ documentation here http://docs.jboss.org/hornetq/2.4.0.Final/docs/user-manual/html_single/index.html#d0e1265. The same values were used for the plain Java JMS examples. This configuration can talk to the HornetQ server with the default configuration running on localhost.

These properties are needed here because our example is not running on a JEE application server. If that were the case, we would exclude the jndiProperties part completely, as Spring would perform a JNDI lookup against the application server naming service.

The next section is the JNDI lookup itself. We use <jee:jndi-lookup> elements to obtain an instance of the JMS ConnectionFactory and Queue based on their JNDI names and jndiProperties map. Again we would be able to exclude the environment-ref attribute if the JNDI lookup were performed against the JEE container. The results of these operations are two registered beans in Spring’s context with the names connectionFactory and queue. This is handy for abstracting out JNDI resources from the Spring application’s point of view.

Next we create the jmsTemplate instance, which will use the connectionFactory bean as the constructor parameter. This demonstrates how we can use beans that were created by the JNDI lookup. The use of and features behind the JmsTemplate itself are explained later.

The last bean registered in this configuration file is destinationResolver. As we are doing a JNDI lookup for JMS resources, we want to use JndiDestinationResolver instead of DynamicDestinationResolver. This allows us to use the string name queue/ExpiryQueue of the JMS destination when we’ll be sending or receiving JMS messages.

Spring JMS Java Configuration Example

Listing 5-18 shows a possible Java configuration of a Spring JMS application in which JMS resources are created explicitly.

The JmsConfiguration class is annotated with @Configuration to define it as Spring’s configuration class. The @EnableJms annotation is used for enabling JMS annotations and some supporting beans for asynchronous JMS message reception.

The first bean registered in this configuration class is the JNDI InitialContext instance with HornetQ access configuration. It will be used during creation of the JMS resources ConnectionFactory and Queue. These are also created in methods annotated with @Bean so that they will be registered in the context. We create them via a JNDI lookup against the initialContext instance, and therefore we need to explicitly cast them into relevant types. These three methods are the only use of the HornetQ-specific API within the 0506-async-jms-java-config example project.

Finally, we create and register an instance of JmsTemplate based on the injected connectionFactory created in this class also. JmsTemplate can be used for sending and synchronous reception of JMS messages.

Listing 5-19 shows a Spring Boot configuration example.

You may notice that this example has no sign of the org.springframework.jms package. Spring Boot is smart enough to recognize that we have Spring JMS dependencies on the classpath and therefore creates all the JMS resources automatically. It uses opinionated default configurations for the creation. If it finds HornetQ client dependencies on the classpath, it creates a ConnectionFactory out of the box. If we use destinations via String names and not as instances of the Destination interface, DynamicDestinationResolver will create them for us. This Spring Boot approach significantly reduces the amount of configuration needed to create modern enterprise-ready services.

The @EnableScheduling annotation is used because the example uses Spring Scheduling support for sending messages. This is shown in later examples.

Caching JMS Resources

As we mentioned, normal JMS flow is to create JMS resources in this order: ConnectionFactory image Connection image Session image MessageProducer/MessageConsumer/Message. JMS 2.0 provided a little bit more convenience with use of JMSContext, JMSConsumer, and JMSProducer. But these are using older JMS 1.1 abstractions under the hood. Also Spring has its own JMS APIs, so these JMS 2.0 convenience abstractions are not used in Spring code at all. Therefore, when we are thinking about caching JMS resources, it is all about JMS 1.1 abstractions.

If instances from this chain are reused across our application, everything should be fine. But if we were creating all these objects every time we wanted to send or receive a message, performance of our application would suffer. Connection and Session are especially time-intensive to create.

Therefore, Spring provides two implementations of ConnectionFactory that can help cache these resources:

  • SingleConnectionFactory: For every call, ConnectionFactory.createConnection() returns the same instance of Connection and ignores ConnectionFactory.close() calls.
  • CachingConnectionFactory: Builds on top of SingleConnectionFactory functionality and adds caching of Session, MessageProducer, and MessageConsumer instances. The sessionCacheSize property is used to change cache size, which is by default 1. MessageConsumer and MessageProducer instances are cached within the Session instance.

Both implementations are used as wrappers for a JMS provider-specific connection factory. An example of such a configuration is shown in Listing 5-20.

Image Note  As Spring Boot currently doesn’t provide an easy way to combine CachingConnectionFactory with HornetQ (see Spring Boot issue https://github.com/spring-projects/spring-boot/issues/2956), this example uses an ActiveMQ embedded broker.

The @Configuration annotation makes a Spring configuration class from JmsConfiguration. The @EnableJms annotation is used to enable JMS-specific Spring annotations and create some JMS supporting beans.

In this class, we register a Spring bean of type ConnectionFactory. But we don’t expose the ActiveMQ instance directly as usual. Instead, we wrap ActiveMQConnectionFactory into CachingConnectionFactory and expose its instance as a default connectionFactory bean. So everywhere JMS connectionFactory is used, JMS resources are reused to boost performance. Cache size is configured in this case to store 10 Session objects.

Using JmsTemplate

JmsTemplate is the most important class of the Spring JMS module. Its primary focus is on synchronous reception and sending JMS messages. For fine-grained control over these tasks, the JmsTemplate instance stores various configuration options. These are some of the most important:

  • setSessionAcknowledgeMode or setSessionAcknowledgeModeName: Configures acknowledge mode. Possible values are as follows:
    • Session.AUTO_ACKNOWLEDGE or "AUTO_ACKNOWLEDGE"
    • Session.CLIENT_ACKNOWLEDGE or "CLIENT_ACKNOWLEDGE"
    • Session.DUPS_OK_ACKNOWLEDGE or "DUPS_OK_ACKNOWLEDGE"
  • setSessionTransacted: Turns on transactional behavior. When turned on, overrides acknowledge mode configuration.
  • setConnecionFactory: Mandatory connection factory instance as main entry class into JMS client access.
  • Default JMS Destination it will be used against
    • setDefaultDestination: Sets the destination via the Destination/Queue/Topic instance.
    • setDefaultDestinationName: Sets the destination via the name.
  • setReceiveTimeout: Changes the receive time-out for this JmsTemplate instance. It applies for synchronous reception of JMS messages. The value is in milliseconds, and the default value is 0, which means that receive calls will be waiting forever.
  • setPubSubDomain: Switches between publish/subscribe and point-to-point communication. The default is false, which uses point-to-point.
  • setDestinationDesolver: Specifies the class that will be responsible for translating JMS destination names into javax.jms.Destination instances. By default, it uses DynamicDestinationResolver. Spring also provides JndiDestinationResolver.
  • setMessageConverter: Delegates the conversion of JMS messages to a separate bean. By default, it uses SimpleMessageConverter. Converters are further explained in later sections of this chapter.

Obviously, all this configuration stored in single object can raise questions about whether such an object/bean can be used in a multithreaded environment. An important aspect of the JmsTemplate class is that it is thread-safe: we can share and use the JmsTemplate instance across various threads.

When our instance is configured, we can use it to perform actions against the JMS destination or session. It covers various features for JMS message handling:

  • Methods for message sending:
    • send: Sends the message to the default or given destination
    • convertAndSend: Converts the message with the use of MessageConverter first and sends it to the default or given destination
    • sendAndReceive: Sends the message to the default or given destination and receives a reply message asynchronously
  • Methods for synchronous message reception—by default, these operations are blocking a caller thread without time-out:
    • receive: Receives the message synchronously.
    • receiveSelected: Receives the message synchronously with use of a given message selector. Message selectors are used to filter messages based on their filtering expression.
    • receiveAndConvert: Receives the message synchronously and converts it via MessageConverter.
    • receiveSelectedAndConvert: Combines the applied message selectors and message converter.
  • Methods for browsing messages, which means to read messages without discarding them from the JMS destination:
    • browse: Used for browsing all messages.
    • browseSelected: Used for browsing selected messages; selected messages are narrowed down via BrowserCallback<T>.
  • Methods for executing custom actions against the JMS session:
    • execute: Registers the action callback where the Session object will be passed. In this callback, we can perform custom actions against JMS Session.

Another important feature of JmsTemplate is that it translates the checked javax.jmsJMSException into the unchecked exception org.springframework.jms.JmsException.

Releasing JMS resources is also convenient. Connection and Session need to be closed when they are no longer needed by the application. With the JMS API, this has to be done manually by calling the close method of both JMS resources. But when we use JmsTemplate, this releasing of JMS resources is done automatically under the hood.

Listing 5-21 shows how to configure and register JmsTemplate as a Spring bean.

In this case, we will be using JmsTemplate for sending messages and for synchronous message reception. Therefore, if we want to avoid eventual blocking of the thread reading the JMS messages, it is often a good idea to configure the receiveTimeout property. Listing 5-22 shows what the JMS message-sending implementation may look like.

The SimpleMessageSender class is a Spring bean, which is defined by the @Component annotation. @Slf4j is Lombok’s convenient shortcut for creating the SLF4J logger constant. We inject in this Spring bean the jmsTemplate instance via constructor injection.

With the send method, we will be sending a simple JMS message every second. Recurring execution of the send method is ensured by the @Scheduled annotation. In the method body, we first log the message and call the jmsTemplate.convertAndSend method. Conversion is needed from Spring into the Message instance, which is done by SimpleMessageConverter.

Listing 5-23 shows a simple implementation of synchronous message reception.

The SimpleMessageReader class is again a Spring component and injects two dependencies via constructor injection. The first one is the famous jmsTemplate instance. We make sure that synchronous reception wouldn’t be blocked forever by configuring the receiveTimeout property for jmsTemplate. The second dependency is simpleService, which is responsible for processing messages (logging them).

The readMessage method is scheduled every 1.2 seconds to highlight delays when we use synchronous reception. The jmsTemplate.receiveAndConvert call returns Object, so we need to explicitly cast to String. Spring also converts the JMS Message instance under the hood. Finally, we pass the message in text form to simpleService.

The last class in this example is JavaConfigJmsSyncApplication (shown in Listing 5-19). When we run this class as a Java application, we get output similar to Listing 5-24.

Such message reception is also called polling. As we can see from the output, this synchronous message reception brings into the equation delays that might not be suitable for our requirements in most cases. Therefore, it is most common to use asynchronous JMS reception via registering JMS listeners.

Using the MessageCreator Callback

Sometimes we may need to construct a message based on the JMS Session object. In such a case, we need to use JmsTemplate with conjunction with the MessageCreator interface for sending messages. The signature of this interface is shown in Listing 5-25.

This callback interface expects that we implement the createMessage method and provide custom logic for the javax.jms.Message creation based on the javax.jms.Session object. Such custom message construction logic is then passed into JmsTemplate.send and JmsTemplate.sendAndReceive methods while sending the message.

This mechanism allows Spring to manage session instances (for example, for caching) and at the same time provides application developers full flexibility of lower-level JMS APIs. An example of this use is shown in Listing 5-26.

This variant of SimpleMessageSender is similar to Listing 5-20, except for the send method implementation. Here we use the jmsTemplate.send call with the name of the destination as its first parameter. The second parameter of type MessageCreator is created via an anonymous inner class.

To create a message in the MessageCreator callback, we use the JMS API call session.createTextMessage, which returns a TextMessage instance. This is then passed to Spring so that it can send the instance to the queue.

Listening to JMS Messages

Synchronous reception of JMS messages, which can be subject to delays, doesn’t really fit into the current real-time demands from enterprise systems. Therefore, listening to JMS messages is a much more common approach for JMS message consumption. Spring uses the MessageListenerContainer interface to listen to new messages that appear on the JMS destination.

There are two out-of-the-box implementations that use opposite approaches for message reception:

  • SimpleMessageListenerContainer: This implementation uses the standard JMS listener registration MessageConsumer.setMessageListener() under the hood, as we showed in the asynchronous JMS example in Listings 5-11 and 5-12. It is not able to participate in any distributed transactions. Also it is using a constant number of JMS Session objects, so it can’t dynamically adapt to runtime spikes in the message load.
  • DefaultMessageListenerContainer: This is a default implementation that uses a mechanism called long polling. This means that Spring uses MessageConsumer.receive() calls in a stand-alone thread to wait for messages. This thread is blocked, and when the message appears on the destination, it is immediately received and passed to the registered listener in a separate thread. We can configure the number of listener threads via the concurrentConsumers property. To avoid endless waiting, the receive call uses a time-out parameter in conjunction with an endless loop, which makes sure that this listener container is listening nearly all the time. There are various advantages to this approach:
    • The consumer can use transactional features of JMS reception and participate in distributed transactions.
    • It can easily recover from a temporary outage of the JMS destination.
    • We can optionally allow for dynamic adaptation to runtime demand by specifying a maxConcurrentConsumers property different from concurrentConsumers.

Various options exist for creating MessageListenerContainer:

  • Using the <jms:listener-container> XML element
  • Registering the MessageListenerContainer bean directly via the @Bean annotation or <bean> XML element

MessageListenerContainer manages the reception of messages, but we also need to register listeners into it. Again, various options are available for registering application logic that will be handling received messages:

  • Using the <jms:listener> XML element
  • Registering the MessageListenerContainer bean with a list of listener implementations
  • Using the @JmsListener annotation

The @JmsListener annotation was introduced in Spring version 4.1. A method annotated with @JmsListener can be used for flexible injection of JMS message instances or their parts. Spring supports injections of the following:

  • javax.jms.Message or any of its subclasses
  • javax.jms.Session
  • org.springframework.messaging.Message<T> as a generic Spring messaging abstraction for Spring messaging. This abstraction also can be used for other messaging types Spring supports (for example, by Spring Integration, WebSockets, AMQP)
  • JMS message header with the @Header annotation
  • All JMS message headers with the @Headers annotation
  • JMS message payload with the @Payload annotation
  • Let’s demonstrate some of these constructs on simple examples.

Examples of Registering Listener with XML Configuration

Our first example of the Spring JMS listener implementation is shown in Listing 5-27.

This listener implements the javax.jms.MessageListener interface. At the same time, it is also a Spring bean that injects the simpleService instance via constructor injection. This service instance is the same as in Listing 5-10.

SimpleMessageListener has to implement the onMessage method as per the JMS listener contract driven by the interface javax.jms.MessageListener. An instance of javax.jms.Message is passed into this method when the message is received via its JMS destination. To consume this message in our application, we need to cast it into TextMessage and pass it into our simpleService instance. Calling textMessage.getTest() forces us to handle JMSException because it is a checked one.

Listing 5-28 shows how to register this listener in Spring.

This Spring XML configuration uses only the bean namespace for registering Spring beans. There is only one bean of type DefaultMessageListenerContainer registered. We need to configure the mandatory parameter connectionFactory, which was created in a similar way as we already explained earlier in this chapter.

The second mandatory parameter is destinationName. As this example is using Spring Boot as a convenience layer for configuring some boilerplate beans, we use the same queue name as in our other examples.

Finally, we register our simpleMessageListener instance, and the listener is configured.

As you can see, this approach has a downside because the invasive JMS API forces us to handle the checked JMSException and we need to implement the javax.jms.MessageListener interface. But there is better way, which can help us to avoid the invasive JMS API altogether.

One possible question arises: “What if we require an application with more than one listener?” In such a case, it is better to use the <jms-listener> namespace or the most modern @JmsListener annotation introduced in Spring 4.1. These are shown in subsequent examples.

Listing 5-29 shows a possible implementation of the message listener that wouldn’t need to implement javax.jms.MessageListener.

It is a Spring bean, which takes the simpleService instance via constructor injection. SimpleService is the bean from Listing 5-10. Listing 5-30 shows how to use XML configuration to configure this listener.

This configuration uses the bean namespace to enable working with Spring beans, and the jms namespace for JMS container support. The root XML element <jms:listener-container> by default uses the DefaultMessageListenerContainer implementation of the listener container. The attribute destination-resolver uses the bean already shown in Listing 5-16.

The XML subelement <jms:listener> registers the listener instance simpleMessageListener for message reception via its method handleMessage. This method takes the String parameter, so Spring automatically converts the JMS message payload into the String type and injects it as a parameter.

<jms:listener-container> can have various <jms-listener> subelements registered for different JMS destinations.

Examples of Registering Listener with Java Configuration

Registering the listener with a Java configuration looks like Listing 5-31.

This Spring configuration class registers only the DefaultMessageListenerContainer bean. The method registering the bean injects three other Spring beans: containerFactory, queue, and simpleMessageListener. These are needed for configuring the listenerContainer instance after it’s created via the constructor.

Spring 4.1 introduced a new JMS annotation, shown in Listing 5-32.

The method handling messages is annotated with @JmsListener. Spring exposes this method as a JMS endpoint that listens to JMS messages. This annotation has the mandatory attribute destination, which defines the name of the destination it listens to. This is the preferred way of receiving messages nowadays.

Converting JMS Messages

A useful feature provided by the Spring JMS module is message conversion. Although plain JMS provides an option to send Serializable objects as a payload, it’s not a popular solution because of lack of interoperability. So as JMS primary uses a text and sometimes binary payload format, conversion from/to some text-based formats is a pretty standard requirement. This may be a verbose and error-prone exercise with plain Java JMS APIs.

Fortunately, Spring JMS provides an elegant solution. We can convert messages on both sides of the communication channel, because both JmsTemplate (responsible for sending and for synchronous JMS message reception) and AbstractJmsListenerContainerFactory (responsible for listening to the JMS destination and the abstract parent of DefaultJmsListenerContainerFactory and SimpleJmsListenerContainerFactory) allows us to optionally register the MessageConverter interface as their property. Listing 5-33 shows the signature of this interface.

It’s obvious that bidirectional conversion is expected. This message converter instance is used to convert any message that flows through JmsTemplate or AbstractJmsListenerContainerFactory where it was registered. So with this approach, we can easily plug into our JMS communication custom message conversion.

Furthermore, Spring provides a few out-of-the-box implementations for the most common transport formats:

  • org.springframework.jms.support.converter.MappingJackson2MessageConverter: As a wrapper for Jackson library version 2, providing JSON to Java POJOs conversion.
  • MarshallingMessageConverter: For XML marshaling/unmarshalling using org.springframework.oxm.Marshaller and org.springframework.oxm.Unmarshaller.
  • MessagingMessageConverter: Is used in cases where we are also sending custom JMS headers alongside the payload that should be converted. Was introduced in Spring 4.1.

To demonstrate this support, we’ll change the domain model. The domain model will be represented by the POJO shown in Listing 5-34.

This is a simple Java type with two private fields: email and name. The Lombok annotation @Data will generate a constructor with these two parameters, and getters and setters for them.

Example of JSON Conversion

To demonstrate the out-of-the-box JSON conversion that Spring JMS provides, we need to register the MessageConverter bean, as shown in Listing 5-35.

The first bean registered in this configuration class is messageConverter. We use the MappingJackson2MessageConverter implementation that covers JSON conversion. In order to tell Spring that our target format is plain text, we need to configure the targetType property to MessageType.TEXT. The typeIdPropertyName property with the value __type is needed, because Spring needs to know that we are converting text-transport format into/from Java POJOs. The __type value is used as a special JMS header that holds the full Java name of the type being used for JSON conversion. Two underscores should ensure that this header won’t conflict with the custom JMS header that the application may use.

The next two beans are already familiar to us. JmsTemplate is used for sending messages, and DefaultJmsListenerContainerFactory is used for message reception. The difference here is that we inject and set the messageConverter property for both.

Listing 5-36 shows how to send the User object to the JMS destination.

In this class, the JMS message is sent every second, as defined by the @Scheduled annotation. First we create and initialize the User object and log the message about sending it. Next we call the jmsTemplate.convertAndSend() method with the message queue name and the user object as parameters. Spring will use the underlying MappingJackson2MessageConverter to convert our POJO into JSON and embed it into the JMS message payload.

Listing 5-37 shows how to listen to such a message.

The readMessage method is annotated with @JmsListener, so it listens to the JMS queue with the name queue/ExpiryQueue. The parameter of this method is of type User. Spring knows which converter to use to convert the JSON message into a User object. After conversion, it is injected into our logic.

The UserService bean implementation is shown in Listing 5-38.

Handling of the message is simple: print the output to the console. The last class in this 0509-jms-message-converter example is the main class in Listing 5-39.

This is a standard Spring Boot main class. When we run it, the output looks like Listing 5-40.

Note that listening to messages doesn’t introduce any artificial delays into our communication.

Custom Conversion Example

The next example highlights how to transfer and convert messages with a custom conversion format. Listing 5-41 shows conversion into/from our custom payload format.

This class implements the org.springframework.jms.support.converter.MessageConverter interface. Its contract is driven by two methods: toMessage and fromMessage. In toMessage, we receive a Java object that should be converted into javax.jms.Message. We know that it will be of type User; therefore, we cast it into this type. Our custom format is a simple listing of User object properties in the order email and name, delimited by ;. When we have the userString payload constructed, we call session.createTextMessage() to wrap String into javax.jms.Message. The session instance is injected into the toMessage conversion method via a parameter from Spring.

The second method, fromMessage, does the exact opposite. It extracts an injected message of type javax.jms.Message into the String representation userMessage and splits it into an array of values based on the delimiter. Subsequently, a user object is created based on these values and returned to the Spring framework.

This converter component is annotated by @Component, and therefore Spring can use it for configuring JmsTemplate and DefaultJmsListenerContainerFactory. This configuration is shown in Listing 5-42.

This configuration is similar to Listing 5-35. The only difference is that the message converter is our custom converter UserMessageConverter instance.

Sending and listening to messages is exactly the same as in Listings 5-36 and 5-37 from the JSON conversion example.

MessagingMessageConverter Example

The last JMS message conversion example covers MessagingMessageConverter and the annotations for injecting JMS message parts when using the @JmsListener annotation. This message conversion is handy when we need to convert messages and send custom JMS headers alongside the converted message payload.

To explain these capabilities of the Spring 4.1 framework, we again need to amend the requirements. We keep the need to send the User object as a payload. The new requirement is sending the role or the user in the JMS header as a String value. Listing 5-43 shows an implementation of the message sender with this new requirement.

The beginning of this UserMessageSender class should be familiar from previous examples. What’s new is the constant defining the role of the user, which we want to send as the JMS header. The send method is again scheduled to run every second.

In the body of this method, we create the user object that will be sent to the JMS destination and the log data we want to send. Next we use MessageBuilder support that was introduced in Spring 4.0 for constructing JMS messages. The payload will be our user object, and the role header has the admin value. Finally, we call jmsTemplate to send the message we constructed.

Listing 5-44 covers the listener part of this example.

In this case, the readMessage method doesn’t take the String parameter of the message body anymore. When we annotate the user parameter with @Payload, we indicate to Spring that conversion of the JMS message into the User type should be performed. The second injection we do here is injection of the custom JMS header with the name role. This injection is possible via the @Header annotation. Spring is able to identify the header based on the parameter name. Both objects are later passed into the userWithRoleService.processUser() call.

Finally, we inject all the headers of the JMS message into the map and use the header with the name id in the log entry, indicating that the message was received. UserWithRoleService is defined in Listing 5-45.

Again, to simplify the example and at the same time allow our examples to be backed up by the integration test suite (as we want to make sure that code snippets in the book are working), we just log the data received. The interesting configuration difference for the Spring JMS resource beans is shown in Listing 5-46.

The noticeable difference in this snippet is in the messageConverter bean construction. Instead of returning MappingJackson2MessageConverter as our message converter, we wrap it into MessagingMessageConverter as the payload converter, which allows us to append the String value of the role to the messages. Without this construct, String would also try to convert our role from/to the User type. If we didn’t wrap it this way, conversion would obviously fail. So this configuration is needed when we want to inject JMS message headers and at the same time use custom conversion for the JMS message payload.

All these Spring beans are again under the same umbrella of the main Spring Boot class shown in Listing 5-47.

If you paid attention to the previous examples, there’s no surprise here. Listing 5-48 shows the output from running this main class.

Using the Publish/Subscribe JMS Model

At the beginning of this chapter, we mentioned a communication paradigm for sending messages to various subscribers. This important JMS abstraction hasn’t been covered by the examples so far. The following examples will fix that.

The first class introduced is the service where we delegate received messages. It’s shown in Listing 5-49.

This logs the message received alongside the ID of the listener from which the message was received. Listing 5-50 shows the listener used in this example.

SimpleMessageListener1 looks like a listener we’ve already seen. But notice that the JMS destination used here has the name simpleTopic. In the next section, you will see how this JMS destination of type javax.jms.Topic can be configured.

The most obvious difference compared to other listeners in this chapter is the number in the class name. This is intentional; to demonstrate reception of one message by two listeners, we have two listeners in this example project. To keep the examples simple and listings shorter, the second listener is named SimpleMessageListener2 and is an exact copy of SimpleMessageListener1, except 1 is changed to 2.

Configuring JMS Publish/Subscribe with Spring

So far we’ve used the JMS destination queue/ExpiryQueue, because this queue is configured by the HornetQ server by default. But for the publish/subscribe JMS communication model, we need to use the JMS topic instead of the queue. So we need to change the HornetQ default configuration to provide this capability.

At the beginning of this chapter, we explained how to run the HornetQ server with the bin/run.sh or bin/run.bat commands. For the same HornetQ server, we need to append the topic configuration at the end of the configuration file config/stand-alone/non-clustered/hornet-jms.xml, as shown in Listing 5-51.

As we mentioned, ExpiryQueue is configured by default in the HornetQ installation. Every JMS example so far has used this queue. To configure our simpleTopic, we append the <topic> XML element at the end of this configuration file.

The main Spring class is named JmsApplication. It uses the same constructs as the main classes already shown in the previous examples, so we don’t list it. Also, the sender of the messages is similar to other sender classes from this chapter, except instead of queue/ExpiryQueue, we use simpleTopic.

Listing 5-52 shows the Java JMS configuration for the publish/subscribe model for the sender and listeners.

The HornetQ JNDI configuration with the connectionFactory bean is the same as for the point-to-point examples. The publish/subscribe model needs to be turned on for JmsTemplate and DefaultJmsListenerContainerFactory via the pubSubDomain flag on both beans. When we run the JmsApplication class, we will see output similar to Listing 5-53.

Spring Boot JMS Publish/Subscribe Example

There is also a new way of configuring the embedded HornetQ or ActiveMQ JMS broker hosted alongside our application. This configuration can help us reduce the Spring configuration boilerplate to a bare minimum and focus on business logic.

So far, we’ve used HornetQ and our JMS provider. But the latest stable version of Spring Boot doesn’t work with embedded HornetQ in publish/subscribe mode. Therefore, we switch to the embedded ActiveMQ JMS broker for this example. ActiveMQ by default creates the JMS destination if it’s been configured before. Therefore, no special configuration targeted to ActiveMQ is needed.

The only crucial piece of configuration is in Listing 5-54.

This configuration enables publish/subscribe mode on the JMS provider used in this example. It is fair to mention that Spring Boot externalized a huge number of properties for configuring underlying technologies. This way, we can amend the opinionated default behavior of Spring Boot to fit our requirements. In this case, we switched from point-to-point JMS communication to publish/subscribe.

All other classes are the same as for the previous plain Spring Java configuration example, but in this case we don’t need to specify any JMS configuration beans, because Spring will autoconfigure them for us.

Integrating JMS with Higher-Level Messaging Abstractions

The Spring framework introduced in version 4.0 uniform messaging abstractions that can be used across various protocols such as STOMP, WebSockets, AMQP, JMS, or SockJS. These abstractions are located under a new Spring module called spring-messaging. The central abstraction for this support is org.springframework.messaging.Message<T>, which aims to abstract all the messages for the mentioned protocols.

Spring 4.1 created a new JmsMessagingTemplate class, built on top of JmsTemplate, that provides integration with the generic messaging abstraction (see Listing 5-55). Similar to JmsTemplate, JmsMessagingTemplate is also used for synchronous reception and sending of messages. On the other hand, listening to generic messages can be also integrated with the @JmsListener annotation.

This creates three beans. The construction of JmsMessaggingTemplate is similar to JmsTemplate, as it’s using the connectionFactory and messageConverter beans. These two beans are created in exactly the same way as in Listing 5-46 from the example project 0514-jms-message-annotations. All other beans are similar to examples we already covered.

The question may come up: “What is the benefit of using JmsMessagingTemplate over JmsTemplate, when there isn’t a big difference between their usage and configuration?” It’s true that using JmsMessagingTemplate isn’t much different and doesn’t bring any syntactic sugar for us, but bear in mind that JmsMessagingTemplate represents integration with the new Spring 4.0 generic type for messaging, org.springframework.messaging.Message<T>. Therefore, it can be essential when we need to integrate JMS with other Spring-supported messaging. For example, imagine a requirement to receive a message from the JMS destination synchronously and push it to a browser via WebSockets. Reading the message from the JMS destination via JmsMessagingTemplate allows us to read it as type org.springframework.messaging.Message<T> and send it to a web socket without any conversion.

In addition, the Spring Integration module is highly dependent on using org.springframework.messaging.Message<T> for sending via its pipes. Therefore, JmsMessagingTemplate can be useful in some use cases involving Spring Integration and JMS.

Summary

This chapter explained how JMS messaging communication differs from other types of communication protocols and approaches. In a pure Java example, we explored how to use the older JMS 1.1 and newer JMS 2.0 APIs as well as synchronous vs. asynchronous reception. These Java examples enabled us to compare the advantages of Spring abstractions and the significant reduction of plain JMS API verbosity.

Various possible Spring JMS configurations were explained based on plain XML or the jms namespace. Java configuration and Spring Boot also were covered. Then we presented the features of JmsTemplate and how it can be used for synchronous JMS message reception as well as for sending messages.

To receive JMS messages from JMS destinations immediately, you learned about Spring’s MessageListener abstractions and how to configure them with XML and Java configuration.

The important MessageConverter abstractions allow us to use various standard and custom transport formats. We also dived into various new Spring 4.1 features such as @JmsListener, @Header, @Headers, @Payload, and JmsMessagingTemplate.

Near the end of the chapter, we also showed how to use the publish/subscribe JMS model.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.63.95