Spring Integration DirectChannel Example
1. Introduction
This article discusses the implementation of Spring Integration Direct Channel in a Spring Boot application.
Spring Integration supports Enterprise Integration patterns, of which the message channel pattern decouples the producer and consumer endpoints and is agnostic to the message payload. A message channel provides for connectivity with various components like filters and adapters. There are good articles giving more detailed information of different types of Spring Integration message channels along with their components and aspects. I have listed a few in the Useful Links section below for your further reference.
The key feature of DirectChannel
is that though it works on the publish-subscribe model, it gives each message it receives to only one of the subscribers in a round-robin manner. Thus, it is a combination of both the publish-subscribe and point-to-point transfer models.
2. Application
The application that we will build is a Book Library. The functionality is that a Book Publisher sends books to the library. The librarian sends the new arrivals to Priority Readers. Let’s assume the library is in a small town and has limited budget, so it buys only one copy of each book. The librarian is an old and quaint gentleman who sends a nice text message along with the book and the receivers take the book and reply with equal warmth. Currently the Book Publisher sends 20 books and there are three Premium Readers who have subscribed. When the books are sent by the librarian, one book goes to one Premium Reader in a round-robin fashion.
3. Environment
I have used the following technologies for this application:
- Java 1.8
- Spring Boot 1.5.8
- Maven 4.0
- Ubuntu 16.04
4. Source Code
Let’s look at the files and code. This is a Maven project, so we start with the pom file.
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.channels</groupId> <artifactId>directchannel</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>directchannel</name> <description>Spring Integration Direct Channel example</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.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.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 Book
class that serves as the model for the application.
Book.java
package org.javacodegeeks.springintegration.channels.directchannel.model; public class Book { public enum Genre { fantasy, horror, romance, thriller } private long bookId; private String title; private Genre genre; public Book() {} public long getBookId() { return bookId; } public void setBookId(long bookId) { this.bookId = bookId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Genre getGenre() { return genre; } public void setGenre(Genre genre) { this.genre = genre; } public String toString() { return String.format("Book %s, Genre: %s.", title, genre); } }
A Book
has an enum
indicating which genre
it belongs to. The other two properties are bookId
and title
.
Below is the BookPublisher
class that initiates the message flow in the application.
BookPublisher.java
package org.javacodegeeks.springintegration.channels.directchannel.incoming; import org.javacodegeeks.springintegration.channels.directchannel.model.Book; import org.javacodegeeks.springintegration.channels.directchannel.model.Book.Genre; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Component public class BookPublisher { private long nextBookId; public BookPublisher() { this.nextBookId = 1001l; } public List<Book> getBooks() { List<Book> books = new ArrayList<Book>(); books.add(createFantasyBook()); books.add(createFantasyBook()); books.add(createFantasyBook()); books.add(createFantasyBook()); books.add(createFantasyBook()); books.add(createHorrorBook()); books.add(createHorrorBook()); books.add(createHorrorBook()); books.add(createHorrorBook()); books.add(createHorrorBook()); books.add(createRomanceBook()); books.add(createRomanceBook()); books.add(createRomanceBook()); books.add(createRomanceBook()); books.add(createRomanceBook()); books.add(createThrillerBook()); books.add(createThrillerBook()); books.add(createThrillerBook()); books.add(createThrillerBook()); books.add(createThrillerBook()); return books; } Book createFantasyBook() { return createBook("", Genre.fantasy); } Book createHorrorBook() { return createBook("", Genre.horror); } Book createRomanceBook() { return createBook("", Genre.romance); } Book createThrillerBook() { return createBook("", Genre.thriller); } Book createBook(String title, Genre genre) { Book book = new Book(); book.setBookId(nextBookId++); if (title == "") { title = "# " + Long.toString(book.getBookId()); } book.setTitle(title); book.setGenre(genre); return book; } }
The main functionality of this class is to create and return a list of twenty books, five each with the fantasy, horror, romance and thriller genres. There is a book creation method for each of the genre type, which call a utility function createBook
by passing the correct enum
type. Book ids start from 1001 and are set incrementally.
Below is the Librarian
class that is the publisher endpoint of the application.
Librarian.java
package org.javacodegeeks.springintegration.channels.directchannel.pub; import org.javacodegeeks.springintegration.channels.directchannel.model.Book; import org.springframework.beans.factory.annotation.Value; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.support.MessageBuilder; import org.springframework.stereotype.Component; @Component public class Librarian { private DirectChannel channel; @Value("#{bookChannel}") public void setChannel(DirectChannel channel) { this.channel = channel; } public void sendPremiumReaders(Book book) { System.out.println("Dear Premium Reader, Just Arrived - " + book.toString()); channel.send(MessageBuilder.withPayload(book).build()); } }
This class has a private DirectChannel
member which is referenced to a channel identified as bookChannel
using the @Value
annotation. The sendPremiumReaders
method is used to publish a book with its payload to the DirectChannel
.
Below is the PremiumReader
class that is the subscriber endpoint of the application.
PremiumReader.java
package org.javacodegeeks.springintegration.channels.directchannel.sub; import org.javacodegeeks.springintegration.channels.directchannel.model.Book; import org.springframework.messaging.Message; import org.springframework.integration.MessageRejectedException; import org.springframework.messaging.MessagingException; import org.springframework.messaging.MessageHandler; import org.springframework.stereotype.Component; @Component public class PremiumReader implements MessageHandler { @Override public void handleMessage(Message<?> message) throws MessagingException { Object payload = message.getPayload(); if (payload instanceof Book) { receiveAndAcknowledge((Book) payload); } else { throw new MessageRejectedException(message, "Unknown data type has been received."); } } void receiveAndAcknowledge(Book book) { System.out.println("Hi Librarian, this is Reader #" + System.identityHashCode(this) + ". " + "Received book - " + book.toString() + "\n"); } }
In the handleMessage
method, it validates that the message payload is a Book
instance and calls the method receiveAndAcknowledge
to print an acknowledgement message to the console.
Below is the DirectchannelApplication
class that is the main class of the application.
DirectchannelApplication.java
package org.javacodegeeks.channels.directchannel; import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.integration.channel.DirectChannel; import org.springframework.messaging.MessageChannel; import org.javacodegeeks.springintegration.channels.directchannel.model.Book; import org.javacodegeeks.springintegration.channels.directchannel.incoming.BookPublisher; import org.javacodegeeks.springintegration.channels.directchannel.pub.Librarian; import org.javacodegeeks.springintegration.channels.directchannel.sub.PremiumReader; @SpringBootApplication @ComponentScan(basePackages = "org.javacodegeeks.springintegration.channels.directchannel") public class DirectchannelApplication { @Autowired private BookPublisher bookPublisher; @Autowired private Librarian librarian; @Autowired private DirectChannel library; @Bean public MessageChannel bookChannel() { return new DirectChannel(); } public static void main(String[] args) { SpringApplication.run(DirectchannelApplication.class, args); } @Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> { library.subscribe(new PremiumReader()); library.subscribe(new PremiumReader()); library.subscribe(new PremiumReader()); List<Book> books = bookPublisher.getBooks(); for (Book book : books) { librarian.sendPremiumReaders(book); } }; } }
This class is analogous to the real-world library. It takes subscriptions from premium readers, gets books from the book publisher and then sends those books to the premium readers.
It autowires BookPublisher
, Librarian
and DirectChannel
beans. It has the utility method bookChannel
to create a DirectChannel
which is invoked by the publisher class Librarian
.
How does the program identify the reader who got the book? Simply by calling System.identityHashCode
on the PremiumReader
object. This hash code is printed in the acknowledgement text. Thus, in the output shown below, you can see that the 20 books go to the three subscribed PremiumReaders
one each, in a circular manner.
5. How to Run
From the command line, use either mvn spring-boot:run
or mvn test
.
6. Useful Links
- https://www.javacodegeeks.com/2014/05/spring-integration-4-0-a-complete-xml-free-example.html
- https://www.javacodegeeks.com/2015/09/spring-integration-full-example.html
- https://www.javacodegeeks.com/2015/01/high-available-amqp-backed-message-channels-via-spring-integration-and-rabbitmq.html
7. Download the Source Code
You can download the full source code of this example here: DirectChannelExample