Spring JMS Error Handling Configuration Example
This tutorial demonstrates error handling using Spring JMS. We create a custom JMS error handler which is capable of handling all errors thrown during transportation, conversion and validation errors.
Project Structure
Let’s start by looking at the project structure.
Maven Dependencies
We use Apache Maven to manage our project dependencies. Make sure the following dependencies reside on the class-path.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.memorynotfound.integration.jms.activemq</groupId> <artifactId>error-handling</artifactId> <version>1.0.0-SNAPSHOT</version> <url>https://memorynotfound.com</url> <name>Spring Integration + ActiveMQ - ${project.artifactId}</name> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.7.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-broker</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
Spring JMS ActiveMQ Configuration
Spring Boot can automatically configure a ConnectionFactory when it detects that ActiveMQ is available on the class-path. If the broker is present, an embedded broker is started and configured automatically (as long as no broker URL is specified through configuration). For conveniences we created and configured an embedded activeMQ server. The application.yml file is located in the src/main/resources/ folder. This configuration file creates and configures an embedded ActiveMQ broker.
spring: # Embedded ActiveMQ Configuration activemq: broker-url: vm://embedded?broker.persistent=false,useShutdownHook=false in-memory: true non-blocking-redelivery: true packages: trust-all: false trusted: com.memorynotfound pool: block-if-full: true block-if-full-timeout: -1 create-connection-on-startup: true enabled: false expiry-timeout: 0 idle-timeout: 30000 max-connections: 1 maximum-active-session-per-connection: 500 reconnect-on-exception: true time-between-expiration-check: -1 use-anonymous-producers: true # Spring JMS Settings jms: listener: acknowledge-mode: auto auto-startup: true concurrency: 2 max-concurrency: 2 pub-sub-domain: false template: default-destination: delivery-mode: non_persistent priority: 100 qos-enabled: true receive-timeout: 1000 time-to-live: 36000 # Logging configuration print only current thread and messages for tutorial purposes logging: pattern: console: "[%thread]:%msg%n" level: - ".=info" - "com.memorynotfound=debug" - "org.springframework=info"
Spring ActiveMQ Configuration
The @EnableJms enables JMS listener annotated endpoints that are created under the cover by JmsListenerContainerFactory. The JmsListenerContainerFactory is responsible to create the listener container responsible for a particular endpoint. The @EnableJms annotation also enables detection of JmsListener annotations on any Spring-managed beans in the container.
We can set a custom error handler by configuring the DefaultJmsListenerContainerFactory class using the setErrorHandler() method.
The MappingJackson2MessageConverter uses Jackson to convert messages to and from JSON.
package com.memorynotfound.integration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jms.annotation.EnableJms; import org.springframework.jms.annotation.JmsListenerConfigurer; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.jms.config.JmsListenerEndpointRegistrar; import org.springframework.jms.support.converter.MappingJackson2MessageConverter; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.converter.MessageType; import javax.jms.ConnectionFactory; @EnableJms @Configuration @SuppressWarnings("SpringJavaAutowiringInspection") public class ActiveMQConfig implements JmsListenerConfigurer { public static final String ORDER_QUEUE = "order-queue"; @Autowired private ConnectionFactory connectionFactory; @Override public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { registrar.setContainerFactory(containerFactory()); } @Bean public JmsListenerContainerFactory<?> containerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setErrorHandler(new DefaultErrorHandler()); factory.setMessageConverter(messageConverter()); return factory; } @Bean public MessageConverter messageConverter() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); converter.setTargetType(MessageType.TEXT); converter.setTypeIdPropertyName("_type"); return converter; } }
Order Object
We use the Order object to send an receive JMS message to and from a JMS Message Queue.
package com.memorynotfound.integration; import java.io.Serializable; import java.math.BigDecimal; public class Order implements Serializable { private String from; private String to; private BigDecimal amount; public Order() { } public Order(String from, String to, BigDecimal amount) { this.from = from; this.to = to; this.amount = amount; } public String getFrom() { return from; } public void setFrom(String from) { this.from = from; } public String getTo() { return to; } public void setTo(String to) { this.to = to; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } @Override public String toString() { return "Order{" + "from='" + from + '\'' + ", to='" + to + '\'' + ", amount=" + amount + '}'; } }
Spring JMS Custom Error Handling
We create a custom error handler by implementing the ErrorHandler interface. This implies us to overwrite the handleError method. This allows us to intercept all errors during JMS message transportation, conversion or validations.
package com.memorynotfound.integration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ErrorHandler; public class DefaultErrorHandler implements ErrorHandler { private static Logger log = LoggerFactory.getLogger(OrderConsumer.class); @Override public void handleError(Throwable t) { log.warn("spring jms custom error handling example"); log.error(t.getCause().getMessage()); } }
Sending Messages to a JMS Queue
Now we have configured the ActiveMQ message broker, we can start sending messages to an ActiveMQ Queue. We use the JmsTemplate to send JMS messages to the queue. We simply need to pass in a destination and message arguments and the JmsTemplate handles the rest.
package com.memorynotfound.integration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.stereotype.Service; import static com.memorynotfound.integration.ActiveMQConfig.ORDER_QUEUE; @Service public class OrderSender { private static Logger log = LoggerFactory.getLogger(OrderSender.class); @Autowired private JmsTemplate jmsTemplate; public void sendQueue(Order order) { log.info("sending with convertAndSend() to " + ORDER_QUEUE + " <" + order + ">"); jmsTemplate.convertAndSend(ORDER_QUEUE, order); } }
Creating Custom Exception
We created a custom OrderProcessingException to demonstrate that an error has occurred during the order processing stage.
package com.memorynotfound.integration; public class OrderProcessingException extends RuntimeException { public OrderProcessingException(String message) { super(message); } }
Consuming JMS Messages
The @JmsListener annotation marks a method to be the target of a JMS message listener on the specified destination. After the JMS message is received we throw a new OrderProcessingException that we created to indicate that something went wrong during the order processing.
package com.memorynotfound.integration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.annotation.JmsListener; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.stereotype.Component; import javax.validation.Valid; import static com.memorynotfound.integration.ActiveMQConfig.ORDER_QUEUE; @Component public class OrderConsumer { private static Logger log = LoggerFactory.getLogger(OrderConsumer.class); @JmsListener(destination = ORDER_QUEUE) public void receiveMessage(@Payload Order order) { log.info("received <" + order + ">"); // processing or the order throw new OrderProcessingException("problem occurred while processing the order."); } }
Spring JMS Error Handling Configuration Example
We bootstrap the application using Spring Boot. When the application is initialized, we simply send a couple of messages to a JMS queue and print the output to the console.
package com.memorynotfound.integration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.math.BigDecimal; import java.util.concurrent.TimeUnit; @SpringBootApplication public class Run implements ApplicationRunner { private static Logger log = LoggerFactory.getLogger(Run.class); @Autowired private OrderSender orderSender; @Override public void run(ApplicationArguments applicationArguments) throws Exception { log.info("Spring JMS Validate Messages using JSR-303 Bean Validation"); orderSender.sendQueue(new Order("me", "lukas", new BigDecimal(48))); orderSender.sendQueue(new Order("me", "underwood", new BigDecimal(73))); orderSender.sendQueue(new Order("me", "nancy", new BigDecimal(2))); log.info("Waiting for all ActiveMQ JMS Messages to be consumed"); TimeUnit.SECONDS.sleep(3); System.exit(-1); } public static void main(String[] args) throws Exception { SpringApplication.run(Run.class, args); } }
Example Output
The previous application prints the following output to the console. The first instance has some invalid constraint validations and will produce a validation error. The latter are validated correctly and are handled correctly.
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.7.RELEASE) ... Spring JMS Error Handling Configuration Example sending with convertAndSend() to order-queue <Order{from='me', to='lukas', amount=48}> sending with convertAndSend() to order-queue <Order{from='me', to='underwood', amount=73}> sending with convertAndSend() to order-queue <Order{from='me', to='nancy', amount=2}> Waiting for all ActiveMQ JMS Messages to be consumed received <Order{from='me', to='lukas', amount=48}> problem occurred while processing the order. received <Order{from='me', to='underwood', amount=73}> problem occurred while processing the order. received <Order{from='me', to='nancy', amount=2}> problem occurred while processing the order.
Download
From:一号门
COMMENTS