Open source Java projects: Spring Integration

Develop a robust message-passing architecture with Spring Integration

Get an overview of Spring Integration's out-of-the-box event-driven messaging architecture, which you can use to coordinate messages, channels, adapters, and gateways. Then practice using ActiveMQ and JMS in a Spring Integration solution, followed by a short introduction to stitching together multiple application workflows for both lightweight and heavyweight payloads.

Spring Integration is an enterprise integration framework that provides out-of-the-box implementation of the patterns in the now-classic Enterprise Integration Patterns book. Building on Spring's Inversion of Control design pattern, Spring Integration abstracts message sources and destinations and uses message passing and message manipulation to integrate various components within the application environment. Applications built with Spring Integration are able to send messages between components, either across a message bus to another server in your environment or even to another class within the same virtual machine.

I'll get you started with Spring Integration in this second of three Open source Java projects installments focusing on Spring projects. I'll start with an overview of the core components of Spring Integration's support for event-driven architecture (EDA), then develop a simple example to familiarize you with how Spring Integration works. I'll conclude by demonstrating a more complex scenario that integrates components across an ActiveMQ message bus via JMS.


Created by Steven Haines for JavaWorld. March 2014.
 

Spring Integration's event-driven architecture

Event-driven architecture is one of the most powerful and successful patterns used for enterprise integration, and is the main focus of examples in this article. In an event-driven architecture, a system publishes events as they happen. Components within a given system listen for specific events, or types of events, occurring within that system. When an event of interest occurs, the components are alerted and can respond as necessary.

Event-driven architecture affords a high degree of loose coupling and enhances system scalability because message producers don't need to know anything about their consumers. This makes integrating a new component with an existing or legacy system relatively easy: existing systems publish events and new components are configured to listen for those events. Because all interactions in an event-driven architecture are asynchronous, components can process messages on their own time. If load increases substantially, a component may take longer to process a message, but it will eventually happen.

While an application may slow down, it should never go down.

Spring Integration's support for event-driven architecture rests on three core components:

  • Messages are objects sent from one component to another.
  • Channels are the means by which messages are sent, they can be synchronous or asynchronous.
  • Adapters route the output from one channel to the input of another one.

Figure 1 illustrates the relationship between messages, channels, and adapters in Spring Integration.

Figure 1. Messages, channels, and adapters

jw osjp spring integration fig1

Note that when Component 1 sends a message to the specified channel, the adapter routes it to Component 2. Essentially, the adapter says that any message sent to that channel should be directed to Component 2.

Hello, Spring Integration!

No Java technology introduction would be complete without a "Hello World" example. In this case, we'll use Spring Integration to put together a small program that routes a text message from one component to another. This exercise will make the workings of Spring Integration's messages, channels, and adapters clearer. (See the latest Javadoc for Spring Integration for more detailed information about each component.)

First, Listing 1 shows the contents of an applicationContext.xml file, which is the glue holding together our three application components.

Listing 1. applicationContext.xml


<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns="http://www.springframework.org/schema/integration"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                                 http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">


    <!-- Component scan to find all Spring components -->
    <context:component-scan base-package="com.geekcap.springintegrationexample" />

    <!-- A Spring Integration channel -->
    <channel id="helloWorldChannel" />

    <!-- A Spring Integration adapter that routes messages sent to the helloWorldChannel to the bean named "helloServiceImpl"'s hello() method -->
    <service-activator input-channel="helloWorldChannel" ref="helloServiceImpl" method="hello" />

</beans:beans>

Note that the <beans> node defines the schemas and namespaces employed by the XML file. The default context (xmlns) is defined to be http://www.springframework.org/schema/integration, which means that we do not need to prefix the channel or service-activator nodes. (Later we'll switch back to using the beans default context, at which point we'll need to give our Spring Integration nodes their own prefix. I just wanted to make the XML easier to read in the beginning.)

