使用入站适配器公开HTTP Restful API。第2部分(Java DSL)

1. 介绍

在本教程的前一部分中,我们使用XML配置实现了公开Restful API的应用程序。这部分将使用Spring Integration Java DSL重新实现此应用程序。

该应用程序是用Java 8实现的,但是当使用Java 8特定代码时(例如,在使用lambdas时),我还将向您展示如何在Java 7中执行此操作。无论如何,我都会在Github上共享这两个版本,以防您想要检查出来:

Java 7 Java DSL示例

Java 8 Java DSL示例

这篇文章分为以下几节

  1. 介绍
  2. 应用配置
  3. 获取操作
  4. 投放和发布操作
  5. 删除操作
  6. 结论

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 {

    

    @Bean

    public ExpressionParser parser() {

        return new SpelExpressionParser();

    }

    

    @Bean

    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。

猜你喜欢

转载自my.oschina.net/u/1032870/blog/1633184
DSL