Open source Java projects: Spring Integration

Develop a robust message-passing architecture with Spring Integration

1 2 3 4 Page 2
Page 2 of 4

Listing 7. Maven POM for Hello, Spring Integration


<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>com.geekcap.javaworld</groupId>
    <artifactId>HelloSpringIntegration</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>HelloSpringIntegration</name>
    <url>http://maven.apache.org</url>

    <properties>
        <spring.version>3.2.1.RELEASE</spring.version>
        <spring.integration.version>2.2.5.RELEASE</spring.integration.version>
        <java.version>1.6</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Spring Integration -->
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-core</artifactId>
            <version>${spring.integration.version}</version>
        </dependency>

        <!-- Testing -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.geekcap.springintegrationexample.main.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>install</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>

        <finalName>hello-spring-integration</finalName>
    </build>
</project>

The POM file imports Spring's core, context, and bean dependencies as well as the specific Spring Integration dependency. It defines three plug-ins:

ul>

  • maven-compiler-plugin tells Spring to build at Java version 1.6.
  • maven-jar-plugin tells Spring to include all files in the lib directory in the resultant JAR file's classpath. It also instructs Spring to make the com.geekcap.springintegrationexample.main.App the main-class for this JAR file (meaning that if you execute the file as java -jar then this class will be executed.
  • maven-dependency-plugin tells Maven to copy all of the project's dependencies to the target/lib directory.

You can build this project by executing the command:

mvn clean install

The target directory will then be configured properly to execute the source code as follows:

java -jar hello-spring-integration.jar

Spring's logger outputs a few lines, followed by this program output:

Hello, Spring Integration!

Note that we can configure Spring's logger to be less verbose later by including a log4j.properties file that changes the logging level on Spring components.

Spring Integration with a gateway proxy

Sending messages is good but sometimes you need a response. If you want to execute a method on a service and receive a response, the solution is to employ a gateway proxy. For now, just follow along with these updates, I'll explain them shortly.

First, create a channel and a service-activator. We did this previously (see Listing 1), but this time you'll add a gateway node:

Listing 8. New channel and service-activator


    <!-- A Spring Integration channel for use by our gateway -->
    <channel id="helloWorldWithReplyChannel" />

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

    <!-- Define a gateway that we can use to capture a return value -->
    <gateway id="helloWorldGateway" service-interface="com.geekcap.springintegrationexample.service.HelloService" default-request-channel="helloWorldWithReplyChannel" />	

Notice that the gateway is defined with an interface that it implements (in this case HelloService) and it defines a default request/input channel to use. Also notice that the service-activator calls a new method, getHelloMessage(), instead of hello(). This method just returns "Hello, NAME".

After you define the gateway, you can autowire it into your GreeterService as we did in Listing 5. This time, instead of autowiring the channel, you'll autowire the gateway and note its type as being HelloService, as shown in Listing 9.

Listing 9. Updated 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;

    @Autowired
    private HelloService helloWorldGateway;

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

    @Override
    public void greet2(String name)
    {
        System.out.println( helloWorldGateway.getHelloMessage( name ) );
    }
}	

The updated GreeterServiceImpl class autowires helloWorldGateway, which is resolved by its name in the applicationContext.xml file, and its type is HelloService. The new greet2() method invokes it as though it is HelloService. From the method's point of view, it is just invoking a HelloService as it would a service bean directly: it has no need to know that Spring Integration is involved in the transaction.

Listing 10 shows the updated App class that invokes the new greet2() method.

Listing 10. Updated 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!" );

        greeterService.greet2( "Spring Integration (with response)!");
    }
}	

The output from executing this code is shown below, with the Spring logging omitted:


Hello, Spring Integration!
Hello, Spring Integration (with response)!	

Refactoring recap

