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

本指南将引导您完成创建一个功能性的响应式应用程序的过程,该应用程序使用 Spring Data 通过非阻塞的 Lettuce 驱动与 Redis 进行交互。

您将构建什么

您将构建一个使用 Spring Data RedisProject Reactor 的 Spring 应用程序,以响应式方式与 Redis 数据存储交互,存储和检索 Coffee 对象而不会阻塞。该应用程序使用基于 Reactive Streams 规范的 Reactor 的 Publisher 实现,即 Mono(用于返回 0 或 1 个值的 Publisher)和 Flux(用于返回 0 到 n 个值的 Publisher)。

所需内容

  • 大约 15 分钟

  • 一个喜欢的文本编辑器或 IDE

  • Java 17 或更高版本

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者通过查看此仓库中的代码直接跳到解决方案。

要在本地环境中查看最终结果,您可以执行以下操作之一:

配置 Redis 服务器

在构建消息传递应用程序之前,您需要设置服务器来处理接收和发送消息。本指南假定您使用 Spring Boot Docker Compose 支持。此方法的前提是您的开发机器上安装了 Docker 环境,例如 Docker Desktop。添加一个 spring-boot-docker-compose 依赖项,它将执行以下操作:

  • 在工作目录中搜索 compose.yml 和其他常见的 compose 文件名
  • 使用发现的 compose.yml 调用 docker compose up
  • 为每个支持的容器创建服务连接 bean
  • 当应用程序关闭时调用 docker compose stop

要使用 Docker Compose 支持,您只需遵循本指南。根据您引入的依赖项,Spring Boot 会找到正确的 compose.yml 文件,并在您运行应用程序时启动您的 Docker 容器。

如果您选择自己运行 Redis 服务器而不是使用 Spring Boot 的 Docker Compose 支持,您有几个选择:- 下载服务器 并手动运行它 - 如果您使用的是 Mac,可以通过 Homebrew 安装 - 使用 docker compose up 手动运行 compose.yaml 文件。

如果您选择这些替代方法中的任何一种,您应该从 Maven 或 Gradle 构建文件中移除 spring-boot-docker-compose 依赖项。您还需要向 application.properties 文件中添加配置,如 准备构建应用程序 部分中更详细描述的那样。如前所述,本指南假设您在 Spring Boot 中使用 Docker Compose 支持,因此此时不需要对 application.properties 进行额外的更改。

使用 Spring Initializr 开始

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

要手动初始化项目:

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

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

  3. 点击 Dependencies,然后选择 Spring Reactive WebSpring Data Reactive RedisDocker Compose Support

  4. 点击 Generate

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

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

创建一个领域类

创建一个记录,表示我们希望添加到咖啡目录中的一种咖啡类型:

src/main/java/com/example/demo/Coffee.java

package com.example.demo;

public record Coffee(String id, String name) {
}

创建一个配置类

创建一个包含支持响应式 Redis 操作的 Spring Beans 的类:

src/main/java/com/example/demo/CoffeeConfiguration.java

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class CoffeeConfiguration {
  @Bean
  ReactiveRedisOperations<String, Coffee> redisOperations(ReactiveRedisConnectionFactory factory) {
    Jackson2JsonRedisSerializer<Coffee> serializer = new Jackson2JsonRedisSerializer<>(Coffee.class);

    RedisSerializationContext.RedisSerializationContextBuilder<String, Coffee> builder =
        RedisSerializationContext.newSerializationContext(new StringRedisSerializer());

    RedisSerializationContext<String, Coffee> context = builder.value(serializer).build();

    return new ReactiveRedisTemplate<>(factory, context);
  }

}

创建一个 Spring Bean 来加载数据

创建一个 Spring Bean,在启动应用程序时加载示例数据:

由于我们可能会多次(重新)启动应用程序,因此我们首先应该清除之前执行可能遗留的任何数据。我们通过执行 flushAll()(Redis)服务器命令来实现这一点。一旦我们清除了所有现有数据,我们创建一个小的 Flux,将每个咖啡名称映射到一个 Coffee 对象,并将其保存到响应式 Redis 仓库中。然后我们查询仓库中的所有值并将它们显示出来。

src/main/java/com/example/demo/CoffeeLoader.java

package com.example.demo;

import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;

import jakarta.annotation.PostConstruct;
import java.util.UUID;

@Component
public class CoffeeLoader {
  private final ReactiveRedisConnectionFactory factory;
  private final ReactiveRedisOperations<String, Coffee> coffeeOps;

  public CoffeeLoader(ReactiveRedisConnectionFactory factory, ReactiveRedisOperations<String, Coffee> coffeeOps) {
    this.factory = factory;
    this.coffeeOps = coffeeOps;
  }

  @PostConstruct
  public void loadData() {
    factory.getReactiveConnection().serverCommands().flushAll().thenMany(
        Flux.just("Jet Black Redis", "Darth Redis", "Black Alert Redis")
            .map(name -> new Coffee(UUID.randomUUID().toString(), name))
            .flatMap(coffee -> coffeeOps.opsForValue().set(coffee.id(), coffee)))
        .thenMany(coffeeOps.keys("*")
            .flatMap(coffeeOps.opsForValue()::get))
        .subscribe(System.out::println);
  }
}

创建一个RestController

创建一个 RestController 为我们的应用程序提供一个外部接口:

src/main/java/com/example/demo/CoffeeController.java

package com.example.demo;

