Integration

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 Librarianclass 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.

Application output showing each receiver getting only one message

5. How to Run

From the command line, use either mvn spring-boot:run or mvn test.

6. Useful Links

7. Download the Source Code

Download
You can download the full source code of this example here: DirectChannelExample

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.

0 Comments
Inline Feedbacks
View all comments
Back to top button