So what's just happened? Here's a summary of the refactored application's behavior:

  1. The App class invokes the GreeterService's greet2() method
  2. The greet2() method invokes the getHelloMessage() method on what it thinks is a HelloService (although it's actually a gateway).
  3. The gateway implements the methods defined in the HelloService class to route the requests through the helloWorldWithReplyChannel channel.
  4. The service-activator is configured so that any messages sent to the helloWorldWithReplyChannel are routed to the helloServiceImpl's getHelloMessage() method.
  5. The helloServiceImpl constructs its response and returns it.
  6. The service-activator looks for one of two things to handle the response: an output-channel defined in the service-activator itself, or a reply channel defined in the Message's header. The gateway automatically creates a temporary anonymous, point-to-point reply channel that it listens on and adds that channel to the replyHeader of the message.
  7. The gateway receives the response, as a Message, via the reply channel and converts it to the appropriate return value defined by the service.
  8. Finally, the gateway returns the response back to the caller (the GreeterServiceImpl in this case).

Spring Integration with JMS and ActiveMQ

Thus far we have abstracted a service call by passing a message. We have also hidden a service call behind a gateway and a channel. Conceptually, these two activities should give you enough information to understand how Spring Integration works. Next we'll take that knowledge and extend it into a more realistic enterprise scenario, by creating channels that communicate with a JMS (Java Message Service) topic running in ActiveMQ. An open-source messaging broker that supports the JMS APIs, ActiveMQ is written in Java and is available at no cost under an Apache license.

JMS defines two types of message passing: topics and queues. Topics operate in a publish-subscribe fashion while queues operate in a point-to-point fashion. The publish-subscribe paradigm means that when a message producer publishes a message, zero or more consumers will receive those messages. The point-to-point paradigm means that when a message producer publishes a message there will be exactly one consumer. Queues are great for asynchronously communicating between two components, but topics really promote the power of event-driven architecture.

The point of an EDA application is to decouple a message producer from its consumers, which is what publish-subscribe does. Using topics, a component notifies the world of changes or updates, but only consumers who have subscribed to that topic will receive its messages. The producer does not know who its consumers are and it does not care a perfect example of loose coupling!

In order to demonstrate publish-subscribe messaging in an enterprise system, we'll build two components:

  • PublisherService: A component that publishes a message to a topic.
  • MessageListener: A component that subscribes to that topic and receives its message.

In order toPOST messages to a RESTful service (as a way of publishing a message to a topic) we'll also need to build infrastructure around the PublisherService. Figure 3 shows the interaction of the various components, which we'll look at in detail below.

Figure 3. JMS Spring Integration example

jw osjp spring integration fig3

Here's the flow of what's going on in Figure 3

  • A REST client publishes a message to the MessageController, which is a Spring MVC Controller
  • The MessageController invokes the PublishService's send() method to send the message to the topicChannel
  • A JMS outbound channel adapter is configured to route messages sent to the topicChannel to the topic.myTopic destination
  • The JMS configuration is defined in a ConnectionFactory
  • A JMS Message-Driven Channel Adapter is configured to listen to the topic.myTopic topic and send those messages to the listenerChannel
  • A service-activator is configured to route messages sent to the listenerChannel to the messageListenerImpl's processMessage() method
  • The messageListenerImpl class receives the message and processes it (prints it out to the screen)

The configuration for this application is shown in Listing 11.

Listing 11. springintegrationexample-servlet.xml (Application Context)


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:int="http://www.springframework.org/schema/integration"
       xmlns:int-jms="http://www.springframework.org/schema/integration/jms"
       xmlns:oxm="http://www.springframework.org/schema/oxm"
       xmlns:int-jme="http://www.springframework.org/schema/integration"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
                http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
                http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd
                http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">


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

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="order" value="1" />
        <property name="messageConverters">
            <list>
                <!-- Default converters -->
                <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
                <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
            </list>
        </property>
    </bean>

    <!-- Define a channel to communicate out to a JMS Destination -->
    <int:channel id="topicChannel"/>

    <!-- Define the ActiveMQ connection factory -->
    <bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616"/>
    </bean>

    <!--
        Define an adaptor that route topicChannel messages to the myTopic topic; the outbound-channel-adapter
        automagically finds the configured connectionFactory bean (by naming convention)
      -->
    <int-jms:outbound-channel-adapter channel="topicChannel"
                                      destination-name="topic.myTopic"
                                      pub-sub-domain="true" />

    <!-- Create a channel for a listener that will consume messages-->
    <int:channel id="listenerChannel" />

    <int-jms:message-driven-channel-adapter id="messageDrivenAdapter"
                                            channel="listenerChannel"
                                            destination-name="topic.myTopic"
                                            pub-sub-domain="true" />

    <int:service-activator input-channel="listenerChannel" ref="messageListenerImpl" method="processMessage" />

</beans>	

The beginning of Listing 11 sets up the AnnotationMethodHandlerAdapter for Spring MVC, which is beyond the scope for this article. What you really need to know is that the message converters render items returned by the Spring Controllers. The important sections for this example are the following:

1 2 3 4 Page 2
Page 2 of 4