The Java Messaging Service (JMS) API, which came into existence a decade ago, was an instant hit in the area of distributed messaging and enterprise applications. The demand for easier and standardized integration with the Messaging Middleware was the drive for creating this technology. Although the API is straightforward and relatively easy, Spring has taken a step forward in creating an even easier framework around it. This chapter will introduce you to the JMS basics and explains Spring’s take on JMS and how it simplified developers’ lives even further.
JMS provides a mechanism of communication between applications without being coupled. The applications can talk to each other via the JMS provider without even knowing who is on the other side of the fence.
For example, a PriceQuote
service can publish price
quotes onto a channel, expecting someone to receive them. However, the
service may not be aware of any consumers that would consume these quotes. Similarly, the
consumers may not have any clue about the source of the data or the producers. In a way, they
are hidden behind the walls, just interacting via destinations or channels. Producers and
consumers don’t have to bother about each other’s existence as long as they know their
expected messages are delivered or received, respectively.
There are three important moving pieces in JMS: the service provider, the producer, and the consumer.
The service provider is the central piece, while the other two are either data producers or consumers. The architecture is based on hub and spoke; that is, the server maintains a central ground like a hub while producers and consumers act like the spokes.
The overall responsibility in this architecture is taken by the service provider. The provider makes sure all the consumers receive the messages, while at the same time, all producers are able to publish the messages. I would recommend picking up a book on the subject to understand JMS in detail.
In JMS, there are primarily two messaging models: one is point-to-point (or P2P, as it is sometimes called) and the other is publish-subscribe (Pub/Sub). There are specific use cases where you should use these models. Although both differ fundamentally in delivery and reception mechanisms, a unified API is used to access both the models.
In point-to-point (P2P) mode, a message is delivered to a single consumer via a destination—it’s for a specific consumer only. A publisher publishes a message onto a destination, while a consumer consumes the message off that destination. This is analogous to a birthday greeting card sent to you—only to you!
When a message is published in P2P mode to a queue, one and only one consumer can receive that message. Even if there are hundreds of consumers connected to that queue, still only one consumer will get that message delivered. Of course, you never know who’s the lucky winner though!
The destination in a point-to-point model is called a Queue.
On the other hand, if a message is published to a destination, there could be several subscribers each receiving a copy of the message. The publisher obviously publishes the message once. All the subscribers interested will head to the same destination to consume that message. The JMS Provider makes sure each of the subscribers receives a copy of the message. This is analogous to a car dealer sending offers to you and possibly thousands of others!
The destination in a Pub/Sub model is called a Topic.
The Spring framework has simplified the JMS API quite a bit. All we need to do is follow a simple template software pattern in order to understand the Spring JMS usage. Before we go into code examples, let me explain the high-level classes and their usage.
The core of Spring JMS revolves around a single class, JmsTemplate
. If
you understand this one class in the Spring JMS framework, you are good to go! The JmsTemplate
class is heavily used in Spring JMS for both message
consumption and production. The only case JmsTemplate
is not used is for
asynchronous message consumption. We will discuss this case a bit later in the chapter.
First, let’s start digging into the JmsTemplate
class.
We can use the JmsTemplate
class for both publishing
and consuming the messages. The template class hides all the plumbing needed for connecting
to a provider. It also abstracts away lots of boilerplate code for publishing or receiving
the messages. It manges the resources, such as connections, behind the scenes.
When using Spring JMS, the usual way is to configure a JmsTemplate
instance (similar to a normal bean) and inject into our bean. We can set some Qualitiy of
Service (QOS) properties such as priority, and delivery mode on the class, but the ConnectionFactory
property is mandatory. The
ConnectionProperty
provides the enough information about the service
provider to connect to it. There are other properties that are required for more features
(such as defaultDestination
and receiveTimeout
parameters), which we will see in the following sections.
The template works by using callsbacks—callbacks such as MessageCreator
for the message creation, SessionCallback
for associating with a Session
, and ProducerCallback
for creating a
message producer.
Once configured and instantiated, the JmsTemplate
instance is thread-safe, so it can
be shared across different beans without having to worry about session
corruption.
There are primarily two ways of creating or instantiating
JmsTemplate
. One is to create the class by using a normal
new
operator, while the other is to declare and configure it in a
configuration file. Both of them require us to pass in a ConnectionFactory
property, though.
The TradePublisher
class, shown here, uses a
JmsTemplate
for its message publication. The JmsTemplate is
instantiated by passing in the constuctor argument - a connectionFactory
instance.
public
class
TradePublisher
{
private
String
destinationName
=
"justspring.core.jms.trades"
;
private
JmsTemplate
jmsTemplate
=
null
;
public
void
setConnectionFactory
(
ConnectionFactory
connectionFactory
)
{
// create the JmsTemplate using the injected ConnectionFactory
jmsTemplate
=
new
JmsTemplate
(
connectionFactory
);
}
...
}
Once we have the TradePublisher
created with a
JmsTempate
instance, the next step is to use this instance to publish
our messages. There is a send
method on the
jmsTemplate
instance that we should invoke to publish a message. There
are basically two variants of the send
method—one that takes in a
destination and another that doesn’t—the MessageCreator
callback is
mandatory though. We’ll go through them in a few minutes.
The following snippet shows the usage:
public
class
TradePublisher
{
// access the template when publishing the message
public
void
publishTrade
(
final
Trade
t
)
{
jmsTemplate
.
send
(
destinationName
,
new
MessageCreator
()
{
@Override
public
Message
createMessage
(
Session
session
)
throws
JMSException
{
ObjectMessage
msg
=
session
.
createObjectMessage
();
msg
.
setObject
(
t
);
return
msg
;
}
});
}
}
The only requirement in this case is to wire the ConnectionFactory
to the publisher in your config so it can be injected when
the bean is created. Of course, you need to instantiate the JmsTemplate
object, using this connection factory. Here’s the XML file:
<bean
name=
"tradePublisher"
class=
"com.madhusudhan.jscore.jms.TradePublisher"
>
<property
name=
"connectionFactory"
ref=
"connectionFactory"
/>
</bean>
<bean
id=
"connectionFactory"
class=
"org.apache.activemq.ActiveMQConnectionFactory"
>
<property
name=
"brokerURL"
value=
"tcp://localhost:61616"
/>
</bean>
As we discussed earlier, there is another way to get a hold of
JmsTemplate
in our bean. We can wire the template with a connection
factory and inject into our bean—this is all done in the config. If you carefully observe,
all we did was moving the creation of template from our application to the framework.
<bean
name=
"tradePublisher"
class=
"com.madhusudhan.jscore.jms.TradePublisher"
>
<property
name=
"jmsTemplate"
ref=
"jmsTemplate"
/>
</bean>
<bean
id=
"jmsTemplate"
class=
"org.springframework.jms.core.JmsTemplate"
>
<property
name=
"connectionFactory"
ref=
"connectionFactory"
/>
</bean>
<bean
id=
"connectionFactory"
class=
"org.apache.activemq.ActiveMQConnectionFactory"
>
<property
name=
"brokerURL"
value=
"tcp://localhost:61616"
/>
</bean>
Your bean will have a reference to JmsTemplate
via a
setter method. The TradePublisher
with a wired-in
template as shown in the following snippet:
public
class
TradePublisher
{
private
String
destinationName
=
"justspring.core.jms.trades"
;
private
JmsTemplate
jmsTemplate
=
null
;
public
void
setJmsTemplate
(
JmsTemplate
jmsTemplate
)
{
this
.
jmsTemplate
=
jmsTemplate
;
}
public
JmsTemplate
getJmsTemplate
()
{
return
jmsTemplate
;
}
public
void
publishTrade
(
final
Trade
t
)
{
...
}
}
The JmsTemplate
is injected
into the related class that requires publishing or receiving messages
from a JMS server, in this case the TradePublisher
. As you can see, the ConnectionFactory
is already wired to the
JmsTemplate
declaratively.
As we mentioned earlier, for sending messages, the
JmsTemplate
exposes these three
methods:
send
(
MessageCreator
msgCreator
)
//Default Destination
send
(
String
destinationName
,
MessageCreator
msgCreator
)
send
(
Destination
destinationName
,
MessageCreator
msgCreator
)
The first one assumes that messages should end up in a default destination, while the
next two methods specify the destination. The MessageCreator
argument is a callback hook so we can create the appropriate JMS
Message
. The callback has one method, createMessage(Session session)
. Using this session object, we
create one of the types of Message
with data.
Check out the publishTrade
method implementation
above in the TradePublisher
class now to get a complete
picture.
In the JMS world, all messages should be declared as one of the predefined five types
before attempting to publish or receive them. The five types are TextMessage
, BytesMessage
, ObjectMessage
, StreamMessage
,
and MapMessage
. You should consult the JMS API for the
details of these messages.
Let’s develop an example to understand the mechanics of message publication.
The StudentEnroller
is the publishing component in
the workflow that would be invoked whenever a Student
enrolls for a course. The aim is to publish the enrolled
onto a JMS destination so the
parties interested will consume and act on them (probably to lure the student to take one of
their subjects or to provide info about the library facilities, cafeteria, or
unions).Student
The following snippet shows the code:
public
class
StudentEnroller
{
private
String
destinationName
=
null
;
private
JmsTemplate
jmsTemplate
=
null
;
public
void
publish
(
final
Student
s
)
{
getJmsTemplate
().
send
(
getDestinationName
(),
new
MessageCreator
()
{
@Override
public
Message
createMessage
(
Session
session
)
throws
JMSException
{
TextMessage
m
=
session
.
createTextMessage
();
m
.
setText
(
"Enrolled Student: "
+
s
.
toString
());
return
m
;
}
});
}
// setters and getters for the
//jmsTemplate and destinationName variables
...
}
The StudentEnroller
has two
instance variables: jmsTemplate
and
destinationName
. The JmsTemplate
is used to publish the messages
and the destinationName
defines the
location (queue or topic) where the messages should be sent.
The StudentEnroller
instance is
injected with a JmsTemplate
instance
and a destinationName
value at
runtime. The publish
method on the StudentEnroller
instance is invoked when a
client wishes to publish a Student
message.
In order to create a JMS Message, we should use Spring’s MessageCreator
implementation. The template
class expects a new instance of this callback with createMessage()
being implemented:
public
Message
createMessage
(
Session
session
)
throws
JMSException
{
TextMessage
m
=
session
.
createTextMessage
();
m
.
setText
(
"Enrolled Student: "
+
s
.
toString
());
return
m
;
}
The appropriate message is created using the session, in
this case a TextMessage
.
Before injecting the JmsTemplate
into StudentEnroller
, it is fully configured in the
Spring’s XML file. The template also has a variable called connectionFactory
that needs to be defined and
referenced. The connection factory is specific to individual JMS
Providers. I am going to use ActiveMQ as the JMS Provider for the rest
of the chapter, but you can use any other providers that are
JMS-compliant (so the code should work without tweaking for each
provider).
The ActiveMQ connection factory requires a brokerUrl
property to be set as shown here:
<bean
id=
"jmsTemplate"
class=
"org.springframework.jms.core.JmsTemplate"
>
<property
name=
"connectionFactory"
ref=
"connectionFactory"
/>
</bean>
<bean
id=
"connectionFactory"
class=
"org.apache.activemq.ActiveMQConnectionFactory"
>
<property
name=
"brokerURL"
value=
"tcp://localhost:61616"
/>
</bean>
The last piece is the declaration of the StudentEnroller
bean itself in the config file wired with the two properties jmsTemplate
and destinationName
.
<bean
name=
"studentEnroller"
class=
"com.madhusudhan.jscore.jms.pub.StudentEnroller"
>
<property
name=
"destinationName"
value=
"topic.STUDENTS"
/>
<property
name=
"jmsTemplate"
ref=
"jmsTemplate"
/>
</bean>
Write a simple test client that creates a Spring container by loading the jms-pub-beans.xml config file.
public
class
StudentEnrollerClient
{
private
ApplicationContext
ctx
=
null
;
private
StudentEnroller
enroller
=
null
;
public
StudentEnrollerClient
()
{
ctx
=
new
ClassPathXmlApplicationContext
(
"jms-pub-beans.xml"
);
enroller
=
(
StudentEnroller
)
ctx
.
getBean
(
"studentEnroller"
);
}
public
void
publishStudent
(
Student
s
)
{
System
.
out
.
println
(
"Publishing Student.."
);
enroller
.
publish
(
s
)
;
}
public
static
void
main
(
String
[]
args
)
{
StudentEnrollerClient
client
=
new
StudentEnrollerClient
();
Student
s
=
new
Student
();
client
.
publishStudent
(
s
);
}
}
The
client gets ahold of the StudentEnroller
instance to
invoke the publish method with a new Student
object. The
StudentEnroller
was already injected with the
dependencies such as jmsTemplate
and destinationName
(see the config declaration).
Now, run the ActiveMQ server. The server is started and running on our local machine (hence, localhost) on a default port 61616.
Run the client and if all is set correctly, we should see a message landing up in the ActiveMQ destination.
Publishing Student.. Successfully published student message to topic.STUDENTS
Note that the ActiveMQ creates the required destinations (in this
case the topic.STUDENTS
topic) on demand if they do
not exist.
If you wish to send the messages to a default destination, use the JmsTemplate
’s send(MessageCreator
msgCreator)
method variant. As the method signature suggests, it does not take
any parameter for destination—the message is destined to go to a default destination. This
default destination, however, needs to be wired in the config file.
In order to create a destination in the config file, we need to use a concrete
implementation of javax.jms.Destination
class—usually provided by the JMS
providers. In the case of ActiveMQ, the destination is
org.apache.activemq.command.ActiveMQTopic
which is declared as a bean
first
here:
<bean
id=
"defaultDestination"
class=
"org.apache.activemq.command.ActiveMQTopic"
>
<constructor-arg
value=
"topic.DEFAULT_STUDENTS"
/>
</bean>
Then add a property defaultDestination
in our JmsTemplate
, making the reference point to the above
destination:
<bean
id=
"jmsTemplate"
class=
"org.springframework.jms.core.JmsTemplate"
>
<property
name=
"connectionFactory"
ref=
"connectionFactory"
/>
<property
name=
"defaultDestination"
ref="defaultDestination" /></bean>
Now
that the moving parts are glued together, see the following publishing code snippet. Note
that the send
method does not have any reference to the
destination.
public
void
publishToDefautDestination
(
final
Student
s
)
{
getDefaultDestinationJmsTemplate
().
send
(
new
MessageCreator
()
{
@Override
public
Message
createMessage
(
Session
session
)
throws
JMSException
{
....
}
});
}
By default, the JmsTemplate
assumes that your
messaging mode is point-to-point and hence the destination to be a Queue
. However, if you wish to change this mode to pub/sub, all you are
required to do is wire in a property called pubSubDomain
,
setting it to true.
<bean
id=
"jmsTemplate"
class=
"org.springframework.jms.core.JmsTemplate"
>
...<property
name=
"pubSubDomain"
value=
"true"
/>
</bean>
This way, we are expecting the messages to be published onto to a Topic
. Remember to create the queues or topic in the config as
shown here (for ActiveMQ):
<!- Queue--> <bean id="defaultDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="queue.STUDENTS" /> </bean> <!-- Topic --> <bean id="defaultDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="topic.STUDENTS" /> </bean>
Using JmsTemplate
makes consuming the messages
simple. However, a bit of complexity arises when the reception modes are considered. The two
modes in which you can receive messages are synchronous and
asynchronous modes.
In synchronous mode, we will not be able to process any other actions until and unless we receive at least one message from the server. The thread that calls the receive method will not return, but waits indefinitely to pick up the message. As this is more of a single-theaded nature, I recommend not use this mode unless you have a strong case. Should you have no alternatives other than using the synchronous receive method, use it at least by setting a timeout so the waiting thread can gracefully exit after the timeout.
In the asynchronous mode, the client will let the provider know that it would be interested in receiving the messages from a destination. When a message arrives at that given destination, the provider checks the list of clients interested in that message and sends the message to those list of clients.
We use JmsTemplate
’s receive
method to consume messages in this fashion.
The receive method on the templace instance takes in a destination so it can start
consuming from there. The JobsReceiver
shown here
connects to a JMS provider and tries to receive messages (the instance has been injected
with a JmsTemplate
object).
public
class
JobsReceiver
{
private
JmsTemplate
jmsTemplate
=
null
;
private
String
destinationName
=
null
;
public
void
receiveMessages
()
{
Message
msg
=
getJmsTemplate
().
receive
(
destinationName
);
System
.
out
.
println
(
"Job Received: "
+
msg
);
}
...
}
The receive method waits until the destination holds at least a message. As explained
earlier, the receive method is a blocking call, which may waste CPU cycles if no message
exists in the queue. So, use the receiveTimeout
variable
with the appropriate value. The receiveTimeout
is an
attribute on JmsTemplate
that needs to be declared in
config file, which is shown here:
<bean
id=
"jmsTemplate"
class=
"org.springframework.jms.core.JmsTemplate"
>
<property
name=
"connectionFactory"
ref=
"connectionFactory"
/>
<property
name=
"receiveTimeout"
value=
"2000"
/>
...</bean>
In the above snippet, the JobsReceiver
will timeout after two seconds if
it does not receive any messages in that time period.
We can also receive messages from a defaultDestination
. As we did earlier, what we have to do is wire the jmsTemplate
with a defaultDestination
property. Simple!
<bean
id=
"jmsTemplate"
class=
"org.springframework.jms.core.JmsTemplate"
>
<property
name=
"defaultDestination"
value=
"topic.DEFAULT_JOBS"
/>
...</bean>
Receiving messages asynchronously is a bit different, let’s see how we can do that.
In order to receive the messages asynchronously, we have to do a couple of things:
Create a class that implements the MessageListener
interface.
Wire a Spring JMS Container in your spring beans XML file with a reference to your listener. We will talk about Containers in a minute.
So, the asynchronous client must implement JMS API’s interface MessageListener
. This interface has one method called onMessage
that must be implemented by your class. The BookOrderMessageListener
, for example, is a simple class that implements the
MessageListener
. The method does not do much except
print out the message to the console.
public
class
BookOrderMessageListener
implements
MessageListener
{
@Override
public
void
onMessage
(
Message
msg
)
{
System
.
out
.
println
(
"Book order received:"
+
msg
.
toString
());
}
}
The second part is the task of wiring the containers.
Don’t confuse these containers with the ApplicationContext
or BeanFactory
containers.
These containers are utility classes provided by the framework used in clients that are
destined to receive messages. The containers are simple yet powerful classes that hide away
all the complexities of connections and sessions. They are responsible for fetching the data
from the JMS Provider and pushing it to your listener. They do this by having a reference to
ConnectionFactory
(and hence JMS Provider) and a
reference to our listener class.
Now, let’s looks at the workings in detail.
Wire up the container and the listener as shown in the following code snippet. Define an
instance of DefaultMessageListenerContainer
and inject it
with a connectionFactory
, destination
, and messageListener
instances.
Note that the messageListener
class refers to our listener class.
<bean
id=
"bookOrderListener"
class=
"com.madhusudhan.jscore.jms.async.BookOrderMessageListener"
/>
<bean
id=
"defaultListenerContainer"
class=
"org.springframework.jms.listener.DefaultMessageListenerContainer"
>
<property
name=
"connectionFactory"
ref=
"connectionFactory"
/>
<property
name=
"destination"
ref=
"defaultDestination"
/>
<property
name=
"messageListener"
ref=
"bookOrderListener" /></bean>
Once
the wiring is done, fire up the client, which loads up the above beans. It would start up
the messageListener
instance, which waits to receive
messages from the JMS server.
Publish a message onto the destination and we can see that message popping up at the
messageListener
client.
We have seen the usage of DefaultMessageListenerContainer
in the
previous section. Spring provides three different types of containers
for receiving messages asynchronously, including the DefaultMessageListenerContainer
. The other two
are SimpleMessageListenerContainer
and ServerSessionMessageListenerContainer
.
The SimpleMessageListenerContainer
is basically the
simplest of all and is not recommended for production use. On the other hand, ServerSessionMessageListenerContainer
is one level higher than
DefaultMessageListenerContainer
in complexity as well
as features. This works if we want to work with JMS sessions directly. It is also used in
the situation where XA transactions are required.
The DefaultMessageListenerContainer
is well-suited
for most of the applications and does allow you to participate in
external transactions. Obviously choose the appropriate one based on
your application’s requirement.
One of the requirements when publishing a message is to convert your domain object into
five predefined JMS message types. We cannot simply publish or receive domain objects such
as Trade
or Order
,
even if they are properly serialized Java Objects. So, if you wish to publish your domain
objects, you need to convert them into the appropriate JMS Message
type.
See the following publish method shown here:
public
void
publish
(
final
Student
s
)
{
getJmsTemplate
().
send
(
getDestinationName
(),
new
MessageCreator
()
{
@Override
public
Message
createMessage
(
Session
session
)
throws
JMSException
{
TextMessage
m
=
session
.
createTextMessage
();
m
.
setText
(
"Enrolled Student: "
+
s
.
toString
());
return
m
;
}
});
}
...
}
Did you notice that in order to send a message, we need to work with a session (to create appropriate object) and message creators (which provides us the session)? It’s not that clean, is it?
The publish method can be enhanced to lose its wrinkles and look smart as shown—with the help of converters. See the enhanced method shown here:
public
void
publish
(
final
Student
student
)
{
jmsTemplate
.
convertAndSend
(
destinationName
,
student
);
}
The convertAndSend
method will hide away all the extra boiler plate
code. We can acheive this goal by using Spring’s MessageConverter
s. They
do the conversions easily without us having to sweat it anymore. Once the converter is
plugged into the JmsTemplate, we do have to worry about the conversions. Of course, we have
to write one of the converters to begin with though!
Let’s see how the converters work.
First, we should create a class that implements the MessageConverter
interface. This interface has
two methods: fromMessage
and toMesage
methods. As the names indicate, we
code these methods either to convert a JMS message to a domain object or
vice versa.
The following code shows a typical converter used for Account
objects:
public
class
AccountConveter
implements
MessageConverter
{
@Override
public
Object
fromMessage
(
Message
msg
)
throws
JMSException
,
MessageConversionException
{
Account
t
=
(
Account
)
((
ObjectMessage
)
msg
).
getObject
();
System
.
out
.
println
(
"fromMessage: "
+
msg
.
toString
());
return
t
;
}
@Override
public
Message
toMessage
(
Object
obj
,
Session
session
)
throws
JMSException
,
MessageConversionException
{
ObjectMessage
objMsg
=
session
.
createObjectMessage
();
objMsg
.
setObject
((
Account
)
obj
);
System
.
out
.
println
(
"toMessage: "
+
objMsg
.
toString
());
return
objMsg
;
}
}
In
the fromMessage
method, the Account
object is grabbed from JMS ObjectMessage
and converted to the actual domain object. Whereas, in the
toMessage
method, we use the passed-in
session
object to create an ObjectMessage
and push the domain object by using setObject
method.
Now that we have created an Account converter, the next step is to wire it into the JmsTemplate object.
<bean
id=
"
accountConverter" class="com.madhusudhan.jscore.jms.convert.AccountConverter"/><bean
id=
"jmsTemplate"
class=
"org.springframework.jms.core.JmsTemplate"
>
<property
name=
"messageConverter"
ref=
"accountConverter"
/>
....</bean>
In the publisher and receiver code, you need to change the send
and receive methods to convertAndSend()
and receiveAndConvert()
so the converter bean gets
used at publishing and receiving end.
//Publisher public void publish(final Student student) { jmsTemplate.convertAndSend(destinationName, student); } //Receiver public void receive() { Object o = jmsTemplate.receiveAndConvert(destinationName); }
Whenever we publish an Account
message, the jmsTemplate
uses the
converter to convert the Account
to
ObjectMessage
. Similarly when
receiving, the template calls the converter to do the reverse
conversion. This way, you write the converter once and use it everywhere
and at all times.
We have seen Java Messaging in action in this chapter. We briefly touched the subject of
JMS and delved into using Spring’s JmsTemplate
class. We
learned how we can publish our messages using the template class. We also saw how we can
receive messages synchronously and asynchronously using Spring’s framework classes called
Message Containers. Lastly, we touched on the converters that would convert JMS message types
to our business domain types and take away some more boilerplate code.
The next chapter deals with persistence and retrieval of Data, using Spring’s JDBC and Hibernate support.
18.191.150.231