Listing 1 defines three components:

  1. Using component-scan we can annotate our beans in code with annotations like @Service or @Component. When we annotate our beans we'll need to run a component scan so that Spring can find them. The component-scan node takes a base package to scan and will scan the classpath for that package and all subpackages. In our case we're going to define and annotate two beans: HelloService and GreeterService.
  2. The HelloService bean prints "Hello, name" to the standard output. The GreeterService bean sends a name to HelloService. (You'll see these interactions in the code snips below.)
  3. helloWorldChannel is a channel to which our code can send messages.
  4. service-activator is an adapter that says that all messages sent to helloWorldChannel should be forwarded to the helloServiceImpl's hello() method. The default name that Spring chooses for your beans is the name of the class, but note that it starts with a lowercase letter.

Listing 2 shows the contents of the HelloService interface. An interface is not required -- i.e., we could send messages directly to a bean without involving an interface -- but it's customary when using Spring to define interfaces, giving us the flexibility to change an implementation later. (Using an interface in this case will also make unit testing easier.)

Listing 2. HelloService.java


package com.geekcap.springintegrationexample.service;

public interface HelloService
{
    public void hello( String name );
}

The HelloService interface defines a single method: hello(), which accepts a String parameter. Spring is smart enough to look at the method and its parameter signature to perform a conversion of the message to a String value.

Listing 3 shows the HelloServiceImpl class that implements the HelloService interface.

Listing 3. HelloServiceImpl.java


package com.geekcap.springintegrationexample.service;

import org.springframework.stereotype.Service;

@Service
public class HelloServiceImpl implements HelloService
{
    @Override
    public void hello(String name)
    {
        System.out.println( "Hello, " + name );
    }
}

The HelloServiceImpl implements the hello() method by printing "Hello, name" to the standard output. It is annotated with the @Service annotation, so the component-scan defined in the applicationContext.xml file will find it. Notice that the service looks very standard and there's no indication that it will be involved in a Spring Integration action.

Listing 4 shows the contents of GreeterService, which is the interface that our greeters need to implement.

Listing 4. GreeterService.java


package com.geekcap.springintegrationexample.service;

public interface GreeterService
{
    public void greet( String name );
}

Listing 5 shows the implementation of the GreeterService interface.

Listing 5. GreeterServiceImpl.java


package com.geekcap.springintegrationexample.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
public class GreeterServiceImpl implements GreeterService
{
    @Autowired
    private MessageChannel helloWorldChannel;

    @Override
    public void greet(String name)
    {
        helloWorldChannel.send( MessageBuilder.withPayload( name ).build() );
    }
}

More about the code

The GreeterServiceImpl class is annotated with the @Service annotation, so that Spring will identify it as a service. It has auto-wired into it a MessageChannel named helloWorldChannel. Because the name of the channel matches the one defined in the applicationContext.xml file, Spring will just find it for you. If you wanted to override that name, you could add a @Qualifier annotation to the MessageChannel to give it the name of the channel bean with which you want to communicate. When the GreeterServiceImpl's greet() method is invoked, it creates and sends a message to the helloWorldChannel.

The MessageChannel is an interface that defines two variants of the send() method: one that accepts a timeout and one that does not (which can, depending on the implementation, block indefinitely). The MessageBuilder class, an implementation of the Builder design pattern, helps you build Messages. In this case, we passed MessageBuilder a single String, but it could be used to specify message headers, expiration dates, priority, correlation IDs, reply and error channels, and more. Once we're finished configuring the MessageBuilder, invoking the build() method returns a Message that can be sent to any channel.

Listing 6 shows the source code for a command-line application that pulls all of our code together.

Listing 6. App.java


package com.geekcap.springintegrationexample.main;

import com.geekcap.springintegrationexample.service.GreeterService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Main entry-point into the test application
 */
public class App
{
    public static void main( String[] args )
    {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" );

        GreeterService greeterService = applicationContext.getBean( "greeterServiceImpl", GreeterService.class );

        greeterService.greet( "Spring Integration!" );
    }
}

The App class loads the applicationContext.xml file from the classpath, which is located in the src/main/resources path so that it will be automatically embedded in the JAR file by Maven. Next, it retrieves the greeterServiceImpl bean from the application context. And finally, it invokes the GreeterService's greet() method.

A Spring Integration

Figure 2 shows the original Spring Integration diagram from Figure 1, retrofitted for the specifics of this example.

Figure 2. Hello, Spring Integration!

jw osjp spring integration fig2

Here's a summary of the integrated application's flow:

  1. The App class invokes the GreeterService's greet() method, passing it the String "Spring Integration!"
  2. The GreeterService has wired into it a MessageChannel named helloWorldChannel. It uses a MessageBuilder to build a Message that contains the String "Spring Integration!", which it sends to the MessageChannel.
  3. The service-activator has been configured such that any message sent to the helloWorldChannel will be routed to the HelloService's hello() method.
  4. The HelloServiceImpl class's hello() method is invoked and "Hello, Spring Integration!" is printed out to the screen.

Listing 7 shows a Maven pom.xml file that builds this sample application:

1 2 3 4 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more