This guide walks you through the process of creating an asynchronous, event-driven system using the Reactor project.

What you’ll build

You’ll build an application that fires off events to fetch a random Spring Boot quote, and then asynchronously gathers them together.

What you’ll need

How to complete this guide

Like most Spring Getting Started guides, you can start from scratch and complete each step, or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

To start from scratch, move on to Build with Gradle.

To skip the basics, do the following:

When you’re finished, you can check your results against the code in gs-messaging-reactor/complete.

Build with Gradle

Build with Gradle

First you set up a basic build script. You can use any build system you like when building apps with Spring, but the code you need to work with Gradle and Maven is included here. If you’re not familiar with either, refer to Building Java Projects with Gradle or Building Java Projects with Maven.

Create the directory structure

In a project directory of your choosing, create the following subdirectory structure; for example, with mkdir -p src/main/java/hello on *nix systems:

└── src
    └── main
        └── java
            └── hello

Create a Gradle build file

build.gradle

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-snapshot" }
        mavenLocal()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.8.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'gs-messaging-reactor'
    version =  '0.2.0'
}

repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/libs-snapshot" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    compile("org.projectreactor.spring:reactor-spring-context")
    compile("com.fasterxml.jackson.core:jackson-databind")
    compile("org.springframework:spring-web")
    testCompile("junit:junit")
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.0'
}

The Spring Boot gradle plugin provides many convenient features:

  • It collects all the jars on the classpath and builds a single, runnable "über-jar", which makes it more convenient to execute and transport your service.

  • It searches for the public static void main() method to flag as a runnable class.

  • It provides a built-in dependency resolver that sets the version number to match Spring Boot dependencies. You can override any version you wish, but it will default to Boot’s chosen set of versions.

Build with Maven

Build with Maven

First you set up a basic build script. You can use any build system you like when building apps with Spring, but the code you need to work with Maven is included here. If you’re not familiar with Maven, refer to Building Java Projects with Maven.

Create the directory structure

In a project directory of your choosing, create the following subdirectory structure; for example, with mkdir -p src/main/java/hello on *nix systems:

└── src
    └── main
        └── java
            └── hello

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.springframework</groupId>
    <artifactId>gs-messaging-reactor</artifactId>
    <version>0.2.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.8.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectreactor.spring</groupId>
            <artifactId>reactor-spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <start-class>hello.Application</start-class>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

The Spring Boot Maven plugin provides many convenient features:

  • It collects all the jars on the classpath and builds a single, runnable "über-jar", which makes it more convenient to execute and transport your service.

  • It searches for the public static void main() method to flag as a runnable class.

  • It provides a built-in dependency resolver that sets the version number to match Spring Boot dependencies. You can override any version you wish, but it will default to Boot’s chosen set of versions.

Build with Spring Tool Suite

Build with Spring Tool Suite

If you have Spring Tool Suite, then you can simply import this guide directly.

Create a representation for a quote

For this event-driven example, you’ll fetch quotes from The Spring Boot quote database. The JSON format looks like this:

{
	"type": "success",
	"value": {
		"id": 10,
		"quote": "Really loving Spring Boot, makes stand alone Spring apps easy."
	}
}

The easiest thing to do is capture the inner value, i.e. the quote, with one class and then wrap the whole in another class.

src/main/java/hello/Quote.java
package hello;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class Quote {

    Long id;
    String quote;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getQuote() {
        return quote;
    }

    public void setQuote(String quote) {
        this.quote = quote;
    }

}

This class contains both the id and the quote text supplied from the website. @JsonIgnoreProperties(ignoreUnknown=true) signals that any other attributes are to be ignored.

The wrapper class looks like this:

src/main/java/hello/QuoteResource.java
package hello;

public class QuoteResource {

    String type;
    Quote value;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Quote getValue() {
        return value;
    }

    public void setValue(Quote value) {
        this.value = value;
    }

}

The wrapper class has the type attribute along with a Quote. This makes it easy later to use Spring’s RestTemplate and convert JSON to a POJO with the Jackson binding library.

Create a receiver

An asynchronous application has publishers and receivers. To create the receiver, implement a receiver with a method to respond to events:

src/main/java/hello/Receiver.java

package hello;

import java.util.concurrent.CountDownLatch;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import reactor.event.Event;
import reactor.function.Consumer;

@Service
class Receiver implements Consumer<Event<Integer>> {

    @Autowired
    CountDownLatch latch;

    RestTemplate restTemplate = new RestTemplate();

    public void accept(Event<Integer> ev) {
		QuoteResource quoteResource = restTemplate.getForObject("http://gturnquist-quoters.cfapps.io/api/random", QuoteResource.class);
        System.out.println("Quote " + ev.getData() + ": " + quoteResource.getValue().getQuote());
        latch.countDown();
    }

}

The Receiver implements the Consumer interface by implementing the accept() method. It is geared to receive Event<Integer>.

For this example, every time the Receiver receives an integer, it fetches another Spring Boot quote using Spring’s RestTemplate. Then it signals its completion to the CountDownLatch to coordinate when all events have been processed.

Receiver has the @Service annotation so it will be automatically registered with the application context.

Create a publisher

The next step is to publish a handful of events to the reactor.

