Spring Boot 与 Docker
观察 GraphQL 的实际运行

本指南将引导您完成使用 Spring WebFlux(自 Spring Boot 2.0 起新增)创建“Hello, Spring!”的 RESTful Web 服务,并使用 WebClient(同样是 Spring Boot 2.0 新增功能)来消费该服务的过程。

本指南展示了使用 Spring WebFlux 的函数式方法。您也可以使用 WebFlux 的注解

你将构建什么

您将使用 Spring Webflux 构建一个 RESTful Web 服务,并创建一个该服务的 WebClient 消费者。您将能够在 System.out 和以下位置看到输出:

http://localhost:8080/hello

您需要准备的内容

如何完成本指南

与大多数 Spring 入门指南 一样,您可以从头开始并完成每个步骤,也可以跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会获得可运行的代码。

从头开始,请继续阅读 从 Spring Initializr 开始

跳过基础部分,请执行以下操作:

** 完成后,您可以将您的结果与 gs-reactive-rest-service/complete 中的代码进行对比。 **

从 Spring Initializr 开始

您可以使用这个预初始化项目,点击生成以下载一个ZIP文件。该项目已配置为适合本教程中的示例。

要手动初始化项目:

  1. 访问 https://start.spring.io。该服务会为您拉取应用程序所需的所有依赖项,并完成大部分设置工作。

  2. 选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。

  3. 点击 Dependencies 并选择 Spring Reactive Web

  4. 点击 Generate

  5. 下载生成的 ZIP 文件,这是一个根据您的选择配置好的 Web 应用程序的归档文件。

如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。

您也可以从 Github 上 fork 该项目,并在您的 IDE 或其他编辑器中打开它。

创建一个 WebFlux Handler

我们将从一个 Greeting POJO 开始,它将被我们的 RESTful 服务序列化为 JSON:

src/main/java/hello/Greeting.java

package com.example.reactivewebservice;


public class Greeting {

  private String message;

  public Greeting() {
  }

  public Greeting(String message) {
    this.message = message;
  }

  public String getMessage() {
    return this.message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  @Override
  public String toString() {
    return "Greeting{" +
        "message='" + message + '\'' +
        '}';
  }
}

在 Spring 响应式编程方法中,我们使用一个处理器来处理请求并创建响应,如下例所示:

src/main/java/hello/GreetingHandler.java

package com.example.reactivewebservice;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import reactor.core.publisher.Mono;

@Component
public class GreetingHandler {

  public Mono<ServerResponse> hello(ServerRequest request) {
    return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
      .body(BodyInserters.fromValue(new Greeting("Hello, Spring!")));
  }
}

这个简单的响应式类总是返回一个包含“Hello, Spring!”问候的 JSON 响应体。它可以返回许多其他内容,包括来自数据库的流数据、由计算生成的流数据等等。请注意响应式代码:一个包含 ServerResponse 响应体的 Mono 对象。

创建路由器

在本应用程序中,我们使用一个路由器来处理我们暴露的唯一路由 (/hello),如下例所示:\ src/main/java/hello/GreetingRouter.java

package com.example.reactivewebservice;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;

@Configuration(proxyBeanMethods = false)
public class GreetingRouter {

  @Bean
  public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {

    return RouterFunctions
      .route(GET("/hello").and(accept(MediaType.APPLICATION_JSON)), greetingHandler::hello);
  }
}

路由器监听 /hello 路径上的流量,并返回由我们的反应式处理器类提供的值。

创建一个 WebClient

Spring 的 RestTemplate 类本质上是阻塞的。因此,我们不希望在响应式应用程序中使用它。对于响应式应用程序,Spring 提供了 WebClient 类,它是非阻塞的。我们使用基于 WebClient 的实现来调用我们的 RESTful 服务:

src/main/java/hello/GreetingClient.java

package com.example.reactivewebservice;

import reactor.core.publisher.Mono;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

@Component
public class GreetingClient {

  private final WebClient client;

  // Spring Boot auto-configures a `WebClient.Builder` instance with nice defaults and customizations.
  // We can use it to create a dedicated `WebClient` for our component.
  public GreetingClient(WebClient.Builder builder) {
    this.client = builder.baseUrl("http://localhost:8080").build();
  }

