Spring Integration Http Gateway Example
1. Introduction
In this post, we feature a comprehensive Example on Spring Integration Http Gateway. Messaging Gateway is an Enterprise Integration pattern that encapsulates the messaging code enabling the application to have only business logic code. In Spring Integration, the Messaging Gateway pattern is implemented in the gateway component, which is used to provide an entry / exit system between the application and an external system.
If you use just message channels and adapters, your application will need to have plumbing code of the messaging system, which results in tight coupling. Gateways eliminate tight coupling. Essentially, gateways are facades that abstract away the functionality of the messaging system into a discrete interface, thus your interface to the messaging system is just a method or a service call.
The message flow can happen in a bi-directional fashion. Output gateways send messages out of the application and take in the response where as inbound gateways receive messages into the application from an external system, process it and emit a response.
2. Application
In this article, we will discuss a Spring Integration application which sends a GET
request to a REST
web service and processes the response received, both operations being done through an output gateway that has a send channel and a receive channel. The REST
service and HTTP
application are built and packaged separately.
3. Environment
- Java 1.8
- Spring Boot 2.0.1
- Maven 3.5.4
- Windows 10
4. Source Code
Let’s look at the files and code. First we start with the accounts application which is a REST
web service. It is a Maven
based project, so all the required libraries are specified in 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.webservices</groupId> <artifactId>accounts</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>accounts</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.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-web</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>
The base domain class of the application is AccountsTransaction
.
AccountsTransaction.java
package org.javacodegeeks.webservices.accounts.domain; public class AccountsTransaction { String description = ""; Double amount = 0.0; public String getDescription() { return description; } public Double getAmount() { return amount; } }
This is a simple class with two properties, a description
of type String
and an amount of type Double
, which are initialized to a null string and 0.0 respectively. Each property has its own getter defined.
In the real world, there are many types of accounting transactions. In our application, we use only one, Income
.
Income.java
package org.javacodegeeks.webservices.accounts.domain; public class Income extends AccountsTransaction { public Income(String description, Double amount) { this.description = description; this.amount = amount; } }
This class is a sub-class of AccountsTransaction
and has a constructor using the two base class properties.
The endpoint class handling the GET
requests is TransactionsEndpoint
.
TransactionsEndpoint.java
package org.javacodegeeks.webservices.accounts.controllers; import java.util.ArrayList; import java.util.List; import org.javacodegeeks.webservices.accounts.domain.Income; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TransactionsEndpoint { static List incomeList = new ArrayList(); static { incomeList.add(new Income("First Income", 1000.0)); } @GetMapping("/incomes") public List getAllIncomes() { return incomeList; } }
The @RestController
annotation itself has two annotations, @Controller
and @ResponseBody
. These make the class to be identified as a web controller and auto-detected through scanning and make the method return values bound to the response body.
The class declares incomeList
to be a List
of Income
objects and refers it to an ArrayList
object. Next, inside a static
block, it constructs an Income
object with “First Income” as the description
and an amount
of value 1000.0. This new object is added to the ArrayList
. As a quick refresher, static
blocks in Java are executed only once, when the JVM loads the class into memory; they are also called initialization blocks and are executed before the constructors.
The annotation @GetMapping
is used to map incoming GET
requests with “/incomes” in the URL to the ListAllIncomes
method. As per the Spring framework documentation, “@GetMapping
is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.GET)
.” The method getAllIncomes
just returns incomeList
.
The main class of the accounts service is AccountsApplication
.
AccountsApplication.java
package org.javacodegeeks.webservices.accounts; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AccountsApplication { public static void main(String[] args) { SpringApplication.run(AccountsApplication.class, args); } }
The @SpringBootApplication
annotation is a convenience annotation that combines three other annotations, @EnableConfiguration
, @ComponentScan
, and @Configuration
. In other words, the class is marked for auto-configuration, component scan and having the ability to register additional beans and import extra configuration classes. The main method invokes SpringApplication.run
to start the application which waits for incoming web service requests.
Now, we will go through the second application which uses a Spring Integration gateway to interoperate with the accounts application. This is also a Maven
based project and hence all the required libraries are specified in pom.xml.
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.gateway</groupId> <artifactId>http-get</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>http-get</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.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.integration</groupId> <artifactId>spring-integration-core</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-http</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</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>
The gateway along with its request and reply channels are configured in the file http-outbound-gateway.xml.
http-outbound-gateway.xml
<?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:int="http://www.springframework.org/schema/integration" xmlns:int-http="http://www.springframework.org/schema/integration/http" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd"> <int:channel id="get_send_channel" /> <int:channel id="get_receive_channel"> <int:queue capacity='10' /> </int:channel> <int-http:outbound-gateway id="get.outbound.gateway" request-channel="get_send_channel" url="http://localhost:8080/incomes" http-method="GET" reply-channel="get_receive_channel" expected-response-type="java.lang.String"> </int-http:outbound-gateway> </beans>
In this file, two DirectChannel
s are defined, with ids get_send_channel
and get_receive_channel
. You would recall that a DirectChannel
has point-to-point semantics with its unique feature being that one message is to only one subscriber in a round-robin fashion.
Next, we define a HTTP
outbound gateway with the id get.outbound.gateway
and configure the get_send_channel
to be its request channel and get_receive_channel
to be its reply channel. We also specify the URL to communicate to as http://localhost:8080/incomes
and specify that a String
is expected as the response type.
The main class of this application is HttpApplication
.
HttpApplication.java
package org.javacodegeeks.springintegration.gateway.http; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ImportResource; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.PollableChannel; @SpringBootApplication @ImportResource("http-outbound-gateway.xml") public class HttpApplication { @Autowired @Qualifier("get_send_channel") MessageChannel getSendChannel; @Autowired @Qualifier("get_receive_channel") PollableChannel getReceiveChannel; public static void main(String[] args) { SpringApplication.run(HttpApplication.class, args); } @Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> { Message message = MessageBuilder.withPayload("").build(); getSendChannel.send(message); System.out.println(getReceiveChannel.receive().getPayload()); }; } }
As explained above, the @SpringBootApplication
annotation gives a class the combined features of @EnableAutoConfiguration
, @ComponentScan
, and @Configuration
.
With @ImportResource
we tell the class to import beans from the file “http-outbound-gateway.xml
“. The annotation @Autowired
is used on a variable for Spring bean autowiring, thus allowing it to be used for dependency injection by the Spring container. The annotation @Qualifier
is used to clearly identify which exact bean has to be auto-wired. In our case, we use it to specify that the getSendChannel
bean configured with the configuration identified by “get_send_channel
” and the getReceiveChannel
bean configured with the identifier “get_receive_channel
.”
The commandLineRunner
method is annotated with @Bean
indicating that it returns a bean to the spring container, which, in this case is a CommandLineRunner
object. Within the method, a message is built with an empty payload by invoking the build method of MessageBuilder
class. The message is sent to getSendChannel
after which the payload of the message received on the getReceiveChannel
is printed to the console.
5. How To Run
Starts the accounts application first. In a terminal window, go to the accounts directory and enter
mvn spring-boot:run
In another terminal window, go to the http-get folder and enter
mvn spring-boot:run
You will see that the application has called the accounts service and printed the following response.
[{"description":"First Income","amount":1000.0}]
See the screen shot given below.
6. Summary
In this article, we have seen a REST web service invoked from a Spring Boot application using a Spring Integration gateway.
7. Download the Source Code
That was an example of the Spring Integration Http Gateway.
You can download the full source code of this example here: http-outbound-gateway.zip