src/main/java/hello/Publisher.java

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.Reactor;
import reactor.event.Event;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class Publisher {

    @Autowired
    Reactor reactor;

    @Autowired
    CountDownLatch latch;

    public void publishQuotes(int numberOfQuotes) throws InterruptedException {
        long start = System.currentTimeMillis();

        AtomicInteger counter = new AtomicInteger(1);

        for (int i=0; i < numberOfQuotes; i++) {
            reactor.notify("quotes", Event.wrap(counter.getAndIncrement()));
        }

        latch.await();

        long elapsed = System.currentTimeMillis()-start;

        System.out.println("Elapsed time: " + elapsed + "ms");
        System.out.println("Average time per quote: " + elapsed/ numberOfQuotes + "ms");
    }

}

The code uses a for loop to publish a fixed number of events. An AtomicInteger is used to fashion a unique number, which gets turned into a Reactor event with Event.wrap(). The event is published to the quotes channel using reactor.notify().

Reactor events can contain any type of POJO. This guide uses a very simple integer, but a more detailed event can be used if more information needs to be transmitted to the receiver.

Receiver has the @Service annotation so it will be automatically registered with the application context.

The code is a bit contrived in that it manually sends a fixed number of integers. In production, this would be replaced by some triggering input, perhaps using Reactor’s TcpServer to respond to incoming data.

Create an Application class

The final step in putting together your application is to register the components and then invoke them.

src/main/java/hello/Application.java

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import reactor.core.Environment;
import reactor.core.Reactor;
import reactor.core.spec.Reactors;

import java.util.concurrent.CountDownLatch;

import static reactor.event.selector.Selectors.$;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application implements CommandLineRunner {

    private static final int NUMBER_OF_QUOTES = 10;

    @Bean
    Environment env() {
        return new Environment();
    }

    @Bean
    Reactor createReactor(Environment env) {
        return Reactors.reactor()
                .env(env)
                .dispatcher(Environment.THREAD_POOL)
                .get();
    }

    @Autowired
    private Reactor reactor;

    @Autowired
    private Receiver receiver;

    @Autowired
    private Publisher publisher;

    @Bean
    public CountDownLatch latch() {
        return new CountDownLatch(NUMBER_OF_QUOTES);
    }

    @Override
    public void run(String... args) throws Exception {
        reactor.on($("quotes"), receiver);
        publisher.publishQuotes(NUMBER_OF_QUOTES);
    }

    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(Application.class, args);
    }

}

The Reactor environment is defined with the environment() method. The environment gets fed into the reactor() method to create an asynchronous reactor. In this case, you are using the THREAD_POOL dispatcher.

Reactor has four dispatchers to pick from: synchronous, ring buffer, thread pool, and event loop.

  • Synchronous is typically used inside a consumer, especially if you use Stream s and Promise s.

  • Ring buffer is used for large volumes of non-blocking events and is based on the LMAX disruptor.

  • Thread pool is ideal for longer running tasks that might be IO bound, and when it doesn’t matter what thread they are run on.

  • Event loop is used when you need all events on the exact same thread.

It also defines the number of events to send at the top with NUMBER_OF_QUOTES and creates a CountDownLatch with the latch() method.

The Application class is tagged with the @Configuration and @ComponentScan annotations. This lets it define the application context while also scanning the hello package for the @Service objects.

The main() method fetches the reactor and the receiver. It then registers the receiver to digest events on the "quotes" selector. With everything registered, it uses the Publisher to send out a series of quote-fetching events.

The CountDownLatch then waits until every thread reports that it’s done before proceeding.

Build an executable JAR

If you are using Gradle, you can run the application using ./gradlew bootRun.

You can build a single executable JAR file that contains all the necessary dependencies, classes, and resources. This makes it easy to ship, version, and deploy the service as an application throughout the development lifecycle, across different environments, and so forth.

./gradlew build

Then you can run the JAR file:

java -jar build/libs/gs-messaging-reactor-0.1.0.jar

If you are using Maven, you can run the application using mvn spring-boot:run. Or you can build the JAR file with mvn clean package and run the JAR by typing:

java -jar target/gs-messaging-reactor-0.1.0.jar
The procedure above will create a runnable JAR. You can also opt to build a classic WAR file instead.

You should see output similar to this:

Quote 5: The real benefit of Boot, however, is that it's just Spring. That means any direction the code takes, regardless of complexity, I know it's a safe bet.
Quote 8: The real benefit of Boot, however, is that it's just Spring. That means any direction the code takes, regardless of complexity, I know it's a safe bet.
Quote 7: Working with Spring Boot is like pair-programming with the Spring developers.
Quote 1: Really loving Spring Boot, makes stand alone Spring apps easy.
Quote 3: @springboot with @springframework is pure productivity! Who said in #java one has to write double the code than in other langs? #newFavLib
Quote 2: Really loving Spring Boot, makes stand alone Spring apps easy.
Quote 4: Really loving Spring Boot, makes stand alone Spring apps easy.
Quote 6: Previous to Spring Boot, I remember XML hell, confusing set up, and many hours of frustration.
Quote 10: Spring has come quite a ways in addressing developer enjoyment and ease of use since the last time I built an application using it.
Quote 9: Really loving Spring Boot, makes stand alone Spring apps easy.
Elapsed time: 206ms
Average time per quote: 20ms

The events were dispatched in order, one through ten. But the output shows that they were consumed asynchronously due to the results being out of order.

Summary

Congratulations! You’ve just developed an asynchronous, message-driven system using the Reactor project. This is just the beginning of what you can build with it.