About Xavier Padro

Xavier is a software developer working in a consulting firm based in Barcelona. He is specialized in web application development with experience in both frontend and backend. He is interested in everything related to Java and the Spring framework

Spring Integration 4.0: A complete XML-free example

1. Introduction

Spring Integration 4.0 is finally here, and this release comes with very nice features. The one covered in this article is the possibility to configure an integration flow without using XML at all. Those people that don’t like XML will be able to develop an integration application with just using JavaConfig.

This article is divided in the following sections:
 
 
 

  1. Introduction.
  2. An overview of the flow.
  3. Spring configuration.
  4. Detail of the endpoints.
  5. Testing the entire flow.
  6. Conclusion.
  • The source code can be found at github.
  • The source code of the web service invoked in this example can be found at the spring-samples repository at github.

2. An overview of the flow

The example application shows how to configure several messaging and integration endpoints. The user asks for a course by specifying the course Id. The flow will invoke a web service and return the response to the user. Additionally, some type of courses will be stored to a database.

The flow is as follows:

  • An integration gateway (course service) serves as the entry to the messaging system.
  • A transformer builds the request message from the user specified course Id.
  • A web service outbound gateway sends the request to a web service and waits for a response.
  • A service activator is subscribed to the response channel in order to return the course name to the user.
  • A filter is also subscribed to the response channel. This filter will send some types of courses to a mongodb channel adapter in order to store the response to a database.

The following diagram better shows how the flow is structured:
 
int-v4-full-flow_v2

 

3. Spring configuration

As discussed in the introduction section, the entire configuration is defined with JavaConfig. This configuration is split into three files: infrastructure, web service and database configuration. Let’s check it out:

3.1   Infrastructure configuration

This configuration file only contains the definition of message channels. The messaging endpoints (transformer, filter, etc…) are configured with annotations.

InfrastructureConfiguration.java

@Configuration
@ComponentScan("xpadro.spring.integration.endpoint")	//@Component
@IntegrationComponentScan("xpadro.spring.integration.gateway")	//@MessagingGateway
@EnableIntegration
@Import({MongoDBConfiguration.class, WebServiceConfiguration.class})
public class InfrastructureConfiguration {
    
    @Bean
    @Description("Entry to the messaging system through the gateway.")
    public MessageChannel requestChannel() {
        return new DirectChannel();
    }
    
    @Bean
    @Description("Sends request messages to the web service outbound gateway")
    public MessageChannel invocationChannel() {
        return new DirectChannel();
    }
    
    @Bean
    @Description("Sends web service responses to both the client and a database")
    public MessageChannel responseChannel() {
        return new PublishSubscribeChannel();
    }
    
    @Bean
    @Description("Stores non filtered messages to the database")
    public MessageChannel storeChannel() {
        return new DirectChannel();
    }
}

The @ComponentScan annotation searches for @Component annotated classes, which are our defined messaging endpoints; the filter, the transformer and the service activator.

The @IntegrationComponentScan annotation searches for specific integration annotations. In our example, it will scan the entry gateway which is annotated with @MessagingGateway.

The @EnableIntegration annotation enables integration configuration. For example, method level annotations like @Transformer or @Filter.

3.2   Web service configuration

This configuration file configures the web service outbound gateway and its required marshaller.

WebServiceConfiguration.java

@Configuration
public class WebServiceConfiguration {
    
    @Bean
    @ServiceActivator(inputChannel = "invocationChannel")
    public MessageHandler wsOutboundGateway() {
        MarshallingWebServiceOutboundGateway gw = new MarshallingWebServiceOutboundGateway("http://localhost:8080/spring-ws-courses/courses", jaxb2Marshaller());
        gw.setOutputChannelName("responseChannel");
        
        return gw;
    }
    
    @Bean
    public Jaxb2Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("xpadro.spring.integration.ws.types");
        
        return marshaller;
    }
}

The gateway allows us to define its output channel but not the input channel. We need to annotate the adapter with @ServiceActivator in order to subscribe it to the invocation channel and avoid having to autowire it in the message channel bean definition.

3.3   Database configuration

This configuration file defines all necessary beans to set up mongoDB. It also defines the mongoDB outbound channel adapter.

MongoDBConfiguration.java

@Configuration
public class MongoDBConfiguration {
    
    @Bean
    public MongoDbFactory mongoDbFactory() throws Exception {
        return new SimpleMongoDbFactory(new MongoClient(), "si4Db");
    }
    
    @Bean
    @ServiceActivator(inputChannel = "storeChannel")
    public MessageHandler mongodbAdapter() throws Exception {
        MongoDbStoringMessageHandler adapter = new MongoDbStoringMessageHandler(mongoDbFactory());
        adapter.setCollectionNameExpression(new LiteralExpression("courses"));
        
        return adapter;
    }
}

Like the web service gateway, we can’t set the input channel to the adapter. I also have done that by specifying the input channel in the @ServiceActivator annotation.

4. Detail of the endpoints

The first endpoint of the flow is the integration gateway, which will put the argument (courseId) into the payload of a message and send it to the request channel.

