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.StreamMessageandjavax.jms.TextMessageare offered.ObjectMessageis 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-messagingcan 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.Messageoffered by spring-messaging is used to comply with implementation method of messaging.However,javax.jms.Messageis used when it is preferable to useorg.springframework.jms.core.JmsTemplate.
org.springframework.jms.core.JmsMessagingTemplateandorg.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,
JmsMessagingTemplatewhich can handleorg.springframework.messaging.Messageis used.SinceJmsMessagingTemplatewrapsJmsTemplate, configuration can be done by usingJmsTemplateproperty.However, sometimes it is preferable to use aJmsTemplateas it is. Specific use cases are explained later.
org.springframework.jms.listener.DefaultMessageListenerContainerDefaultMessageListenerContainerreceives messages from Queue and initiatesMessageListenerwhich processes the received messages.
@org.springframework.jms.annotation.JmsListener- A marker annotation which indicates that it is a method to be handled as
MessageListenerof JMS.A@JmsListenerannotation 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.JmsMessagingTemplatedelegates the process toJmsTemplate. (2)JmsTemplatefetchesjavax.jms.ConnectionfromConnectionFactoryfetched through JNDI. (3)JmsTemplatepassesDestinationand messages toMessageProducer.MessageProduceris generated fromjavax.jms.Session. (Sessionis generated fromConnectionfetched in (2).)Further,Destinationis fetched through JNDI based on “Destination name for sending” passed in (1). (4)MessageProducersends messages toDestinationfor sending.
8.2.1.3.2. When messages are received asynchronously¶
Sr. No. Description (1) FetchConnectionfromConnectionFactoryfetched through JNDI. (2)DefaultMessageListenerContainerpassesDestinationtoMessageConsumer.MessageConsumeris generated fromSession. (Sessionis generated fromConnectionfetched in (1))Further,Destinationis fetched through JNDI based on “Destination name for receiving” specified in@JmsListenerannotation. (3)MessageConsumerreceives messages fromDestination. (4) A method (listener method) configured by@JmsListenerannotation inMessageListeneris 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.JmsMessagingTemplatedelegates the process toJmsTemplate. (2)JmsTemplatefetchesConnectionfromConnectionFactoryfetched through JNDI. (3)JmsTemplatepassesDestinationand messages inMessageConsumer.MessageConsumeris generated fromSession. (Sessionis generated fromConnectionfetched in (2).)Further,Destinationis fetched through JNDI based on “Destination name for receiving” passed in (1). (4)MessageConsumerreceives the messages fromDestination.Message is returned to the Service throughJmsTemplateorJmsMessagingTemplate.
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-jmsis dependent onspring-messaging,spring-messagingis 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 ofConnectionFactoryoffered by application server, injndi-nameattribute.Sinceresource-refattribute is set totrueby 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,
ConnectionFactorycan be used even when a Bean is defined for implementation class ofConnectionFactory. In such a case, implementation class ofConnectionFactoryis 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/), settruewhen it is auto-assigned. Default value isfalse.Warning
Note that
resource-refattribute 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.CachingConnectionFactorywhich performs caching ofSessionandMessageProducer/Consumer.JMS provider specificConnectionFactorylooked 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 specificConnectionFactorywhich 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.JmsTemplatealternates for a low level API handling (JMS API calling).For attributes that can be configured, refer list of attributes ofJmsTemplategiven below. (5) Define a Bean forJmsMessagingTemplate. InjectJmsTemplatewhich alternates as a synchronous sending process.
JmsTemplate for synchronous sending are as below.
Sr. No. Configuration item Details Mandatory Default value
connectionFactory ConfigureConnectionFactoryto be used.○ Nil (since it is mandatory)
pubSubDomain Configure for a messaging model.Setfalsefor PTP (Queue) model and settruefor Pub/Sub (Topic).- false
sessionTransacted Specify whether the transaction control is performed in the session.In this guideline,falseis 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 useJndiDestinationResolverwhich performs name resolution in JNDI.- DynamicDestinationResolver(*2) is used.(WhenDynamicDestinationResolveris 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 thatexplicitQosEnableddescribed 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 thatexplicitQosEnableddescribed 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 thatexplicitQosEnableddescribed later must be set totrue.- 0 (Unrestricted)
explicitQosEnabled SpecifytruewhendeliveryMode,priorityandtimeToLiveare 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.Serializableinterface 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 usingconvertAndSendmethod ofJmsMessagingTemplate, JavaBean of argument is converted to implementation class oforg.springframework.messaging.Messageinterface and the message is synchronously sent for a specified Destination.org.springframework.jms.support.converter.SimpleMessageConverteris used in the default conversion.WhenSimpleMessageConverteris used, the class which implementsjavax.jms.Message,java.lang.String,byte array,java.util.Mapandjava.io.Serializableinterface 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 JmsMessagingTemplateconvertJmsExceptionmethod ofJmsMessagingTemplateMessagingException(*1) and its sub-exceptionJmsTemplateconvertJmsAccessExceptionmethod ofJmsAccessorJmsException(*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 usingconvertAndSendmethod ofJmsMessagingTemplate.Warning
Regarding header attributes that can be edited
Components of header attributes (
JMSDestination,JMSDeliveryMode,JMSExpiration,JMSMessageID,JMSPriority,JMSRedeliveredandJMSTimestamp) are handled as read-only while converting messages usingSimpleMessageConverterof 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,JMSDeliveryModeandJMSPrioritycan be configured inJmsTemplateunit. For details, refer list of attributes ofJmsTemplateof 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
@Transactionalannotation is assigned, a Bean registered by Bean nametransactionManageris used as a default. (For details, refer Settings for using transaction management )In Blank project, since
DataSourceTransactionManageris defined by Bean name calledtransactionManager, Bean is defined with an alias in the configuration above.Hence, when only one
TransactionManageris used in the application, specification oftransactionManagerattribute in@Transactionalannotation can be omitted by usingtransactionManageras a Bean name. (2) SpecifyCachingConnectionFactorywhich 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@Transactionalannotation.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 whereinsendJmsTransactionManagerof Transaction control is used in JMS transaction control whereastransactionManagerdefined in default configuration of Blank project is used in DB transaction control.
[projectName]-domain/src/main/java/com/example/domain/service/todo/TransactionalTodoServiceImpl.javapackage 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.javaimport 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@Transactionalannotation.SpecifysendJmsTransactionManagerwhich performs transaction control of JMS. (2) Declare transaction boundary of DB by using@Transactionalannotation.Since value is omitted, Bean nametransactionManageris referred as a default.For details of@Transactionalannotation, 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 whereinsendJmsTransactionManagerof Transaction control is used in JMS transaction management whereastransactionManagerdefined 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.javapackage 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 specifyingsendChainedTransactionManagerin@Transactionalannotation.For details of@Transactionalannotation, 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
@JmsListenerannotation. - 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
@JmsListenerannotation. - Specify
@JmsListenerannotation 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/jmsas a value.For details of JMS Namespace, refer JMS Namespace Support.xsi:schemaLocation Specify URL of schema.Addhttp://www.springframework.org/schema/jmsandhttp://www.springframework.org/schema/jms/spring-jms.xsdto values. (2)- Enable JMS related annotation function of@JmsListenerannotation and@SendToannotation by using<jms:annotation-driven />. (3)- ConfigureDefaultMessageListenerContainerby assigning parameters to the factory which generateDefaultMessageListenerContainer, by using<jms:listener-container/>.connection-factoryattribute which can specify a Bean ofConnectionFactoryto be used, exists in the<jms:listener-container/>attribute. Default value ofconnection-factoryattribute isconnectionFactory.In this example, since Bean (Bean name isconnectionFactory) ofConnectionFactoryshown in Configuration of ConnectionFactory is used,connection-factoryattribute 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
DefaultMessageListenerContaineris equipped with an independent cache function,CachingConnectionFactoryshould not be used in case of asynchronous receiving. For details, refer Javadoc of DefaultMessageListenerContainer. For above,ConnectionFactorydefined in Configuration of ConnectionFactory should be specified inconnection-factoryattribute of<jms:listener-container/>.concurrency Specify upper limit of parallel number of each listener method managed byDefaultMessageListenerContainer.Default value forconcurrencyattribute 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,
concurrencyattribute of@JmsListenerannotation can be used.destination-resolver Specify Bean name ofDestinationResolverwhich is used to resolve Destination name at the time of asynchronous receiving.For Bean definition ofDestinationResolver, refer Configuration of DestinationResolver.Whendestination-resolverattribute is not specified,DynamicDestinationResolvergenerated inDefaultMessageListenerContaineris used.factory-id Specify name ofDefaultJmsListenerContainerFactorywhich defines a Bean.Since@JmsListenerannotation refers Bean namejmsListenerContainerFactoryas a default, it is recommended to consider Bean name asjmsListenerContainerFactoryin case of a single<jms:listener-container/>.cache Specify cache level to determine cache targets likeConnection,SessionorConsumeretc.Default isauto.When Connection is not pooled in the application server while configuringtransaction-managerattribute described later, specifyingconsumeris recommended for performance improvement.Note
In case of
auto, whentransaction-managerattribute is not configured, behaviour is same asconsumer(Consumeris cached). However, behaviour is same asnone(invalid cache) considering the pooling in the application server using global transaction at the time oftransaction-managerattribute 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
@JmsListenerannotation 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@JmsListenerannotation for a method, for asynchronous receiving. Specify Destination name for receiving indestinationattribute.A list of main attributes of
@JmsListenerannotation 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 ofDefaultJmsListenerContainerFactorywhich 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 forconcurrencyattribute 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@Headerannotation to fetch value of header attributeJMSReplyToof 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
@SendToannotation which specifies a destination, for a method defined by@JmsListenerannotation.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@SendToannotation. (2) Return data to be sent to Destination defined in@SendToannotation.Permissible return value types are the classes which implementorg.springframework.messaging.Message,javax.jms.Message,String,bytearray,MapandSerializableinterface.
- When sending destination of process results is changed dynamically
When sending destination is to be changed dynamically,forDestinationorforQueuemethods ofJmsResponseclass 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,forDestinationorforQueuemethods ofJmsResponseclass are used.In this example, messages are sent from Destination name by usingforQueuemethod.Note
When
forQueuemethod ofJmsResponseclass is used, Destination name is used as a string. For resolving destination name,DestinationResolverspecified inDefaultMessageListenerContaineris 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 attributeJMSReplyToof 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 usingselectorattribute.TodoStatusof header attribute receives onlydeletedmessages.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@Validatedannotation. (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,@Nullannotation 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) CaptureConstraintViolationExceptionwhich 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 forJmsTransactionManagerof asynchronous receiving. (2) SpecifyConnectionFactorywhich manages a transaction. Note that,CachingConnectionFactorycannot 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,SessionorConsumer.Default isauto.As described earlier in Basic asynchronous receiving,consumeris specified when connection is not pooled in the application server.transaction-manager Specify Bean name forJmsTransactionManagerto be used.Note that,JmsTransactionManagerdoes not manageCachingConnectionFactory.Warning
Since
ConnectionorSessioncaching 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@Transactionalannotation in the Service class called by listener method.Implementation example is given below whereinjmsListenerContainerFactoryof Transaction control is used for transaction control of JMS andtransactionManagerdefined 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@JmsListenerannotation and specifyDefaultJmsListenerContainerFactorywhich enables transaction control of JMS.Since@JmsListenerannotation refers to Bean namejmsListenerContainerFactoryas a default,containerFactoryattribute is omitted.(2)Define transaction boundary of DB.Since value is omitted, Bean nametransactionManageris referred as a default.For details of@Transactionalannotation, 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 whereinjmsAsyncReceiveTransactionManagerof Transaction control is used in JMS transaction control andtransactionManagerdefined 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-idSpecify Bean name ofDefaultJmsListenerContainerFactory.In this example, Bean name is set tochainedTransactionJmsListenerContainerFactoryby considering its combined use with<jms:listener-container/>of Basic asynchronous receiving.transaction-managerSpecify Bean name forChainedTransactionManager.acknowledgeSpecifytransactedin check response mode in order to enable the transaction. Default isauto.Note
In
DefaultMessageListenerContainer, when Bean of implementation class oforg.springframework.transaction.support.ResourceTransactionManageris specified intransaction-managerattribute, the transaction control which uses this Bean is enabled. However, sinceChainedTransactionManagerdoes not implementResourceTransactionManager, transaction control is not enabled.transactedmust be specified inacknowledgeattribute in order to enable transaction control. Accordingly, Session acting as a target for transaction control is generated and transaction control forChainedTransactionManageris 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 usesDefaultJmsListenerContainerFactoryofchainedTransactionJmsListenerContainerFactory,chainedTransactionJmsListenerContainerFactoryis specified incontainerFactoryattribute.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.JMSMessageIDrecorded earlier andJMSMessageIDof 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¶
Table-Exception handling patterns¶ 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 inDefaultMessageListenerContainerfor 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 usingforQueuemethod ofJmsResponseclass.In this example, sinceBusinessExceptionwhich 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 ofErrorHandlerdefined inerror-handlerattribute 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-handlerattribute 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 implementsErrorHandlerinterface.(2)Exception occurring in the listener method is wrapped inorg.springframework.jms.listener.adapter.ListenerExecutionFailedExceptionand 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.CachingConnectionFactorywhich performs caching ofSessionandMessageProducer/Consumer.Cache function can be used by using JMS provider specificConnectionFactorywhich is looked up in Bean definition or JNDI name, by wrapping it inCachingConnectionFactoryinstead of using it as it is. (2) SpecifyConnectionFactorywhich 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.JmsTemplatealternates for a low level API handling (JMS API calling).For the attributes that can be configured, refer list of attributes ofJmsTemplateshown below. (5) Define a Bean forJmsMessagingTemplate. InjectJmsTemplatewhich alternates as a synchronous receiving process.
JmsTemplate related to synchronous receiving are shown below.
Sr. No. Configuration item Details Mandatory Default value
connectionFactory SetConnectionFactoryto be used.○ Nil (since it is mandatory)
pubSubDomain Set for messaging model.Setfalsefor PTP (Queue) model andtruefor Pub/Sub (Topic).- false
sessionTransacted Set whether the transaction is controlled in the session.In this guideline, since transaction control described later is used, defaultfalseis 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 settingJndiDestinationResolverwhich performs name resolution by JNDI.- DynamicDestinationResolver(*2) is used.(IfDynamicDestinationResolveris 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 usingreceiveAndConvertmethod ofJmsMessagingTemplate.receiveAndConvertmethod 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 byMessageobject of Spring Framework, by usingreceivemethod.
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.hawtbufandcom.thoughtworks.xstream.mapperare 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-jmslibrary.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-clientis added to pom.xml of domain project and web project as a library for build.Further,activemq-clientand 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-clientlibrary, 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 ofConnectionFactorymust 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 forConnectionFactoryof 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) WhenJmsMessagingTemplateis used,MessageCreateris generated at the time of sending. Hence,JmsTemplatewhich can define generation ofMessageCreaterby isolating it from sending, is used. (2) Generate instance ofMessageCreatorfor creatingMessageof JMS. (3) When messages are sent bysendmethod ofJmsTemplateclass, instances ofMessageCreatorare 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,MessageandCachingConnectionFactoryoffered by Spring Framework cannot be used. It is recommended to separately defineJmsTemplateforBlobMessagewhile usingBlobMessageconsidering 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.uploadUrlwherein 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 Messageis 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)GenerateBlobMessagefromActiveMQSessionby specifying sending data.Arguments ofcreateBlobMessagemethod can be specified byFile,InputStreamandURLclass.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 receivedBlobMessageasInputStream.








