Spring Integration Splitter Example
1. Introduction
Spring Integration provides many components for processing messages before they reach their end point. Splitter is the component that breaks down a message into multiple messages based on specific criteria.
The benefit is that after splitting, the system can apply separate business logic on each part. For example, in an order management system, separate parts of the order can be used to send emails to specific vendors or update the credit card management module etc.
2. Application
In this article, we will show an example where we process orders sent to a message channel, split them into separate messages based on their fields and send to a second message channel. Here the split messages are processed one by one.
3. Environment
I have used the following technologies for this application:
- Java 1.8
- Spring Boot 1.5.9
- Maven 3.3.9
- Ubuntu 16.04 LTS
4. Source Code
This is a maven-based project, so all the project-level settings and dependencies are given in pom.xml.
pom.xml
<?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>org.javacodegeeks.springintegration</groupId> <artifactId>splitter</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>splitter</name> <description>Spring Integration Splitter using Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Below is the Order
class that serves as the domain model of the system.
Order.java
package org.javacodegeeks.springintegration.process.model; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @NoArgsConstructor @ToString public class Order { public enum PaymentMethod { CC, COD, COUPON // CC = credit card, COD = cash on delivery } private String id; private int value; private PaymentMethod payment_method; }
An Order
has three fields. They are id
, value
, and payment_method
which is of type PaymentMethod
enum
. The lombok
annotations @Getter
, @Setter
, @NoArgsConstructor
, and @ToString
inject the setters, getters, no-argument constructor and the toString()
method.
Below is the Part
class that serves as the model for the messages split from each Order
.
Part.java
package org.javacodegeeks.springintegration.process.model; import java.io.Serializable; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @NoArgsConstructor @ToString public class Part implements Serializable { private static final long serialVersionUID = 1L; public enum Descriptor { ID, VALUE, PAYMENT_METHOD } private Descriptor descriptor; private String value; public Part(Descriptor d, String value) { this.descriptor = d; this.value = value; } public boolean equals(Object o) { Part f = (Part) o; return (f != null && f.value.equals(this.value)); } }
A Part
has two properties. They are descriptor
of type enum
Descriptor
and value
. The Descriptor
values mirror the Order
properties. The class has a two-argument constructor that takes two arguments to set the values for these properties.
Below is the OrderCreator
class that creates the Order
messages.
OrderCreator.java
package org.javacodegeeks.springintegration.process.incoming; import java.util.ArrayList; import java.util.List; import org.javacodegeeks.springintegration.process.model.Order; import org.springframework.stereotype.Component; @Component public class OrderCreator { public List createOrders() { List orders = new ArrayList(); Order order = new Order(); order.setId("1001"); order.setValue(10000); order.setPayment_method(Order.PaymentMethod.CC); orders.add(order); order = new Order(); order.setId("1002"); order.setValue(20000); order.setPayment_method(Order.PaymentMethod.COD); orders.add(order); order = new Order(); order.setId("1003"); order.setValue(30000); order.setPayment_method(Order.PaymentMethod.COUPON); orders.add(order); order = new Order(); order.setId("1004"); order.setValue(40000); order.setPayment_method(Order.PaymentMethod.CC); orders.add(order); order = new Order(); order.setId("1005"); order.setValue(50000); order.setPayment_method(Order.PaymentMethod.COD); orders.add(order); for (Order ord : orders) System.out.println("Added order " + ord.toString()); System.out.println("+++++++++++++++++++++++++++++++++++++++"); return orders; } }
This class simulates an external system sending a message feed. In the createOrders
method, we create five orders, add them to a ArrayList
and return it.
Below is the OrderSplitter
class which is responsible for splitting the Order
messages.
OrderSplitter.java
package org.javacodegeeks.springintegration.process.splitter; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.javacodegeeks.springintegration.process.model.Order; import org.javacodegeeks.springintegration.process.model.Part; import org.springframework.integration.annotation.Splitter; import org.springframework.stereotype.Component; @Component public class OrderSplitter { @Splitter(inputChannel = "orderInputChannel", outputChannel = "orderSplitterChannel") public Collection splitItem(Order order) { List messages = new ArrayList(); Part part = new Part(Part.Descriptor.ID, order.getId()); messages.add(part); part = new Part(Part.Descriptor.VALUE, String.valueOf(order.getValue())); messages.add(part); part = new Part(Part.Descriptor.PAYMENT_METHOD, order.getPayment_method().toString()); messages.add(part); return messages; } }
This class has a splitItem
method which is annotated with @Splitter
. The inputChannel
is specified as orderInputChannel
and the outputChannel
is specified as orderSplitterChannel
. For each Order
in the channel, three Part
objects are created, one for each property, and added to an ArrayList
which is returned.
Below is the OrderPartsProcessor
class which processes the split messages.
OrderPartsProcessor.java
package org.javacodegeeks.springintegration.process.splitter; import java.text.MessageFormat; import java.util.Map; import org.javacodegeeks.springintegration.process.model.Part; import org.springframework.integration.annotation.Headers; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.stereotype.Component; @Component public class OrderPartsProcessor { @ServiceActivator(inputChannel = "orderSplitterChannel") public void handlePart(Part data, @Headers Map headerMap) { System.out.println( MessageFormat.format("Message with {0} : {1}", data.getDescriptor().toString(), data.getValue())); System.out.print("Headers -- "); for (String key : headerMap.keySet()) { Object value = headerMap.get(key); if (key != "sequenceSize" && key != "timestamp") System.out.print(MessageFormat.format("{0} : {1}. ", key, value)); } System.out.println(); } }
This class has a handlePart
method annotated with @ServiceActivator
whose inputChannel
is specified as orderSplitterChannel
. In this method, we print the descriptor
and value
of each part and its message headers. In real-world systems, this method would have processing code based on each part.
Below is the SplitterApplication
class that is the main class of the application.
SplitterApplication.java
package org.javacodegeeks.springintegration.process; import org.javacodegeeks.springintegration.process.incoming.OrderCreator; import org.javacodegeeks.springintegration.process.model.Order; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.MessageChannel; @SpringBootApplication public class SplitterApplication { public static void main(String[] args) { SpringApplication.run(SplitterApplication.class, args); } @Bean public CommandLineRunner commandLineRunner(ApplicationContext context) { return args -> { MessageChannel channel = context.getBean("orderInputChannel", MessageChannel.class); OrderCreator orderCreator = context.getBean("orderCreator", OrderCreator.class); System.out.println("Sending orders to input channel"); for (Order order : orderCreator.createOrders()) { channel.send(MessageBuilder.withPayload(order).build()); } }; } }
In this class, we first get a reference to a orderInputChannel
as well as a orderCreator
bean. We then call the createOrders
method to get a list of orders that are sent in a loop to the input channel orderInputChannel
on which OrderSplitter
executes the splitItem
method and sends the individual Part
messages to the output channel orderSplitterChannel
. The OrderPartsProcessor
executes the handlePart
method on each split message to print its properties and headers. You can see the sent messages (orders) and the split messages (parts) in the output shown below:
5. How To Run
At the command line, use
mvn spring-boot:run
6. Summary
In this example, we have seen the usage of Spring Integration Splitter component in a Spring Boot application. This was shown with the interaction of the Splitter
and ServiceActivator
components with DirectChannel
.
7. Download the Source Code
You can download the full source code of this example here: splitter.zip
Really superb explanation. thanks