1. 介绍
在本教程的前一部分中,我们使用XML配置实现了公开Restful API的应用程序。这部分将使用Spring Integration Java DSL重新实现此应用程序。
该应用程序是用Java 8实现的,但是当使用Java 8特定代码时(例如,在使用lambdas时),我还将向您展示如何在Java 7中执行此操作。无论如何,我都会在Github上共享这两个版本,以防您想要检查出来:
Java 7 Java DSL示例
Java 8 Java DSL示例
这篇文章分为以下几节
- 介绍
- 应用配置
- 获取操作
- 投放和发布操作
- 删除操作
- 结论
2. 应用程序配置
在web.xml文件中,调度程序servlet被配置为使用Java Config:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value>xpadro.spring.integration.server.configuration</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/spring/*</url-pattern> </servlet-mapping> |
在pom.xml文件中,我们包含Spring Integration Java DSL依赖项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
<properties> <spring-version>4.1.3.RELEASE</spring-version> <spring-integration-version>4.1.0.RELEASE</spring-integration-version> <slf4j-version>1.7.5</slf4j-version> <junit-version>4.9</junit-version> <jackson-version>2.3.0</jackson-version> </properties> <dependencies> <!-- Spring Framework - Core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version> </dependency>
<!-- Spring Framework - Integration --> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-core</artifactId> <version>${spring-integration-version}</version> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-http</artifactId> <version>${spring-integration-version}</version> </dependency>
<!-- Spring Integration - Java DSL --> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-java-dsl</artifactId> <version>1.0.0.RELEASE</version> </dependency>
<!-- JSON --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson-version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson-version}</version> </dependency>
<!-- Testing --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit-version}</version> <scope>test</scope> </dependency>
<!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j-version}</version> </dependency> </dependencies> |
InfrastructureConfiguration.java
配置类包含bean和流定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Configuration @ComponentScan("xpadro.spring.integration.server") @EnableIntegration public class InfrastructureConfiguration {
public ExpressionParser parser() { return new SpelExpressionParser(); }
public HeaderMapper<HttpHeaders> headerMapper() { return new DefaultHttpHeaderMapper(); }
//flow and endpoint definitions } |
为了分析有效载荷表达式,我们使用SpELExpressionParser来定义一个bean解析器。
标题映射器稍后将被注册为入站网关的属性,以便将HTTP标头映射到/到消息标头。
此配置类中定义的流程和端点的详细信息在以下各节中进行了说明。
3. 获取操作
我们的第一步是定义将处理GET请求的HTTP入站网关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Bean public MessagingGatewaySupport httpGetGate() { HttpRequestHandlingMessagingGateway handler = new HttpRequestHandlingMessagingGateway(); handler.setRequestMapping(createMapping(new HttpMethod[]{HttpMethod.GET}, "/persons/{personId}")); handler.setPayloadExpression(parser().parseExpression("#pathVariables.personId")); handler.setHeaderMapper(headerMapper());
return handler; } private RequestMapping createMapping(HttpMethod[] method, String... path) { RequestMapping requestMapping = new RequestMapping(); requestMapping.setMethods(method); requestMapping.setConsumes("application/json"); requestMapping.setProduces("application/json"); requestMapping.setPathPatterns(path);
return requestMapping; } |
createMapping方法是本教程前面部分中请求映射XML元素的Java替代方法。在这种情况下,我们也可以使用它来定义请求路径和支持的方法。
现在我们有了我们的网关集,让我们定义将为GET请求提供服务的流(请记住,您可以查看本教程前面部分中的完整流程图):
1 2 3 4 |
@Bean public IntegrationFlow httpGetFlow() { return IntegrationFlows.from(httpGetGate()).channel("httpGetChannel").handle("personEndpoint", "get").get(); } |
流程如下工作:
- from(httpGetGate()):获取HTTP入站网关收到的消息。
- 通道(“httpGetChannel”):注册一个新的DirectChannel bean并将收到的消息发送给它。
- handle(“personEndpoint”,“get”):发送到前一个通道的消息将被我们的personEndpoint bean使用,调用它的get方法。
由于我们正在使用网关,personEndpoint的响应将被发送回客户端。
为了方便起见,我展示了personEndpoint,因为它实际上与XML应用程序中的相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@Component public class PersonEndpoint { private static final String STATUSCODE_HEADER = "http_statusCode";
@Autowired private PersonService service;
public Message<?> get(Message<String> msg) { long id = Long.valueOf(msg.getPayload()); ServerPerson person = service.getPerson(id);
if (person == null) { return MessageBuilder.fromMessage(msg) .copyHeadersIfAbsent(msg.getHeaders()) .setHeader(STATUSCODE_HEADER, HttpStatus.NOT_FOUND) .build(); }
return MessageBuilder.withPayload(person) .copyHeadersIfAbsent(msg.getHeaders()) .setHeader(STATUSCODE_HEADER, HttpStatus.OK) .build(); }
//other operations } |
GetOperationsTest使用RestTemplate来测试公开的HTTP GET集成流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@RunWith(BlockJUnit4ClassRunner.class) public class GetOperationsTest { private static final String URL = "http://localhost:8081/int-http-dsl/spring/persons/{personId}"; private final RestTemplate restTemplate = new RestTemplate();
private HttpHeaders buildHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); headers.setContentType(MediaType.APPLICATION_JSON);
return headers; }
@Test public void getResource_responseIsConvertedToPerson() { HttpEntity<Integer> entity = new HttpEntity<>(buildHeaders()); ResponseEntity<ClientPerson> response = restTemplate.exchange(URL, HttpMethod.GET, entity, ClientPerson.class, 1); assertEquals("John" , response.getBody().getName()); assertEquals(HttpStatus.OK, response.getStatusCode()); }
//more tests } |
我不会显示完整的类,因为它与XML示例中的相同。
4. 放置和后置操作
继续我们的Restful API应用程序示例,我们为HTTP入站通道适配器定义一个bean。您可能会注意到我们正在创建一个新的网关。原因是入站通道适配器在内部实现为不期待回复的网关。
1 2 3 4 5 6 7 8 9 10 |
@Bean public MessagingGatewaySupport httpPostPutGate() { HttpRequestHandlingMessagingGateway handler = new HttpRequestHandlingMessagingGateway(); handler.setRequestMapping(createMapping(new HttpMethod[]{HttpMethod.PUT, HttpMethod.POST}, "/persons", "/persons/{personId}")); handler.setStatusCodeExpression(parser().parseExpression("T(org.springframework.http.HttpStatus).NO_CONTENT")); handler.setRequestPayloadType(ServerPerson.class); handler.setHeaderMapper(headerMapper());
return handler; } |
我们再次使用解析器来解析返回的状态码表达式。
入站适配器的前XML属性request-payload-type现在被设置为网关的属性。
处理PUT和POST操作的流程使用路由器将消息发送到适当的端点,具体取决于接收到的HTTP方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Bean public IntegrationFlow httpPostPutFlow() { return IntegrationFlows.from(httpPostPutGate()).channel("routeRequest").route("headers.http_requestMethod", m -> m.prefix("http").suffix("Channel") .channelMapping("PUT", "Put") .channelMapping("POST", "Post") ).get(); } @Bean public IntegrationFlow httpPostFlow() { return IntegrationFlows.from("httpPostChannel").handle("personEndpoint", "post").get(); } @Bean public IntegrationFlow httpPutFlow() { return IntegrationFlows.from("httpPutChannel").handle("personEndpoint", "put").get(); } |
流程按以下方式执行:
- from(httpPostPutGate()):获取HTTP入站适配器收到的消息。
- channel(“routeRequest”):注册一个DirectChannel bean并将接收到的消息发送给它。
- 路由(...):发送到前一个通道的消息将由路由器处理,路由器将根据收到的HTTP方法(http_requestMethod标头)重定向它们。使用前缀和后缀解析目标频道。例如,如果HTTP方法是PUT,则解析的通道将是httpPutChannel,它也是此配置类中定义的一个bean。
子流(httpPutFlow和httpPostFlow)将接收来自路由器的消息并在我们的personEndpoint中处理它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Component public class PersonEndpoint { @Autowired private PersonService service;
//Get operation
public void put(Message<ServerPerson> msg) { service.updatePerson(msg.getPayload()); }
public void post(Message<ServerPerson> msg) { service.insertPerson(msg.getPayload()); } } |
由于我们定义了一个入站适配器,因此不会期望来自端点的响应。
在路由器定义中,我们使用了Java 8 lambda表达式。我告诉过你,我会在Java 7中展示替代方案,所以承诺是一个承诺:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Bean public IntegrationFlow httpPostPutFlow() { return IntegrationFlows.from(httpPostPutGate()).channel("routeRequest").route("headers.http_requestMethod", new Consumer<RouterSpec<ExpressionEvaluatingRouter>>() { @Override public void accept(RouterSpec<ExpressionEvaluatingRouter> spec) { spec.prefix("http").suffix("Channel") .channelMapping("PUT", "Put") .channelMapping("POST", "Post"); } } ).get(); } |
再长一点,不是吗?
PUT流程由PutOperationsTest类进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@RunWith(BlockJUnit4ClassRunner.class) public class PutOperationsTest { private static final String URL = "http://localhost:8081/int-http-dsl/spring/persons/{personId}"; private final RestTemplate restTemplate = new RestTemplate();
//build headers method
@Test public void updateResource_noContentStatusCodeReturned() { HttpEntity<Integer> getEntity = new HttpEntity<>(buildHeaders()); ResponseEntity<ClientPerson> response = restTemplate.exchange(URL, HttpMethod.GET, getEntity, ClientPerson.class, 4); ClientPerson person = response.getBody(); person.setName("Sandra"); HttpEntity<ClientPerson> putEntity = new HttpEntity<ClientPerson>(person, buildHeaders());
response = restTemplate.exchange(URL, HttpMethod.PUT, putEntity, ClientPerson.class, 4); assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());
response = restTemplate.exchange(URL, HttpMethod.GET, getEntity, ClientPerson.class, 4); person = response.getBody(); assertEquals("Sandra", person.getName()); } } |
POST流由PostOperationsTest类进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@RunWith(BlockJUnit4ClassRunner.class) public class PostOperationsTest { private static final String POST_URL = "http://localhost:8081/int-http-dsl/spring/persons"; private static final String GET_URL = "http://localhost:8081/int-http-dsl/spring/persons/{personId}"; private final RestTemplate restTemplate = new RestTemplate();
//build headers method
@Test public void addResource_noContentStatusCodeReturned() { ClientPerson person = new ClientPerson(9, "Jana"); HttpEntity<ClientPerson> entity = new HttpEntity<ClientPerson>(person, buildHeaders());
ResponseEntity<ClientPerson> response = restTemplate.exchange(POST_URL, HttpMethod.POST, entity, ClientPerson.class); assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());
HttpEntity<Integer> getEntity = new HttpEntity<>(buildHeaders()); response = restTemplate.exchange(GET_URL, HttpMethod.GET, getEntity, ClientPerson.class, 9); person = response.getBody(); assertEquals("Jana", person.getName()); } } |
5. 删除操作
通过这个操作我们完成了我们的应用 入口点由以下bean定义:
1 2 3 4 5 6 7 8 9 10 |
@Bean public MessagingGatewaySupport httpDeleteGate() { HttpRequestHandlingMessagingGateway handler = new HttpRequestHandlingMessagingGateway(); handler.setRequestMapping(createMapping(new HttpMethod[]{HttpMethod.DELETE}, "/persons/{personId}")); handler.setStatusCodeExpression(parser().parseExpression("T(org.springframework.http.HttpStatus).NO_CONTENT")); handler.setPayloadExpression(parser().parseExpression("#pathVariables.personId")); handler.setHeaderMapper(headerMapper());
return handler; } |
该配置与PutPost网关非常相似。我不会再解释它。
删除流程将删除请求发送给personEndpoint:
1 2 3 4 |
@Bean public IntegrationFlow httpDeleteFlow() { return IntegrationFlows.from(httpDeleteGate()).channel("httpDeleteChannel").handle("personEndpoint", "delete").get(); } |
我们的bean将请求服务删除资源:
1 2 3 4 |
public void delete(Message<String> msg) { long id = Long.valueOf(msg.getPayload()); service.deletePerson(id); } |
测试断言删除后该资源不再存在:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@RunWith(BlockJUnit4ClassRunner.class) public class DeleteOperationsTest { private static final String URL = "http://localhost:8081/int-http-dsl/spring/persons/{personId}"; private final RestTemplate restTemplate = new RestTemplate();
//build headers method
@Test public void deleteResource_noContentStatusCodeReturned() { HttpEntity<Integer> entity = new HttpEntity<>(buildHeaders()); ResponseEntity<ClientPerson> response = restTemplate.exchange(URL, HttpMethod.DELETE, entity, ClientPerson.class, 3); assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());
try { response = restTemplate.exchange(URL, HttpMethod.GET, entity, ClientPerson.class, 3); Assert.fail("404 error expected"); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); } } } |
6. 结论
本教程的第二部分向我们展示了如何使用新的Spring Integration Java DSL实现不使用XML配置的Spring Integration应用程序。虽然使用Java 8 lambda表达式的流程配置更具可读性,但我们仍然可以选择在先前版本的语言中使用Java DSL。