  public Mono<String> getMessage() {
    return this.client.get().uri("/hello").accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .bodyToMono(Greeting.class)
        .map(Greeting::getMessage);
  }

}

WebClient 类使用响应式特性,以 Mono 的形式来保存消息的内容(由 getMessage 方法返回)。这是使用函数式 API 而不是命令式 API 来链式调用响应式操作符。

可能需要一些时间来适应响应式 API,但 WebClient 具有一些有趣的功能,并且也可以在传统的 Spring MVC 应用程序中使用。

您也可以使用 WebClient 与非响应式、阻塞的服务进行通信。

使应用程序可执行

我们将使用 main() 方法来驱动我们的应用程序,并从我们的端点获取 Greeting 消息。

src/main/java/hello/Application.java

package com.example.reactivewebservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class ReactiveWebServiceApplication {

  public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(ReactiveWebServiceApplication.class, args);
    GreetingClient greetingClient = context.getBean(GreetingClient.class);
    // We need to block for the content here or the JVM might exit before the message is logged
    System.out.println(">> message = " + greetingClient.getMessage().block());
  }
}

@SpringBootApplication 是一个方便的注解,它包含了以下所有内容:

  • @Configuration: 将该类标记为应用上下文的 bean 定义来源。

  • @EnableAutoConfiguration: 告诉 Spring Boot 根据类路径设置、其他 bean 及各种属性设置开始添加 bean。例如,如果类路径上有 spring-webmvc,此注解会将应用程序标记为 Web 应用程序,并激活关键行为,例如设置 DispatcherServlet

  • @ComponentScan: 告诉 Spring 在 hello 包中查找其他组件、配置和服务,从而找到控制器。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用程序。您是否注意到没有一行 XML 代码?也没有 web.xml 文件。这个 Web 应用程序是 100% 纯 Java 的,您无需处理任何配置或基础设施的问题。

构建可执行的 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖、类和资源的单个可执行 JAR 文件并运行它。构建可执行 JAR 文件使得在整个开发生命周期中、跨不同环境等场景下,将服务作为应用程序进行交付、版本管理和部署变得更加容易。

如果您使用 Gradle,可以通过 ./gradlew bootRun 来运行应用程序。或者,您可以使用 ./gradlew build 构建 JAR 文件,然后按如下方式运行该 JAR 文件:

java -jar build/libs/gs-reactive-rest-service-0.1.0.jar

如果您使用的是 Maven,可以通过 ./mvnw spring-boot:run 来运行应用程序。或者,您也可以使用 ./mvnw clean package 构建 JAR 文件,然后按如下方式运行该 JAR 文件:

java -jar target/gs-reactive-rest-service-0.1.0.jar

这里描述的步骤会创建一个可运行的 JAR 文件。您也可以构建一个经典的 WAR 文件

日志输出已显示。服务应该会在几秒钟内启动并运行。

一旦服务启动,您可以看到一行内容:

>> message = Hello, Spring!

这行内容来自 WebClient 消费的响应式数据。当然,您可以为输出找到比将其放入 System.out 更有趣的用途。

测试应用程序

现在应用程序正在运行,您可以对其进行测试。首先,您可以打开浏览器并访问 http://localhost:8080/hello,看到“Hello, Spring!” 在本指南中,我们还创建了一个测试类,帮助您开始使用 WebTestClient 类进行测试。

src/test/java/hello/GreetingRouterTest.java

package com.example.reactivewebservice;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
//  We create a `@SpringBootTest`, starting an actual server on a `RANDOM_PORT`
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GreetingRouterTest {

  // Spring Boot will create a `WebTestClient` for you,
  // already configure and ready to issue requests against "localhost:RANDOM_PORT"
  @Autowired
  private WebTestClient webTestClient;

  @Test
  public void testHello() {
    webTestClient
      // Create a GET request to test an endpoint
      .get().uri("/hello")
      .accept(MediaType.APPLICATION_JSON)
      .exchange()
      // and use the dedicated DSL to test assertions against the response
      .expectStatus().isOk()
      .expectBody(Greeting.class).value(greeting -> {
        assertThat(greeting.getMessage()).isEqualTo("Hello, Spring!");
    });
  }
}

总结

恭喜!您已经开发了一个包含 WebClient 用于消费 RESTful 服务的响应式 Spring 应用程序!

本页目录