Integration

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:

Terminal output showing order messages sent and the split parts with their headers

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

Download
You can download the full source code of this example here: splitter.zip

Mahboob Hussain

Mahboob Hussain graduated in Engineering from NIT Nagpur, India and has an MBA from Webster University, USA. He has executed roles in various aspects of software development and technical governance. He started with FORTRAN and has programmed in a variety of languages in his career, the mainstay of which has been Java. He is an associate editor in our team and has his personal homepage at http://bit.ly/mahboob
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Barani
Barani
1 year ago

Really superb explanation. thanks

Back to top button