@MessagingGateway(name = "entryGateway", defaultRequestChannel = "requestChannel")
public interface CourseService {
    
    public String findCourse(String courseId);
}

The message containing the course id will reach the transformer. This endpoint will build the request object that the web service is expecting:

@Component
public class CourseRequestBuilder {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Transformer(inputChannel="requestChannel", outputChannel="invocationChannel")
    public GetCourseRequest buildRequest(Message<String> msg) {
        logger.info("Building request for course [{}]", msg.getPayload());
        GetCourseRequest request = new GetCourseRequest();
        request.setCourseId(msg.getPayload());
        
        return request;
    }
}

Subscribed to the response channel, which is the channel where the web service reply will be sent, there’s a service activator that will receive the response message and deliver the course name to the client:

@Component
public class CourseResponseHandler {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @ServiceActivator(inputChannel="responseChannel")
    public String getResponse(Message<GetCourseResponse> msg) {
        GetCourseResponse course = msg.getPayload();
        logger.info("Course with ID [{}] received: {}", course.getCourseId(), course.getName());
        
        return course.getName();
    }
}

Also subscribed to the response channel, a filter will decide based on its type, if the course is required to be stored to a database:

@Component
public class StoredCoursesFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Filter(inputChannel="responseChannel", outputChannel="storeChannel")
    public boolean filterCourse(Message<GetCourseResponse> msg) {
        if (!msg.getPayload().getCourseId().startsWith("BC-")) {
            logger.info("Course [{}] filtered. Not a BF course", msg.getPayload().getCourseId());
            return false;
        }
        
        logger.info("Course [{}] validated. Storing to database", msg.getPayload().getCourseId());
        return true;
    }
}

5. Testing the entire flow

The following client will send two requests; a BC type course request that will be stored to the database and a DF type course that will be finally filtered:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={InfrastructureConfiguration.class})
public class TestApp {
    @Autowired
    CourseService service;
    
    @Test
    public void testFlow() {
        String courseName = service.findCourse("BC-45");
        assertNotNull(courseName);
        assertEquals("Introduction to Java", courseName);
        
        courseName = service.findCourse("DF-21");
        assertNotNull(courseName);
        assertEquals("Functional Programming Principles in Scala", courseName);
	}
}

This will result in the following console output:

CourseRequestBuilder|Building request for course [BC-45]

CourseResponseHandler|Course with ID [BC-45] received: Introduction to Java

StoredCoursesFilter|Course [BC-45] validated. Storing to database

CourseRequestBuilder|Building request for course [DF-21]

CourseResponseHandler|Course with ID [DF-21] received: Functional Programming Principles in Scala

StoredCoursesFilter|Course [DF-21] filtered. Not a BF course

6. Conclusion

We have learnt how to set up and test an application powered with Spring Integration using no XML configuration. Stay tuned, because Spring Integration Java DSL with Spring Integration extensions is on its way!

 

Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

4 Responses to "Spring Integration 4.0: A complete XML-free example"

  1. Stephen McConnell says:

    I know it is cool to use annotations, but I’m not sure that this is the best way to go with Spring Integration.

    When I have a context.xml, I have one central location to see how pieces fit together. With annotations, I have to go to all the different classes and see what the annotations are to determine how things “integrate”. Having worked with an in-house (pre-spring/spring integration) Enterprise Integration Pattern framework for the past 6 years that has become the backbone of our company, it is much easier to figure out how things are “integrated” if you have a main context.xml with nested sub-contexts for modularization.

    Just because you can do something in a framework doesn’t necessarily mean it’s best practice. But I am open to discussion.

  2. Hi Stephen,

    I don’t think using annotations over XML must be considered the best way to do this. In fact, it is just a matter of personal preferences. I agree with you that with XML you have the configuration more centralized and, especially with Spring Integration, it is much easier to take a glance at the entire flow. Actually, I still prefer a mix of XML (for integration flow) with annotations (for endpoints) when working with SI. However, when SI Extensions bring Java DSL, we should be able to centralize the configuration the same way as with XML. Of course, it will still be each one’s personal choice when deciding which type of configuration to use.

  3. The equivalent in Camel Java DSL would be:

    public class CamelRouteBuilder extends RouteBuilder {

    public void configure() throws Exception {
    JaxbDataFormat jaxbMarshaller = new JaxbDataFormat();
    jaxbMarshaller.setContextPath(“xpadro.spring.integration.ws.types”);

    from(“direct:requestChannel”)

    .process(new Processor() {
    @Override
    public void process(Exchange exchange) throws Exception {
    GetCourseRequest request = new GetCourseRequest();
    request.setCourseId(exchange.getIn().getBody(String.class));
    exchange.getIn().setBody(request);
    }
    })

    .unmarshal(jaxbMarshaller)

    .to(“spring-ws:http://localhost:8080/spring-ws-courses/courses“)

    .filter(simple(“${body.courseId} regex ‘^BC-’”))

    .to(“mongodb:myDB?database=si4Db&collection=courses&operation=save”);
    }
    }

Leave a Reply


× nine = 45



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books