8.2. JMS(Java Message Service)¶
Table of Contents
- Overview
- How to use
- Settings that are common for both sending and receiving messages
- A method to synchronously send messages
- A method to receive messages asynchronously
- Basic asynchronous receiving
- Fetching header information of messages
- Process results after asynchronous receiving are sent as messages
- When messages which are asynchronously received are to be restricted
- Input check for the messages which are received asynchronously
- Transaction control
- Exception handling at the time of asynchronous receiving
- A method wherein the messages are synchronously received
- Appendix
8.2.1. Overview¶
This chapter explains how to send and receive messages which use components for JMS linking of JMS API and Spring Framework.
8.2.1.1. What is JMS¶
Note
Use of JMS1.1 is assumed in this guideline.
- Delivery model
Delivery model consists of two models - Point-to-Point (PTP) and Publisher-Subscriber (Pub/Sub).A major difference between two models is in whether the sender and recipient ratio is 1:1 or 1:many, and should be selected based on the use.
- Point-To-Point (PTP) model
PTP model consists of 2 clients wherein one of the clients (Producer) sends a message and the other client (Consumer) alone receives that message.Destination of the message in PTP model is called as a Queue.Producer sends the message to the Queue and the Consumer fetches the message from the Queue, and the process is carried out.Message is fetched from the consumer or when the message reaches expiry period, the message is deleted from the Queue.
- Publisher-Subscriber (Pub/Sub) model
Pub/Sub model consists of 2 clients wherein one of the clients (Publisher) issues (Publishes) the message and delivers that message to multiple other clients (Subscribers).Destination of the message in Pub/Sub model is called as a Topic.Subscriber raises a subscription request for the Topic and the Publisher issues a message in Topic.The message is delivered to all the subscribers who have raised a request for subscription.This guideline explains how to implement PTP model which is widely used in general.
- A method to send messages
There are two types of processes for message sending - Synchronous sending method and asynchronous sending method for sending the messages to Queue or Topic however JMS1.1 supports only synchronous sending method.
- Synchronous sending method
Message is processed and sent by explicitly calling a function to send messages.Subsequent processing is blocked in order to wait until the response is received from JMS provider.
- Asynchronous sending method
Message is processed and sent by explicitly calling a function to send messages.Subsequent processing is continued since it is not necessary to wait for the response from JMS provider.For the details of asynchronous sending method, refer Java Message Service(Version 2.0)”7.3. Asynchronous send”.
- Message receiving method
There are two types of methods for receiving messages - synchronous receiving method and asynchronous receiving method while implementing the process for receiving messages in Queue or Topic.As described later, since use cases of synchronous receiving methods are limited, generally asynchronous receiving methods are widely used.
- Asynchronous receiving method
When the message is received by Queue or Topic, the processing for the received message is initiated.Since the processing for one message is initiated without terminating the processing for the other message, it is suitable for parallel processing.
- Synchronous receiving method
Receiving the message and related processing is initiated by explicitly calling the function which receives the message.The function which receives the message waits till the message is received when the message does not exist in Queue or Topic.Hence, the waiting period for the message must be specified by configuring the timeout value.As an example of synchronous receiving of message, it can be used when the message accumulated in the Queue, in the Web application is to be fetched and processed at any time like screen operation etc or when the messages are to be processed periodically in a batch.
Structure Description Header Control information of message like destination and identifier, extension header (JMSX) of JMS, JMS provider specific header and application specific header are stored for JMS provider and application. Property Control information to be added to header is stored. Payload Message body is stored.As data types, 5 types of message types namelyjavax.jms.BytesMessage
,javax.jms.MapMessage
,javax.jms.ObjectMessage
,javax.jms.StreamMessage
andjavax.jms.TextMessage
are offered.ObjectMessage
is used while sending a JavaBean.In such a case, JavaBean must be shared between the clients.
8.2.1.2. Using JMS¶
Note
For JMS, Java API is standardized however physical protocols of messages are not standardized.
Note
Since JMS implementation is incorporated in Java EE server as a standard, it can be used as a default (restricted while using JMS provider incorporated in Java EE server), however JMS must be separately implemented in Java EE server like Apache Tomcat wherein JMS is not incorporated.
8.2.1.3. Using JMS which use component of Spring Framework¶
spring-jms
- It offers a component for the messaging which use JMS.By using the components included in this library, low level JMS API calling becomes unnecessary and the implementation becomes simplified.
spring-messaging
can be used.
spring-messaging
- It offers a component to abstract the infrastructure function which is required for creating application of messaging base.It consists of a set of annotations to associate with the message and the method for processing the same.By using the components included in the library, implementation style of messaging can be matched.
spring-jms
, implementation method can be combined by using spring-messaging
.spring-messaging
as well.javax.jms.ConnectionFactory
- An interface for creating a connection with JMS provider.It offers a function to create a connection to JMS provider from the application.
javax.jms.Destination
- An interface for indicating the address (Queue or topic).
javax.jms.MessageProducer
- An interface for sending messages.
javax.jms.MessageConsumer
- An interface for receiving messages.
javax.jms.Message
- An interface which shows the message that retains header and body.Messages are sent and received by implementation class of the interface.
org.springframework.messaging.Message
- An interface which abstracts messages handled by various messaging systems.It can also be used in JMS.As mentioned earlier, basically
org.springframework.messaging.Message
offered by spring-messaging is used to comply with implementation method of messaging.However,javax.jms.Message
is used when it is preferable to useorg.springframework.jms.core.JmsTemplate
.
org.springframework.jms.core.JmsMessagingTemplate
andorg.springframework.jms.core.JmsTemplate
- A class which creates templates for generation and release of resources for using JMS API.The implementation can be simplified by using a message sending and message synchronous receiving function.Basically,
JmsMessagingTemplate
which can handleorg.springframework.messaging.Message
is used.SinceJmsMessagingTemplate
wrapsJmsTemplate
, configuration can be done by usingJmsTemplate
property.However, sometimes it is preferable to use aJmsTemplate
as it is. Specific use cases are explained later.
org.springframework.jms.listener.DefaultMessageListenerContainer
DefaultMessageListenerContainer
receives messages from Queue and initiatesMessageListener
which processes the received messages.
@org.springframework.jms.annotation.JmsListener
- A marker annotation which indicates that it is a method to be handled as
MessageListener
of JMS.A@JmsListener
annotation is assigned for a method which performs a process while receiving messages.
8.2.1.3.1. When messages are sent synchronously¶
Sr.No. Description (1) Execute the process in Service by passing “Destination name for sending” and “payload of messages to be sent” forJmsMessagingTemplate
.JmsMessagingTemplate
delegates the process toJmsTemplate
. (2)JmsTemplate
fetchesjavax.jms.Connection
fromConnectionFactory
fetched through JNDI. (3)JmsTemplate
passesDestination
and messages toMessageProducer
.MessageProducer
is generated fromjavax.jms.Session
. (Session
is generated fromConnection
fetched in (2).)Further,Destination
is fetched through JNDI based on “Destination name for sending” passed in (1). (4)MessageProducer
sends messages toDestination
for sending.
8.2.1.3.2. When messages are received asynchronously¶
Sr. No. Description (1) FetchConnection
fromConnectionFactory
fetched through JNDI. (2)DefaultMessageListenerContainer
passesDestination
toMessageConsumer
.MessageConsumer
is generated fromSession
. (Session
is generated fromConnection
fetched in (1))Further,Destination
is fetched through JNDI based on “Destination name for receiving” specified in@JmsListener
annotation. (3)MessageConsumer
receives messages fromDestination
. (4) A method (listener method) configured by@JmsListener
annotation inMessageListener
is called using received message as an argument. Listener method is managed byDefaultMessageListenerContainer
.
8.2.1.3.3. When the messages are received synchronously¶
Sr. No. Description (1) Pass “Destination name for receiving” in Service forJmsMessagingTemplate
.JmsMessagingTemplate
delegates the process toJmsTemplate
. (2)JmsTemplate
fetchesConnection
fromConnectionFactory
fetched through JNDI. (3)JmsTemplate
passesDestination
and messages inMessageConsumer
.MessageConsumer
is generated fromSession
. (Session
is generated fromConnection
fetched in (2).)Further,Destination
is fetched through JNDI based on “Destination name for receiving” passed in (1). (4)MessageConsumer
receives the messages fromDestination
.Message is returned to the Service throughJmsTemplate
orJmsMessagingTemplate
.
8.2.1.4. Regarding project configuration¶
ObjectMessage
, the JavaBean must be shared by sending side and receiving side.- Sharing model
When client of sending and receiving side does not provide a model
model project is added and Jar file is distributed in the client for communication.
When client of sending and receiving side offer a model
The model provided is added to library.
model project, and, relation between distributed archive file and existing project are as below.
Sr. No. Project name Description (1) web project Place the listener class to receive messages asynchronously. (2) domain project Place the Service executed from listener class for receiving messages asynchronously.Besides, Repository etc are same as used conventionally. (3) model project or Jar file A class which is shared between the clients is used among the classes belonging to domain layer.
Following are implemented to add a model project.
- Creating a model project
- Adding a dependency from domain project to model project
For detail addition method, refer Project configuration is changed for SOAP server. of SOAP Web Service (Server/Client). shared by JavaBean in the same way.
8.2.2. How to use¶
8.2.2.1. Settings that are common for both sending and receiving messages¶
This section explains common settings required for sending and receiving messages.
8.2.2.1.1. Configuration of dependent library¶
spring-jms
of Spring Framework is added to pom.xml of domain project in order to use component for linking JMS of Spring Framework.[projectName]-domain/pom.xml
<dependencies> <!-- (1) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> </dependency> </dependencies>
Sr. No. Description (1) Add dependencies tospring-jms
.Sincespring-jms
is dependent onspring-messaging
,spring-messaging
is also added as a transitive dependent library.A library of JMS provider is added to pom.xml in addition tospring-jms
.For additional examples of libraries for pom.xml, refer JMS provider dependent configuration.Note
In the above setting example, since it is assumed that the dependent library version is managed by the parent project terasoluna-gfw-parent , specifying the version in pom.xml is not necessary. The above dependent library used by terasoluna-gfw-parent is defined by Spring IO Platform.
8.2.2.1.2. Configuration of ConnectionFactory
¶
ConnectionFactory
definition method consists of two methods - a method defined by application server and a method defined by Bean definition file.ConnectionFactory
defined in the application server.[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<!-- (1) --> <jee:jndi-lookup id="connectionFactory" jndi-name="jms/ConnectionFactory"/>
Sr. No. Description (1) Specify JNDI name ofConnectionFactory
offered by application server, injndi-name
attribute.Sinceresource-ref
attribute is set totrue
by default, it is auto-assigned when no prefix exists for JNDI name (java:comp/env/).Note
When ConnectionFactory for which a Bean is defined is used
When JNDI is not used,
ConnectionFactory
can be used even when a Bean is defined for implementation class ofConnectionFactory
. In such a case, implementation class ofConnectionFactory
is dependent on JMS provider. For details, refer JMS provider dependent configuration “configuration when JNDI is not used”.
8.2.2.1.3. Configuration of DestinationResolver
¶
org.springframework.jms.support.destination.JndiDestinationResolver
.JndiDestinationResolver
is shown below.[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<!-- (1) --> <bean id="destinationResolver" class="org.springframework.jms.support.destination.JndiDestinationResolver"> <property name="resourceRef" value="true" /> <!-- (2) --> </bean>
Sr. No. Description (1) Define a Bean forJndiDestinationResolver
. (2) When there is no prefix in JNDI name (java:comp/env/), settrue
when it is auto-assigned. Default value isfalse
.Warning
Note that
resource-ref
attribute of<jee:jndi-lookup/>
is different from the default value.
8.2.2.2. A method to synchronously send messages¶
8.2.2.2.1. Basic synchronous sending¶
JmsMessagingTemplate
.Todo
class is synchronously sent as a message.JmsMessagingTemplate
is shown below.[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="cachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory"> <!-- (1) --> <property name="targetConnectionFactory" ref="connectionFactory" /> <!-- (2) --> <property name="sessionCacheSize" value="1" /> <!-- (3) --> </bean> <!-- (4) --> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="cachingConnectionFactory" /> <property name="destinationResolver" ref="destinationResolver" /> </bean> <!-- (5) --> <bean id="jmsMessagingTemplate" class="org.springframework.jms.core.JmsMessagingTemplate"> <property name="jmsTemplate" ref="jmsTemplate"/> </bean>
Sr. No. Description (1) Define a Bean fororg.springframework.jms.connection.CachingConnectionFactory
which performs caching ofSession
andMessageProducer/Consumer
.JMS provider specificConnectionFactory
looked up by Bean definition or JNDI name is not used as it is. Cache function can be used by wrapping it inCachingConnectionFactory
. (2) Specify JMS provider specificConnectionFactory
which is looked up by Bean definition or JNDI name. (3) Specify cache count forSession
. (Default value is 1)Although 1 is specified in this example, number of caches must be changed appropriately corresponding to performance requirements.When the session is required to be continued even after exceeding cache count, a new session is created and destroyed repeatedly without using a cache.This is likely to cause deterioration of process efficiency resulting in performance degradation. (4) Define a Bean forJmsTemplate
.JmsTemplate
alternates for a low level API handling (JMS API calling).For attributes that can be configured, refer list of attributes ofJmsTemplate
given below. (5) Define a Bean forJmsMessagingTemplate
. InjectJmsTemplate
which alternates as a synchronous sending process.
JmsTemplate
for synchronous sending are as below.
Sr. No. Configuration item Details Mandatory Default value
connectionFactory
ConfigureConnectionFactory
to be used.○ Nil (since it is mandatory)
pubSubDomain
Configure for a messaging model.Setfalse
for PTP (Queue) model and settrue
for Pub/Sub (Topic).- false
sessionTransacted
Specify whether the transaction control is performed in the session.In this guideline,false
is recommended as a default for using transaction control described later.- false
messageConverter
Specify message converter.Default can be used for the scope described in the guideline.- SimpleMessageConverter
(*1) is used.
destinationResolver
Specify DestinationResolver.In this guideline, it is recommended to useJndiDestinationResolver
which performs name resolution in JNDI.- DynamicDestinationResolver
(*2) is used.(WhenDynamicDestinationResolver
is used, name resolution of Destination is performed by JMS provider.)
defaultDestination
Specify existing Destination.When Destination is not specified explicitly, this Destination is used.- null (no existing destination)
deliveryMode
Select 1 (NON_PERSISTENT) or 2(PERSISTENT) for delivery mode.2(PERSISTENT) performs perpetuation of messages.1(NON_PERSISTENT) does not perform perpetuation of messages.Hence, even though performance shows improvement, messages are likely to get lost if JMS provider is restarted.In this guideline, it is recommended to use 2 (PERSISTENT) for avoiding loss of messages.When this configuration is used, note thatexplicitQosEnabled
described later must be set totrue
.- 2(PERSISTENT)
priority
Set priority for messages. Priority can be set from 0 to 9.Higher the number, higher is the priority.Priority is evaluated when the message is stored in the Queue at the time of synchronous sending. Messages are stored in such a way that messages with high priority are extracted before the messages with low priority.FIFO (First-In-First-Out) system is employed for the messages with identical priorities.When this configuration is used, note thatexplicitQosEnabled
described later must be set totrue
.- 4
timeToLive
Specify validity period of messages in milliseconds.When the message reaches the validity period, JMS provider deletes the message from QueueWhen this configuration is used, note thatexplicitQosEnabled
described later must be set totrue
.- 0 (Unrestricted)
explicitQosEnabled
Specifytrue
whendeliveryMode
,priority
andtimeToLive
are enabled.- false
(*1)org.springframework.jms.support.converter.SimpleMessageConverter
(*2)org.springframework.jms.support.destination.DynamicDestinationResolver
[projectName]-domain/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model; import java.io.Serializable; public class Todo implements Serializable { // (1) private static final long serialVersionUID = -1L; // omitted private String description; // omitted private boolean finished; // omitted public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isFinished() { return finished; } public void setFinished(boolean finished) { this.finished = finished; } }
Sr. No. Description (1) Basically, a normal JavaBean can be used, however, ajava.io.Serializable
interface must be implemented for a serialised transmission.
Todo
object consisting of specified text is synchronously sent to Queue.[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import javax.inject.Inject; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.stereotype.Service; import com.example.domain.model.Todo; @Service public class TodoServiceImpl implements TodoService { @Inject JmsMessagingTemplate jmsMessagingTemplate; // (1) @Override public void sendMessage(String message) { Todo todo = new Todo(); // omitted jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo); // (2) } }
Sr. No. Description (1) InjectJmsMessagingTemplate
. (2) By usingconvertAndSend
method ofJmsMessagingTemplate
, JavaBean of argument is converted to implementation class oforg.springframework.messaging.Message
interface and the message is synchronously sent for a specified Destination.org.springframework.jms.support.converter.SimpleMessageConverter
is used in the default conversion.WhenSimpleMessageConverter
is used, the class which implementsjavax.jms.Message
,java.lang.String
,byte array
,java.util.Map
andjava.io.Serializable
interface can be sent.Note
Exception handling of JMS in business logic
As covered in JMS (Java Message Service) introduction, exceptions are converted to run-time exceptions in Spring Framework. Hence, a run-time exception must be handled while handling JMS exception in business logic.
Template class A method to convert exceptions Exception after conversion JmsMessagingTemplate
convertJmsException
method ofJmsMessagingTemplate
MessagingException
(*1) and its sub-exceptionJmsTemplate
convertJmsAccessException
method ofJmsAccessor
JmsException
(*2) and its sub-exception(*1)
org.springframework.messaging.MessagingException
(*2)
org.springframework.jms.JmsException
8.2.2.2.2. When message header is to be edited and sent synchronously¶
Header attribute can be edited and then sent synchronously by specifying header attribute and value of Key-Value format in convertAndSend
method argument of JmsMessagingTemplate
.
For header details, refer javax.jms.Messages.
An implementation example wherein JMSCorrelationID
of role which links sending and response messages is specified at the time of synchronous sending.
[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import java.util.Map; import javax.inject.Inject; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.jms.support.JmsHeaders; import com.example.domain.model.Todo; @Service public class TodoServiceImpl implements TodoService { @Inject JmsMessagingTemplate jmsMessagingTemplate; public void sendMessageWithCorrelationId(String correlationId) { Todo todo = new Todo(); // omitted Map<String, Object> headers = new HashMap<>(); headers.put(JmsHeaders.CORRELATION_ID, correlationId);// (1) jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo, headers); // (2) } }
Sr. No. Description (1) Create header information by configuring header attribute name and its value, for implementation class ofMap
. (2) Synchronously send the message assigned with header information created in (2) by usingconvertAndSend
method ofJmsMessagingTemplate
.Warning
Regarding header attributes that can be edited
Components of header attributes (
JMSDestination
,JMSDeliveryMode
,JMSExpiration
,JMSMessageID
,JMSPriority
,JMSRedelivered
andJMSTimestamp
) are handled as read-only while converting messages usingSimpleMessageConverter
of Spring Framework. Hence, even though read-only header attributes are configured as shown in the implementation example, they are not stored in the header of sent messages. (retained as message properties.) Among read-only header attributes,JMSDeliveryMode
andJMSPriority
can be configured inJmsTemplate
unit. For details, refer list of attributes ofJmsTemplate
of Basic synchronous sending.
8.2.2.2.3. Transaction control¶
org.springframework.jms.connection.JmsTransactionManager
is used to achieve transaction control.[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-domain.xml
<!-- (1) --> <bean id="sendJmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager"> <!-- (2) --> <property name="connectionFactory" ref="cachingConnectionFactory" /> </bean>
Sr. No. Description (1) Define a Bean forJmsTransactionManager
.Note
Regarding bean name of TransactionManager
When
@Transactional
annotation is assigned, a Bean registered by Bean nametransactionManager
is used as a default. (For details, refer Settings for using transaction management )In Blank project, since
DataSourceTransactionManager
is defined by Bean name calledtransactionManager
, Bean is defined with an alias in the configuration above.Hence, when only one
TransactionManager
is used in the application, specification oftransactionManager
attribute in@Transactional
annotation can be omitted by usingtransactionManager
as a Bean name. (2) SpecifyCachingConnectionFactory
which controls a transaction.
An implementation example is shown below wherein Todo
object is synchronously sent to Queue by performing transaction control.
[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import javax.inject.Inject; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.domain.model.Todo; @Service @Transactional("sendJmsTransactionManager") // (1) public class TodoServiceImpl implements TodoService { @Inject JmsMessagingTemplate jmsMessagingTemplate; @Override public void sendMessage(String message) { Todo todo = new Todo(); // omitted jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo); // (2) } }
Sr. No. Description (1) Declare transaction boundary by using@Transactional
annotation.Accordingly, transaction starts while starting each method in the class and transaction is committed while terminating a method. (2) Send message synchronously to Queue.However, note that the message is actually sent to Queue within the timing wherein the transaction is committed.
In the application wherein DB transaction control is performed, a transaction control policy must be determined after reviewing relation between JMS and DB transaction based on business requirements.
When JMS and DB transactions are to be separated and then committed and rolled back
A transaction boundary is declared individually when JMS and DB transactions are to be separated.An implementation example is shown below whereinsendJmsTransactionManager
of Transaction control is used in JMS transaction control whereastransactionManager
defined in default configuration of Blank project is used in DB transaction control.
[projectName]-domain/src/main/java/com/example/domain/service/todo/TransactionalTodoServiceImpl.java
package com.example.domain.service.todo; import javax.inject.Inject; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.domain.model.Todo; @Service @Transactional("sendJmsTransactionManager") // (1) public class TransactionalTodoServiceImpl implements TransactionalTodoService { @Inject JmsMessagingTemplate jmsMessagingTemplate; @Inject TodoService todoService; @Override public void sendMessage(String message) { Todo todo = new Todo(); // omitted jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo); // omitted todoService.update(todo); } }
src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.domain.model.Todo; @Transactional // (2) @Service public class TodoServiceImpl implements TodoService { @Override public void update(Todo todo) { // omitted } }
Sr. No. Description (1) Declare transaction boundary of JMS by using@Transactional
annotation.SpecifysendJmsTransactionManager
which performs transaction control of JMS. (2) Declare transaction boundary of DB by using@Transactional
annotation.Since value is omitted, Bean nametransactionManager
is referred as a default.For details of@Transactional
annotation, refer Regarding transaction management of Domain Layer Implementation.
When JMS and DB transactions are to committed and rolled back together
A method which uses global transaction by JTA exists for linking JMS and DB transactions, however “Best Effort 1 Phase Commit” is recommended since overheads are likely to occur for performance, among protocol characteristics. Refer below for details.
Warning
When transaction process results are not returned in JMS provider due to issues like loss of connection with JMS provider after receiving a message
When transaction process results are not returned in JMS provider due to issues like loss of connection with JMS provider after receiving a message, transaction handling depends on JMS provider. Hence, a design considering loss of received messages must be carried out. Especially, when loss of messages is absolutely not permissible, providing a system to compensate for the loss of messages or using a global transaction etc must be adopted.
“Best Effort 1 Phase Commit” can be achieved by usingorg.springframework.data.transaction.ChainedTransactionManager
.An implementation example is shown below whereinsendJmsTransactionManager
of Transaction control is used in JMS transaction management whereastransactionManager
defined in default configuration of blank project is used in DB transaction management.xxx-env.xml
<!-- (1) --> <bean id="sendChainedTransactionManager" class="org.springframework.data.transaction.ChainedTransactionManager"> <constructor-arg> <list> <!-- (2) --> <ref bean="sendJmsTransactionManager" /> <ref bean="transactionManager" /> </list> </constructor-arg> </bean>
Sr. No. Description (1)Define a Bean forChainedTransactionManager
.(2)Specify JMS and DB transaction manager.Transaction starts in a registered sequence and transaction is committed in the reverse sequence.Implementation example using the configuration given above is shown below.
[projectName]-domain/src/main/java/com/example/domain/service/todo/ChainedTransactionalTodoServiceImpl.java
package com.example.domain.service.todo; import javax.inject.Inject; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.domain.model.Todo; @Service @Transactional("sendChainedTransactionManager") // (1) public class ChainedTransactionalTodoServiceImpl implements ChainedTransactionalTodoService { @Inject JmsMessagingTemplate jmsMessagingTemplate; @Inject TodoSharedService todoSharedService; @Override public void sendMessage(String message) { Todo todo = new Todo(); // omitted jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo); // (2) // omitted todoSharedService.insert(todo); // (3) } }
Sr. No. Description (1) Control JMS and DB transactions by specifyingsendChainedTransactionManager
in@Transactional
annotation.For details of@Transactional
annotation, refer Regarding transaction management of Domain Layer Implementation. (2) Send messages synchronously. (3) Execute process associated with DB access. In this example, SharedService is executed along with DB update.Note
If it is necessary to manage multiple transactions together like JMS and DB, for business, a global transaction is considered. For global transactions, refer “when transaction control (global transaction control) is necessary for multiple DB (multiple resources)” of Settings for using transaction management.
8.2.2.3. A method to receive messages asynchronously¶
@JmsListener
annotation for DefaultMessageListenerContainer
responsible for asynchronous receiving function- Provide a method to receive messages.Messages can be received by executing a method assigned by
@JmsListener
annotation. - Call business process.Business process is not implemented by listener method. It is delegated to Service method.
- Handle exceptions occurring in business logic.Business exceptions and library exceptions occurred during normal operations are handled.
- Send process results as messages.In the methods wherein response messages are required to be sent, process results of listener method and business logic should be sent as messages for a specified Destination, by using
org.springframework.jms.listener.adapter.JmsResponse
.
8.2.2.3.1. Basic asynchronous receiving¶
@JmsListener
annotation is explained.- Define JMS Namespace.
- Enable
@JmsListener
annotation. - Specify
@JmsListener
annotation in the method of component managed by DI container.
[projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<!-- (1) --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jms="http://www.springframework.org/schema/jms" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd"> <!-- (2) --> <jms:annotation-driven /> <!-- (3) --> <jms:listener-container factory-id="jmsListenerContainerFactory" destination-resolver="destinationResolver" concurrency="1" cache="consumer" transaction-manager="jmsAsyncReceiveTransactionManager"/>
Sr. No. Attribute name Details (1)xmlns:jms Define JMS Namespace.Specifyhttp://www.springframework.org/schema/jms
as a value.For details of JMS Namespace, refer JMS Namespace Support.xsi:schemaLocation Specify URL of schema.Addhttp://www.springframework.org/schema/jms
andhttp://www.springframework.org/schema/jms/spring-jms.xsd
to values. (2)- Enable JMS related annotation function of@JmsListener
annotation and@SendTo
annotation by using<jms:annotation-driven />
. (3)- ConfigureDefaultMessageListenerContainer
by assigning parameters to the factory which generateDefaultMessageListenerContainer
, by using<jms:listener-container/>
.connection-factory
attribute which can specify a Bean ofConnectionFactory
to be used, exists in the<jms:listener-container/>
attribute. Default value ofconnection-factory
attribute isconnectionFactory
.In this example, since Bean (Bean name isconnectionFactory
) ofConnectionFactory
shown in Configuration of ConnectionFactory is used,connection-factory
attribute is omitted.<jms:listener-container/>
also consists of attributes other than introduced here.For details, refer Attributes of the JMS <listener-container> element.Warning
Since
DefaultMessageListenerContainer
is equipped with an independent cache function,CachingConnectionFactory
should not be used in case of asynchronous receiving. For details, refer Javadoc of DefaultMessageListenerContainer. For above,ConnectionFactory
defined in Configuration of ConnectionFactory should be specified inconnection-factory
attribute of<jms:listener-container/>
.concurrency
Specify upper limit of parallel number of each listener method managed byDefaultMessageListenerContainer
.Default value forconcurrency
attribute is 1.Lower limit and upper limit of parallel numbers can also be specified. For example, specify “5-10” when lower limit is 5 and upper limit is 10.When the parallel number of listener method has reached specified upper limit, parallel processing is not done and a “waiting” state is reached.A value should be specified as required.Note
When you want to specify parallel number in listener method unit,
concurrency
attribute of@JmsListener
annotation can be used.destination-resolver
Specify Bean name ofDestinationResolver
which is used to resolve Destination name at the time of asynchronous receiving.For Bean definition ofDestinationResolver
, refer Configuration of DestinationResolver.Whendestination-resolver
attribute is not specified,DynamicDestinationResolver
generated inDefaultMessageListenerContainer
is used.factory-id
Specify name ofDefaultJmsListenerContainerFactory
which defines a Bean.Since@JmsListener
annotation refers Bean namejmsListenerContainerFactory
as a default, it is recommended to consider Bean name asjmsListenerContainerFactory
in case of a single<jms:listener-container/>
.cache
Specify cache level to determine cache targets likeConnection
,Session
orConsumer
etc.Default isauto
.When Connection is not pooled in the application server while configuringtransaction-manager
attribute described later, specifyingconsumer
is recommended for performance improvement.Note
In case of
auto
, whentransaction-manager
attribute is not configured, behaviour is same asconsumer
(Consumer
is cached). However, behaviour is same asnone
(invalid cache) considering the pooling in the application server using global transaction at the time oftransaction-manager
attribute configuration.transaction-manager
Specify Bean name which performs transaction control at the time of asynchronous receiving. For details, refer Transaction control.Messages can be asynchronously received from specified Destination by specifying
@JmsListener
annotation in the component method managed by DI container. Implementation method is as shown below.
[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
package com.example.listener.todo; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; import com.example.domain.model.Todo; @Component public class TodoMessageListener { @JmsListener(destination = "jms/queue/TodoMessageQueue") // (1) public void receive(Todo todo) { // omitted } }
Sr. No. Description (1) Specify@JmsListener
annotation for a method, for asynchronous receiving. Specify Destination name for receiving indestination
attribute.A list of main attributes of
@JmsListener
annotation is shown below. For details and other attributes, refer Javadoc of @JmsListener annotation.
Sr. No. Fields Details
destination
Specify Destination to be received.
containerFactory
Specify Bean name ofDefaultJmsListenerContainerFactory
which manages the listener method.Default isjmsListenerContainerFactory
.
selector
Specify a message selector which acts as a condition for restricting the message to be received.When the value is not explicitly specified, default is “”(blank character) and all the messages can be received.For how to use, refer When messages which are asynchronously received are to be restricted.
concurrency
Specify upper limit of parallel numbers for listener method.;Default value forconcurrency
attribute is 1.Lower limit and upper limit of parallel numbers can be specified. For example, specify “5-10” when lower limit is 5 and upper limit is 10.When the parallel number of listener method has reached specified upper limit, parallel processing is not done and a “waiting” state is reached.Value should be specified as required.
8.2.2.3.2. Fetching header information of messages¶
JMSReplyTo
), @org.springframework.messaging.handler.annotation.Header
annotation is used.[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/TodoMessageQueue") public JmsResponse<Todo> receiveAndResponse( Todo todo, @Header("jms_replyTo") Destination storeResponseMessageQueue) { // (1) // omitted return JmsResponse.forDestination(todo, storeResponseMessageQueue); }
Sr. No. Description (1) Specify@Header
annotation to fetch value of header attributeJMSReplyTo
of receiving message.For a key specified while fetching JMS standard header attribute, refer JmsHeaders constants definition.
8.2.2.3.3. Process results after asynchronous receiving are sent as messages¶
@JmsListener
annotation, to Destination as a response message.- When sending destination of process results is specified statically
- When sending destination of process results is specified dynamically
Respective descriptions are as below.
- When sending destination of process results is specified statically
- Process results can be sent as messages to a fixed destination by defining
@SendTo
annotation which specifies a destination, for a method defined by@JmsListener
annotation.Implementation example is as shown below.
[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/TodoMessageQueue") @SendTo("jms/queue/ResponseMessageQueue") // (1) public Todo receiveMessageAndSendTo(Todo todo) { // omitted return todo; // (2) }
Sr. No. Description (1) Default sending destination of process results can be specified by defining@SendTo
annotation. (2) Return data to be sent to Destination defined in@SendTo
annotation.Permissible return value types are the classes which implementorg.springframework.messaging.Message
,javax.jms.Message
,String
,byte
array,Map
andSerializable
interface.
- When sending destination of process results is changed dynamically
When sending destination is to be changed dynamically,forDestination
orforQueue
methods ofJmsResponse
class are usedProcess results can be sent to any Destination by dynamically changing Destination for sending or Destination name. Implementation example is as shown below.
[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/TodoMessageQueue") public JmsResponse<Todo> receiveMessageJmsResponse(Todo todo) { // omitted String resQueue = null; if (todo.isFinished()) { resQueue = "jms/queue/FinihedTodoMessageQueue"; } else { resQueue = "jms/queue/ActiveTodoMessageQueue"; } return JmsResponse.forQueue(todo, resQueue); // (1) }
Sr. No. Description (1) When Queue for sending is to be changed in accordance with the process details,forDestination
orforQueue
methods ofJmsResponse
class are used.In this example, messages are sent from Destination name by usingforQueue
method.Note
When
forQueue
method ofJmsResponse
class is used, Destination name is used as a string. For resolving destination name,DestinationResolver
specified inDefaultMessageListenerContainer
is used.
Note
When sending destination of process results is specified on Producer side
Using the implementation below, messages of process results can be sent to any destination specified on Producer side.
Implementation location Implementation details Producer side Specify Destination in header attributeJMSReplyTo
of messages in accordance with JMS standards.For editing of header attribute, refer When message header is to be edited and sent synchronously. Consumer side Return objects which send a message.
Header attribute JMSReplyTo
is given priority over the default Destination specified on the Consumer side.
For details, refer Response management.
8.2.2.3.4. When messages which are asynchronously received are to be restricted¶
The messages to be received can be restricted by specifying a message selector at the time of receiving.
[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/MessageQueue" , selector = "TodoStatus = 'deleted'") // (1) public void receive(Todo todo) { // omitted }
Sr. No. Description (1) Conditions for receiving can be set by usingselector
attribute.TodoStatus
of header attribute receives onlydeleted
messages.Message selector is based on subset of SQL92 conditional expression syntax.For details, refer Message Selectors.
8.2.2.3.5. Input check for the messages which are received asynchronously¶
Todo
object shown in Basic synchronous sending.[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import javax.validation.Valid; import org.springframework.validation.annotation.Validated; import com.example.domain.model.Todo; @Validated // (1) public interface TodoService { void updateTodo(@Valid Todo todo); // (2) }
Sr. No. Description (1) Declare the interface as a target for input check by attaching a@Validated
annotation. (2) Specify a constraint annotation of Bean Validation as an argument for the method.
[projectName]-domain/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model; import java.io.Serializable; import javax.validation.constraints.Null; // (1) public class Todo implements Serializable { private static final long serialVersionUID = -1L; // omitted @Null private String description; // omitted private boolean finished; // omitted public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isFinished() { return finished; } public void setFinished(boolean finished) { this.finished = finished; } }
Sr. No. Description (1) Define input check of JavaBean in Bean Validation.In this example,@Null
annotation is set as an example.For details, refer “Input Validation”.
[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@Inject TodoService todoService; @JmsListener(destination = "jms/queue/MessageQueue") public void receive(Todo todo) { try { todoService.updateTodo(todo); // (1) } catch (ConstraintViolationException e) { // (2) // omitted } }
Sr. No. Description (1) Implement Service method which performs input check. (2) CaptureConstraintViolationException
which occurs at the time of constraint violation.Any process can be performed after capturing the exception.For examples of sending messages to another Queue like using a Queue for storing logical error messages, refer Exception handling at the time of asynchronous receiving.
8.2.2.3.6. Transaction control¶
<jms:listener-container/>
.Note
When message returns to Queue, it is asynchronously received once again. Since the cause of error is not resolved, the operations of rollback and asynchronous receiving are repeated. A threshold value for number of resends after rollback can be set depending on JMS provider and when resend count exceeds the threshold value, the message is stored in Dead Letter Queue.
How to configure is shown below.
[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-domain.xml
<!-- (1) --> <bean id="jmsAsyncReceiveTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager"> <!-- (2) --> <property name="connectionFactory" ref="connectionFactory" /> </bean>
Sr. No. Description (1) Define a Bean forJmsTransactionManager
of asynchronous receiving. (2) SpecifyConnectionFactory
which manages a transaction. Note that,CachingConnectionFactory
cannot be used at the time of asynchronous receiving.
[projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<!-- (1) --> <jms:listener-container factory-id="jmsListenerContainerFactory" destination-resolver="destinationResolver" concurrency="1" error-handler="jmsErrorHandler" cache="consumer" transaction-manager="jmsAsyncReceiveTransactionManager"/>
Sr. No. Attribute name Details (1)cache
Specify cache level to determine cache targets such asConnection
,Session
orConsumer
.Default isauto
.As described earlier in Basic asynchronous receiving,consumer
is specified when connection is not pooled in the application server.transaction-manager
Specify Bean name forJmsTransactionManager
to be used.Note that,JmsTransactionManager
does not manageCachingConnectionFactory
.Warning
Since
Connection
orSession
caching in the application is likely to be prohibited depending on the application server, cache validation and invalidation should be determined in accordance with the specifications of application server to be used.
Note
A method which performs exception handling other than roll back process in case of specific exceptions
When transaction control is enabled, the message returns to Queue due to roll back if the exception occurred in input check is thrown without getting captured. Since listener method asynchronously receives the message again which has returned in Queue, the sequence asynchronous receiving Error occurrence Rollback is repeated a number of times for JMS provider configuration. In case of an error for which the cause of the error is not resolved even after retry, the error handling is done so as not to throw an exception from listener method after capturing, to restrain futile processes mentioned above. For details, refer Exception handling at the time of asynchronous receiving.
In the application wherein DB transaction control is required, a transaction control policy must be determined after reviewing the relation between JMS and DB transactions based on business requirements.
When the transaction is to be committed and rolled back by separating JMS and DB transactions
In some cases, JMS transaction is rolled back whereas only DB transaction is committed.In such a situation, it is necessary to manage JMS and DB transactions separately.JMS and DB transactions can be separately controlled by defining a@Transactional
annotation in the Service class called by listener method.Implementation example is given below whereinjmsListenerContainerFactory
of Transaction control is used for transaction control of JMS andtransactionManager
defined in default configuration of Blank project is used for transaction control of DB.[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
package com.example.listener.todo; import javax.inject.Inject; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; import com.example.domain.service.todo.TodoService; import com.example.domain.model.Todo; @Component public class TodoMessageListener { @Inject TodoService todoService; @JmsListener(destination = "TransactedQueue") // (1) public void receiveTransactedMessage(Todo todo) { todoService.update(todo); } }
[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.domain.model.Todo; @Transactional // (2) @Service public class TodoServiceImpl implements TodoService { @Override public void update(Todo todo) { // omitted } }
Sr. No. Description (1)Define@JmsListener
annotation and specifyDefaultJmsListenerContainerFactory
which enables transaction control of JMS.Since@JmsListener
annotation refers to Bean namejmsListenerContainerFactory
as a default,containerFactory
attribute is omitted.(2)Define transaction boundary of DB.Since value is omitted, Bean nametransactionManager
is referred as a default.For details of@Transactional
annotation, refer Regarding transaction management of Domain Layer Implementation.Note
Nesting sequence for transaction boundary depends on the business requirements and JMS provider is often used for linking with external systems. In such a case, JMS transaction boundary is kept outside DB transaction boundary and recovery becomes easier when inward DB transaction is completed earlier. When DB transaction is committed and JMS transaction is rolled back, the message returns to the Queue. Hence the same message is processed again. It should be designed so as to allow DB update process to be re-tried at the time of reexecution of business process.
When JMS and DB transactions are to be committed and rolled back together
A method which uses global transaction by JTA exists for linking JMS and DB transactions, however “Best Effort 1 Phase Commit” is recommended since overheads are likely to occur for performance, among protocol characteristics. Refer below for details.
Warning
When transaction process results are not returned in JMS provider due to issues like loss of connection with JMS provider after receiving a message
When transaction process results are not returned in JMS provider due to issues like loss of connection with JMS provider after receiving a message, transaction handling depends on JMS provider. Hence, a design considering loss of received messages, reprocessing of messages due to roll back must be performed. Especially, when loss of messages is absolutely not permissible, providing a system to compensate for the loss of messages or using a global transaction etc must be adopted.
“Best Effort 1 Phase Commit” can be achieved by usingorg.springframework.data.transaction.ChainedTransactionManager
.An implementation example is shown below whereinjmsAsyncReceiveTransactionManager
of Transaction control is used in JMS transaction control andtransactionManager
defined in default setting of Blank project is used in DB transaction control.[projectName]-env/src/main/resources/META-INF/spring/[projectName]-env.xml
<!-- (1) --> <bean id="chainedTransactionManager" class="org.springframework.data.transaction.ChainedTransactionManager"> <constructor-arg> <list> <!-- (2) --> <ref bean="jmsAsyncReceiveTransactionManager" /> <ref bean="transactionManager" /> </list> </constructor-arg> </bean>
Sr. No. Description (1)Define a Bean forChainedTransactionManager
.(2)Specify a transaction manager of JMS and DB.Transaction starts in a registered sequence and transaction is committed in the reverse sequence.[projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<!-- (1) --> <jms:listener-container factory-id="chainedTransactionJmsListenerContainerFactory" destination-resolver="destinationResolver" concurrency="1" error-handler="jmsErrorHandler" cache="consumer" transaction-manager="chainedTransactionManager" acknowledge="transacted"/>
Sr. No. Attribute name Details (1)- Define<jms:listener-container/>
for usingChainedTransactionManager
.factory-id
Specify Bean name ofDefaultJmsListenerContainerFactory
.In this example, Bean name is set tochainedTransactionJmsListenerContainerFactory
by considering its combined use with<jms:listener-container/>
of Basic asynchronous receiving.transaction-manager
Specify Bean name forChainedTransactionManager
.acknowledge
Specifytransacted
in check response mode in order to enable the transaction. Default isauto
.Note
In
DefaultMessageListenerContainer
, when Bean of implementation class oforg.springframework.transaction.support.ResourceTransactionManager
is specified intransaction-manager
attribute, the transaction control which uses this Bean is enabled. However, sinceChainedTransactionManager
does not implementResourceTransactionManager
, transaction control is not enabled.transacted
must be specified inacknowledge
attribute in order to enable transaction control. Accordingly, Session acting as a target for transaction control is generated and transaction control forChainedTransactionManager
is enabled.Implementation example which uses the configuration above is shown below.
[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@Inject TodoService todoService; @JmsListener(containerFactory = "chainedTransactionJmsListenerContainerFactory", destination = "jms/queue/TodoMessageQueue") // (1) public void receiveTodo(Todo todo) { // omitted }
Sr. No. Description (1)Since the Bean name usesDefaultJmsListenerContainerFactory
ofchainedTransactionJmsListenerContainerFactory
,chainedTransactionJmsListenerContainerFactory
is specified incontainerFactory
attribute.Behaviour when an application is created in accordance with the configuration and implementation example above is explained.
- When processing of listener method is successfully completed
JMS and DB transactions are committed together.Transaction starts in the sequence of JMS, DB, and transaction ends in the sequence of DB, JMS after executing listener method.- When an unexpected exception occurs in listener method or business logic
When exception occurs in listener method
Sr. No. Description (1)Start JMS transaction.(2)Start DB transaction.(3)An unexpected exception occurs in listener method or business logic.(4)Roll back DB transaction and terminate DB transaction.(5)Roll back JMS transaction and terminate JMS transaction.Since JMS transaction is rolled back, the message is returned to the Queue.- When connection with JMS provider is lost after receiving the message and only DB transaction is committed
JMS transaction handling is dependent on JMS provider, however, since DB transaction is committed, an inconsistency is likely to occur in JMS and DB status.Considering the possibility of rollback of JMS transaction, integrity of the data must be ensured when same message is received multiple times.An example to ensure data integrity is shown below.- When the process after asynchronous receiving is executed multiple times, process must be designed to ensure same status after the processing.
- Design so as to record
JMSMessageID
.JMSMessageID
recorded earlier andJMSMessageID
of received message are compared for each received message and when they match, received message is destroyed.
Sr. No. Description (1)Start JMS transaction.(2)Start DB transaction.(3)Successfully terminate listener method.(4)Commit DB transaction and terminate DB transaction.(5)An unexpected error like JMS provider disconnected occurs.(6)An error is likely to occur in JMS transaction commit.Hence, a system to ensure consistency must be provided for loss of messages etc.Note
When it is necessary to stringently control multiple transactions like JMS and DB by avoiding events given above, use of global transaction is considered. For global transaction, refer various product manuals.
8.2.2.3.7. Exception handling at the time of asynchronous receiving¶
¶ Sr. No. Objective of handling An example of exception for handling Handling method Handling unit (1) When exceptions occurring in business layer are to be individually handled Business exception like input check error Listener method(try-catch) Listener method unit (2) When the exceptions thrown by listener method are to be handled uniformly System exceptions like input output error etcErrorHandler
JMSListenerContainer unit
When exceptions occurred in the business layer are to be handled individually
The exceptions occurring in the business layer like “message contents are invalid” are trapped (try-catch) by listener method and handled by listener method unit.While performing transaction control, since exceptions must be thrown inDefaultMessageListenerContainer
for the cases which require rollback, the captured exception must be thrown again.Implementation example is shown below.[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@Inject TodoService todoService; @JmsListener(destination = "jms/queue/TodoMessageQueue") public JmsResponse<Todo> receiveTodo(Todo todo) { try { todoService.insertTodo(todo); } catch (BusinessException e) { return JmsResponse.forQueue(todo, "jms/queue/ErrorMessageQueue"); // (1) } return null; // (2) }
Sr. No. Description (1)An object can be sent to a Queue for storing a logical error message by usingforQueue
method ofJmsResponse
class.In this example, sinceBusinessException
which outputs the log in AOP is captured, log output process is not described explicitly. However, exceptions must be handled so as not to eliminate the cause of exception.When the messages are to be processed after roll back, by performing transaction control, the exception thus captured must be thrown.(2)When the messages are not sent, set return value tonull
.When the exceptions thrown by listener method are to be handled uniformly
While commonly handling exceptions, implementation class ofErrorHandler
defined inerror-handler
attribute of<jms:listener-container/>
is used.Configuration method is shown below.[projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<!-- (1) --> <jms:listener-container factory-id="jmsListenerContainerFactory" destination-resolver="destinationResolver" concurrency="1" error-handler="jmsErrorHandler" cache="consumer" transaction-manager="jmsAsyncReceiveTransactionManager"/> <!-- (2) --> <bean id="jmsErrorHandler" class="com.example.domain.service.todo.JmsErrorHandler"> </bean>
Sr. No. Description (1)Define a Bean name of error handling class inerror-handler
attribute of<jms:listener-container/>
.(2)Define a Bean for error handling class.Implementation method is as shown below.
[projectName]-web/src/main/java/com/example/listener/todo/JmsErrorHandler.java
package com.example.listener.todo; import org.springframework.util.ErrorHandler; import org.terasoluna.gfw.common.exception.SystemException; public class JmsErrorHandler implements ErrorHandler { // (1) @Override public void handleError(Throwable t) { // (2) // omitted if (t.getCause() instanceof SystemException) { // (3) // omitted system error handling } else { // omitted error handling } } }
Sr. No. Description (1)Create an error handling class which implementsErrorHandler
interface.(2)Exception occurring in the listener method is wrapped inorg.springframework.jms.listener.adapter.ListenerExecutionFailedException
and passed as an argument.(3)Determine any exception class and implement error handling associated with the exception.t.getCause()
must be executed to fetch the exception occurred in the application.
8.2.2.4. A method wherein the messages are synchronously received¶
JmsMessagingTemplate
.[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="cachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory"> <!-- (1) --> <property name="targetConnectionFactory" ref="connectionFactory" /> <!-- (2) --> <property name="sessionCacheSize" value="1" /> <!-- (3) --> </bean> <!-- (4) --> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="cachingConnectionFactory" /> <property name="destinationResolver" ref="destinationResolver" /> </bean> <!-- (5) --> <bean id="jmsMessagingTemplate" class="org.springframework.jms.core.JmsMessagingTemplate"> <property name="jmsTemplate" ref="jmsTemplate"/> </bean>
Sr. No. Description (1) Define a Bean fororg.springframework.jms.connection.CachingConnectionFactory
which performs caching ofSession
andMessageProducer/Consumer
.Cache function can be used by using JMS provider specificConnectionFactory
which is looked up in Bean definition or JNDI name, by wrapping it inCachingConnectionFactory
instead of using it as it is. (2) SpecifyConnectionFactory
which is wrapped up in Bean definition or JNDI name. (3) Set cache number ofSession
. (default value is 1)Although 1 is specified in this example, cache number should be changed appropriately corresponding to performance requirement.If a session is required to be continued even after exceeding this cache number, a new session is created and destroyed repeatedly without using a cache.It is likely to cause reduction in process efficiency resulting in performance degradation. (4) Define a Bean forJmsTemplate
.JmsTemplate
alternates for a low level API handling (JMS API calling).For the attributes that can be configured, refer list of attributes ofJmsTemplate
shown below. (5) Define a Bean forJmsMessagingTemplate
. InjectJmsTemplate
which alternates as a synchronous receiving process.
JmsTemplate
related to synchronous receiving are shown below.
Sr. No. Configuration item Details Mandatory Default value
connectionFactory
SetConnectionFactory
to be used.○ Nil (since it is mandatory)
pubSubDomain
Set for messaging model.Setfalse
for PTP (Queue) model andtrue
for Pub/Sub (Topic).- false
sessionTransacted
Set whether the transaction is controlled in the session.In this guideline, since transaction control described later is used, defaultfalse
is recommended.- false
sessionAcknowledgeMode
Set confirmation response mode of session forsessionAcknowledgeMode
.For details, refer JavaDoc of JMSTemplate.Todo
Details of sessionAcknowledgeMode will be added later.
- 1
receiveTimeout
Set timeout period (milliseconds) at the time of synchronous receiving. If it is not set, wait till the message is received.If the timeout period is not set, it impacts the subsequent processes, hence an appropriate timeout period must always be set.- 0
messageConverter
Set message converter.Default can be used in the range introduced in the guideline.- SimpleMessageConverter
(*1) is used.
destinationResolver
Set DestinationResolver.This guideline recommends settingJndiDestinationResolver
which performs name resolution by JNDI.- DynamicDestinationResolver
(*2) is used.(IfDynamicDestinationResolver
is used, name resolution of Destination is performed by JMS provider.)
defaultDestination
Specify existing Destination.When the Destination is not specified explicitly, this Destination is used.- null(no existing Destination)
(*1)org.springframework.jms.support.converter.SimpleMessageConverter
(*2)org.springframework.jms.support.destination.DynamicDestinationResolver
Messages are received synchronously by receiveAndConvert
message of JmsMessagingTemplate
class. Implementation example is shown below.
[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import javax.inject.Inject; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.stereotype.Service; import com.example.domain.model.Todo; @Service public class TodoServiceImpl implements TodoService { @Inject JmsMessagingTemplate jmsMessagingTemplate; @Override public String receiveTodo() { // omitted Todo retTodo = jmsMessagingTemplate.receiveAndConvert("jms/queue/TodoMessageQueue", Todo.class); // (1) } }
Sr. No. Description (1) Receive message from specified Destination usingreceiveAndConvert
method ofJmsMessagingTemplate
.receiveAndConvert
method can fetch the class for which type conversion is done, by specifying the class for conversion, in the second argument.A header item can be fetched byMessage
object of Spring Framework, by usingreceive
method.
8.2.3. Appendix¶
8.2.3.1. JMS provider dependent configuration¶
Configuration varies for each JMS provider. Configuration for each JMS provider is explained below.
8.2.3.1.1. While using Apache ActiveMQ¶
Configuration while using Apache ActiveMQ is explained.
JMS provider specific configuration for application server
JMS provider requires specific configuration.In Apache ActiveMQ, environment variable must be added to starting variable of application server to ensure that it consists of objects wherein payload of received messages is permissible.For details, refer ObjectMessage.An example of adding environment variable to startup argument of Apache Tomcat is shown below. Refer Configuring JBoss EAP to Run as a Service in case of JBoss Enterprise Application Platform 7.0, Service Configuration in case of JBoss Enterprise Application Platform 6.4 and Starting Managed Servers with a Startup Script in case of Weblogic.$CATALINA_HOME/bin/setenv.sh
# omitted # (1) -Dorg.apache.activemq.SERIALIZABLE_PACKAGES=java.lang,java.util,org.apache.activemq,org.fusesource.hawtbuf,com.thoughtworks.xstream.mapper,com.example.domain.model # omitted
Sr. No. Description (1)Add package for an arbitrary object to be allowed.java.lang
,java.util
,org.apache.activemq
,org.fusesource.hawtbuf
andcom.thoughtworks.xstream.mapper
are the settings required while using Apache ActiveMQ.“com.example.domain.model” is added as a required configuration value in this sample.Adding a library
JMS API is not included in thespring-jms
library.Although JMS API is normally included in the JMS provider library, JMS API is added to pom.xml if JMS API is not included in JMS provider library.activemq-client
is added to pom.xml of domain project and web project as a library for build.Further,activemq-client
and its dependent libraries are added to the application server.[projectName]-domain/pom.xml
[projectName]-web/pom.xml
<dependencies> <!-- (1) --> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-client</artifactId> <scope>provided</scope> </dependency> </dependencies>
Sr. No. Description (1)Add client library of Apache ActiveMQ to dependencies as a build. Since JMS API is also incorporated inactivemq-client
library, it is not necessary to add JMS API as a library.
Note
In the above setting example, since it is assumed that the dependent library version is managed by the parent project terasoluna-gfw-parent , specifying the version in pom.xml is not necessary. The above dependent library used by terasoluna-gfw-parent is defined by Spring IO Platform.
Warning
The version of the library used while establishing a connection with Apache ActiveMQ is defined in Spring IO Platform which is used by TERASOLUNA Server Framework for Java. Hence, care must be taken while determining Apache ActiveMQ version. Note that, library and middleware versions are likely to be inconsistent while upgrading the version of TERASOLUNA Server Framework for Java.
JNDI registration to application server
For registration of JNDI to application server, refer Manually integrating Tomcat and ActiveMQ.Configuration when JNDI is not used.
Although this guideline recommends a method to resolve names using JNDI,JNDI is not used while connecting to JMS provider during the implementation of a simple test which is not run on the application server.In such a case, a Bean of implementation class ofConnectionFactory
must be generated.Further, use of JNDI is also specified for Queue, however, if a Queue specified in the Destination by using a JMS provider function does not exist, a Queue of specified name can be dynamically generated.Internal Broker of Apache ActiveMQ must be used for establishing a connection without passing through the application server.For configuration of internal Broker for Apache ActiveMQ, refer How do I embed a Broker inside a Connection.Following configuration should be added to the context for the testing.[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<!-- (1) --> <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <constructor-arg value="tcp://localhost:61616"/> <!-- (2) --> </bean>
Sr. No. Description (1)Define a Bean forConnectionFactory
of Apache ActiveMQ.(2)Specify beginning URL of Apache ActiveMQ. Beginning URL sets the value for each environment.
Note
When the configuration method of ConnectionFactory is to be changed by JNDI and bean definition, based on development phase, configuration should be described in
[projectName]-env/src/main/resources/META-INF/spring/[projectName]-env.xml
.
8.2.3.2. Mass-mailing of identical messages¶
JmsMessageTemplate
is to be used for mass mailing of identical message, memory usage is likely to increase.send
method of JmsTemplate
class must be considered.org.springframework.jms.core.MessageCreator
class is generated while sending a message in JmsMessageTemplate
.send
method of JmsTemplate
class wherein MessageCreator
instance is not generated during sending thus reducing the amount of memory used.[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import java.io.IOException; import javax.inject.Inject; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import javax.jms.TextMessage; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.stereotype.Service; @Service public class TodoServiceImpl implements TodoService { @Inject JmsTemplate jmsTemplate; // (1) @Override public void sendManyMessage(final String messageStr) throws IOException { MessageCreator mc = new MessageCreator() { // (2) public Message createMessage(Session session) throws JMSException { TextMessage message = session.createTextMessage(); // (3) message.setText(messageStr); // omitted return message; } }; for (int i = 0; i < 100; i++) { jmsTemplate.send("jms/queue/TodoMessageQueue", mc); // (4) } } }
Sr. No. Description (1) WhenJmsMessagingTemplate
is used,MessageCreater
is generated at the time of sending. Hence,JmsTemplate
which can define generation ofMessageCreater
by isolating it from sending, is used. (2) Generate instance ofMessageCreator
for creatingMessage
of JMS. (3) When messages are sent bysend
method ofJmsTemplate
class, instances ofMessageCreator
are no longer generated for each loop and the amount of memory can be reduced.
8.2.3.3. Sending and receiving large size data¶
While handling data of large size like Image data (Data of size 1MB or more as a rough indication), OutOfMemoryError
is likely to occur due to number of concurrent transactions and heap size.
In standard API of JMS, only StreamMessage
which sends primitive type data and ByteMessage
which can send uninterpreted byte stream can handle large size data as a stream.
Hence, a specific API offered for each JMS provider is used instead of JMS API.
8.2.3.3.1. While using Apache ActiveMQ¶
A message of large size can be received or sent by using Blob Message. Implementation example is shown below.
Note
Since Apache ActiveMQ specific API is used while using
org.apache.activemq.BlobMessage
,Message
andCachingConnectionFactory
offered by Spring Framework cannot be used. It is recommended to separately defineJmsTemplate
forBlobMessage
while usingBlobMessage
considering the impact on the performance.
Configuration
While sending a message by using
BlobMessage
, the message is stored in the server which is temporarily run by Apache ActiveMQ, instead of heap area. Definition example for storage destination for the messages is shown below.[projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL"> <!-- (1) --> <value>tcp://localhost:61616?jms.blobTransferPolicy.uploadUrl=/tmp</value> </property> </bean>
Sr. No. Description (1)Define a directory of Apache ActiveMQ server which temporarily stores messages.http://localhost:8080/uploads/
is set as a default injms.blobTransferPolicy.uploadUrl
wherein location for temporary file can be specified by overloading default orbrokerURL
.For example, the file is temporarily stored in/tmp
.Sending
An implementation example of sending class which use
Blob Message
is shown below.[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import javax.inject.Inject; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import org.apache.activemq.ActiveMQSession; import org.apache.activemq.BlobMessage; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.stereotype.Service; @Service public class TodoServiceImpl implements TodoService { @Inject JmsTemplate jmsTemplate; @Override public void sendBlobMessage(String inputFilePath) throws IOException { Path path = Paths.get(inputFilePath); try (final InputStream inputStream = Files.newInputStream(path)) { jmsTemplate.send("jms/queue/TodoMessageQueue", new MessageCreator() { public Message createMessage(Session session) throws JMSException { ActiveMQSession activeMQSession = (ActiveMQSession) session; // (1) BlobMessage blobMessage = activeMQSession.createBlobMessage(inputStream); // (2) return blobMessage; } }); } } }
Sr. No. Description (1)Useorg.apache.activemq.ActiveMQSession
- a Apache ActiveMQ specific API inBlobMessage
.(2)GenerateBlobMessage
fromActiveMQSession
by specifying sending data.Arguments ofcreateBlobMessage
method can be specified byFile
,InputStream
andURL
class.Receiving
An implementation example of receiving class is shown below.
[projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
package com.example.listener.todo; import java.io.IOException; import javax.inject.Inject; import javax.jms.JMSException; import org.apache.activemq.BlobMessage; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; import com.example.domain.service.todo.TodoService; @Component public class TodoMessageListener { @Inject TodoService todoService; @JmsListener(destination = "jms/queue/TodoMessageQueue") public void receiveBlobMessage(BlobMessage message) throws IOException, JMSException { todoService.fileInputBlobMessage(message); // omitted } }
[projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.apache.activemq.BlobMessage; import org.springframework.stereotype.Service; @Service public class TodoServiceImpl implements TodoService { @Override public void fileInputBlobMessage(BlobMessage message) throws IOException { try(InputStream is = message.getInputStream()){ // (1) Path path = Paths.get("outputFilePath"); Files.copy(is, path); // omitted } } }
Sr. No. Description (1)Fetch data from receivedBlobMessage
asInputStream
.