import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class CoffeeController {
  private final ReactiveRedisOperations<String, Coffee> coffeeOps;

  CoffeeController(ReactiveRedisOperations<String, Coffee> coffeeOps) {
    this.coffeeOps = coffeeOps;
  }

  @GetMapping("/coffees")
  public Flux<Coffee> all() {
    return coffeeOps.keys("*")
        .flatMap(coffeeOps.opsForValue()::get);
  }
}

运行应用程序

您可以通过您的 IDE 运行主方法。需要注意的是,如果您是从解决方案仓库中克隆的项目,您的 IDE 可能会在错误的位置查找 compose.yaml 文件。您可以配置您的 IDE 以在正确的位置查找,或者您可以使用命令行来运行应用程序。./gradlew bootRun./mvnw spring-boot:run 命令将启动应用程序并自动找到 compose.yaml 文件。

测试应用程序

在应用程序运行时,从新的终端运行以下命令:

curl http://localhost:8080/coffees

您应该会看到以下输出:

[
  {
    "id": "04ce0843-c9f8-40f6-942f-1ff643c1d426",
    "name": "Jet Black Redis"
  },
  {
    "id": "e2a0d798-5fa4-48a2-a45c-7770d8bb82bf",
    "name": "Black Alert Redis"
  },
  {
    "id": "13f13e3a-0798-44b7-8ae4-b319b227bb19",
    "name": "Darth Redis"
  }
]

准备构建应用程序

要在没有 Spring Boot Docker Compose 支持的情况下运行代码,您需要在本地运行一个 Redis 版本以供连接。为此,您可以使用 Docker Compose,但首先需要对 compose.yaml 文件进行两处修改。首先,将 compose.yaml 中的 ports 条目修改为 '6379:6379'。其次,添加一个 container_name

修改后的 compose.yaml 文件应如下所示:

services:
  redis:
    container_name: 'guide-redis'
    image: 'redis:latest'
    ports:
      * '6379:6379'

现在,您可以运行 docker compose up 来启动 Redis 服务器。此时,您应该已经拥有一个外部 Redis 服务器,可以接收请求了。您可以重新运行应用程序,并使用外部 Redis 服务器查看相同的输出。

application.properties 文件中不需要进行任何配置,因为默认值与 compose.yaml 中的 Redis 服务器配置相匹配。具体来说,属性 spring.data.redis.hostspring.data.redis.port 的默认值分别为 localhost6379

构建应用程序

本节描述了运行本指南的不同方式:

  1. 构建并执行 JAR 文件

  2. 使用 Cloud Native Buildpacks 构建并执行 Docker 容器

  3. 构建并执行原生镜像

  4. 使用 Cloud Native Buildpacks 构建并执行原生镜像容器

无论您选择如何运行应用程序,输出结果都应该是一样的。

要运行应用程序,您可以将应用程序打包为一个可执行的jar文件。./mvnw clean package命令会将应用程序编译成一个可执行的jar文件。然后,您可以使用java -jar target/demo-0.0.1-SNAPSHOT.jar命令来运行这个jar文件。

另外,如果您有可用的Docker环境,您可以直接使用Maven或Gradle插件通过buildpacks创建一个Docker镜像。借助Cloud Native Buildpacks,您可以创建可以在任何地方运行的Docker兼容镜像。Spring Boot直接支持Maven和Gradle的buildpack功能。这意味着您只需输入一个命令,就能快速将合理的镜像推送到本地运行的Docker守护进程中。要使用Cloud Native Buildpacks创建Docker镜像,可以运行./mvnw spring-boot:build-image命令。在启用了Docker环境的情况下,您可以使用docker run --network container:guide-redis docker.io/library/demo:0.0.1-SNAPSHOT命令来运行应用程序。

--network 标志告诉 Docker 将我们的指南容器附加到外部容器正在使用的现有网络中。您可以在 Docker 文档 中找到更多信息。

原生镜像支持

Spring Boot 还支持 编译为原生镜像,前提是您的机器上安装了 GraalVM 发行版。

然后,您可以运行 ./mvnw -Pnative native:compile 命令来生成原生镜像。构建完成后,通过执行 target/demo 命令,您将能够以近乎即时的启动时间运行代码。

要使用 Maven 创建 原生镜像容器,您应确保您的 pom.xml 文件使用了 spring-boot-starter-parentorg.graalvm.buildtools:native-maven-plugin。该插件应位于 <build> <plugins> 部分中:

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
</plugin>

您还可以使用 Buildpacks 创建原生镜像。您可以通过运行 ./mvnw -Pnative spring-boot:build-image 命令来生成原生镜像。构建完成后,您可以使用 docker run --network container:guide-redis docker.io/library/demo:0.0.1-SNAPSHOT 命令启动您的应用程序。

在 Docker 中测试应用程序

如果您使用 Docker 指令(如前所示)运行应用程序,那么在终端或命令行中执行简单的 curl 命令将不再有效。这是因为我们在一个Docker 网络中运行我们的容器,而该网络无法从终端或命令行访问。要运行 curl 命令,我们可以启动第三个容器来运行 curl 命令,并将其连接到同一网络。

首先,获取一个交互式 shell,以启动一个与 Redis 容器和应用程序在同一网络中运行的新容器:

docker run --rm --network container:guide-redis -it alpine

接下来,在容器内的 shell 中安装 curl:

apk add curl

最后,您可以按照测试应用程序中所述运行 curl 命令。

总结

恭喜您!您已经开发了一个使用 Spring Data 和 Redis 实现完全响应式、非阻塞数据库访问的 Spring 应用程序!

本页目录