使用 Testcontainers 对 Keycloak 进行集成测试

1、简介 通常我们会通过集成测试来验证应用功能是否正常。集成测试至关重要,特别是对于认证这种敏感且重要的功能。Testcontainers 允许在测试阶段启动 Docker 容器,以便对实际的技术栈运行测试。 本文将带你了解如何使用 Testcontainers 针对实际的 Keycloak 实例设置集成测试。 2、配置 Spring Security 和 Keycloak 我们需要配置 Spring Security、Keycloak 和 Testcontainers。 2.1、整合 Spring Boot 和 Spring Security 在 pom.xml 中添加 spring-boot-starter-security 依赖。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 接着,创建一个示例 Controller,它返回一个 User。 @RestController @RequestMapping("/users") public class UserController { @GetMapping("me") public UserDto getMe() { return new UserDto(1L, "janedoe", "Doe", "Jane", "jane.doe@baeldung.com"); } } 至此,我们有了一个受保护的 Controller,用于处理对 /users/me 端点的请求。启动应用时,Spring Security 会为用户 user 生成一个密码,该密码在控制台输出的日志中。 2.配置 Keycloak 启动本地 Keycloak 的最简单方法是使用 Docker。

使用 @NotNull 注解进行非空校验

1、概览 空指针异常 NullPointerException 是一个常见问题,避免这种问题的方法之一是在方法参数上添加 @NotNull 等校验注解。 给方法参数添加了 @NotNull 注解后,还需要其他的一些设置才能自动对参数进行非空校验。 2、给方法参数添加 @NotNull 注解 创建一个类,其中包含一个返回 String 长度的方法。 在 String 参数上添加 @NotNull 注解: public class NotNullMethodParameter { public int validateNotNull(@NotNull String data) { return data.length(); } } 注意,有多个包下都有 @NotNull 注解,我们使用的应该是 jakarta.validation.constraints 包。 创建 NotNullMethodParameter 实例,然后使用 null 参数调用方法。 NotNullMethodParameter notNullMethodParameter = new NotNullMethodParameter(); notNullMethodParameter.doesNotValidate(null); 尽管在参数上使用了 @NotNull,但还是出现了空指针异常:NullPointerException。 注解未生效,因为没有 Validator 来执行它。 3、添加 Validator 添加 Hibernate Validator(jakarta.validation 的实现)来识别 @NotNull。 <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>8.0.0.Final</version> </dependency> 使用默认的 ValidatorFactory 创建 validator。

Spring Boot 中的静态资源配置

Spring Boot 对静态资源提供了支持。默认情况下,以下目录为默认的静态资源目录。其中的静态资源可以被直接访问: classpath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/ ${user.dir}/public/ (程序运行目录下的 public 目录) 优先级从上往下,当多个静态资源目录中出现同名文件时,越靠上的目录权重越高。 静态资源的处理类是 ResourceHttpRequestHandler,它会正确地处理资源的 Last-Modified 响应和 Range 请求。 静态资源和 RequestMapping 冲突 如果静态资源路径和 @RequestMapping 路径冲突,则 @RequestMapping 优先。 例如,有如下 Controller: @RestController @RequestMapping public class DemoController { @GetMapping("/foo") public ResponseEntity<String> foo () { // 返回字符串 “controller” return ResponseEntity.ok("controller"); } } 在 src/main/resources/public 目录下有一个名为 foo 的文本文件,内容如下: public 启动应用,访问 http://localhost:8080/foo: $ curl http://localhost:8080/foo controller 你可以看到,响应的内容是 controller,说明 controller 的 @RequestMapping 优先。 另外,对于这种没有后缀、未知类型的静态资源,Spring Boot 会以 “下载” 的形式响应给客户端(添加了 Content-Disposition 响应头)。

springdoc-openapi 定义全局的默认 SecurityScheme

1、概览 本文将带你了解如何在 Spring MVC Web 应用中使用 springdoc-openapi 配置默认的全局 Security Scheme,并将其应用为 API 的默认安全配置,以及如何覆盖这些默认的安全配置。 OpenAPI 规范 允许为 API 定义一套 Security Scheme。可以配置 API 全局的安全配置,也可以按端点应用/删除安全配置。 2、设置 构建一个 Spring Boot Web 项目,使用 Maven。 2.1、依赖 该示例有两个依赖。第一个是 spring-boot-starter-web,用于构建 Web 应用。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.1</version> </dependency> 另一个依赖是 springdoc-openapi-ui,它用于输出 HTML、JSON 或 YAML 格式的 API 文档: <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>1.6.9</version> </dependency> 2.2、启动类 使用 @SpringBootApplication 注解来定义启动类,通过 SpringApplication 类来启动应用: @SpringBootApplication public class DefaultGlobalSecuritySchemeApplication { public static void main(String[] args) { SpringApplication.run(DefaultGlobalSecuritySchemeApplication.class, args); } } 3、springdoc-openapi 基础配置 配置好 Spring MVC 后,来看看 API 语义信息。

WebClient 将 Flux 读取到 InputStream 中

1、概览 本文将会带你了解在 Java 响应式编程中如何将 Flux<DataBuffer> 读取到 InputStream。 2、请求设置 首先,使用 Spring Reactive WebClient 发起 GET 请求。使用由 gorest.co.in 托管的公共 API 端点来进行测试: String REQUEST_ENDPOINT = "https://gorest.co.in/public/v2/users"; 接下来,定义 getWebClient() 方法,用于获取 WebClient 类的新实例: static WebClient getWebClient() { WebClient.Builder webClientBuilder = WebClient.builder(); return webClientBuilder.build(); } 至此,我们就可以向 /public/v2/users 端点发出 GET 请求了。注意,必须以 Flux<DataBuffer> 对象的形式获取响应体。 3、BodyExtractors 和 DataBufferUtils 我们可以使用 spring-webflux 中 BodyExtractors 类的 toDataBuffers() 方法将响应体提取到 Flux<DataBuffer> 中。 将 body 构建为 Flux<DataBuffer> 类型的实例: Flux<DataBuffer> body = client .get( .uri(REQUEST_ENDPOINT) .exchangeToFlux( clientResponse -> { return clientResponse.

构建 GraalVM Docker 镜像

1、简介 GraalVM 使用其 Ahead-Of-Time(AOT)编译器将 Java 应用程序编译为机器可执行文件。这些可执行文件直接在目标机器上执行,而无需使用即时编译器 (JIT)。GraalVM生成的二进制文件体积较小,启动速度快,并且在没有任何预热的情况下提供最佳性能。此外,这些可执行文件相比在 JVM 上运行的应用程序而言,内存占用和 CPU 使用率较低。 通过 Docker,我们可以将软件组件打包成 Docker Image,并作为 Docker 容器运行。Docker 容器包含应用程序运行所需的一切,包括应用程序代码、运行时、系统工具和库。 在本教程中,我们将了解如何创建 Java 应用程序的 GraalVM 原生(native)镜像,以及如何将该原生镜像用作 Docker 镜像,并将其作为 Docker 容器运行。 2、原生镜像是什么? 原生镜像(Native Image)是一种将 Java 代码提前编译成原生可执行文件的技术。该原生可执行文件只包含运行时需要执行的代码。这包括应用程序类、标准库类、语言运行时和 JDK 中静态链接的本地代码。 原生镜像生成器(Native Image Builder)会扫描应用程序类和其他元数据,以创建一个特定于操作系统和体系结构的二进制文件。本地镜像工具会执行静态应用程序代码分析,以确定应用程序运行时可访问的类和方法。然后,它将所需的类、方法和资源编译成二进制可执行文件。 3、原生镜像的优点 原生镜像可执行文件有几个好处: 由于原生镜像生成器只编译运行时所需的资源,因此可执行文件的体积很小 本地可执行文件的启动时间极短,因为它们是在目标机器中直接执行的,无需使用 JIT 编译器 由于只打包所需的应用程序资源,暴漏的攻击面较小 将其打包为轻量级容器镜像(如 Docker Image),有助于快速高效地部署 4、构建 GraalVM 原生镜像 在本节中,我们将为 Spring Boot 应用构建 GraalVM 原生镜像。首先,需要安装 GraalVM 并设置 JAVA_HOME 环境变量。其次,创建一个包含 Spring Web 和 GraalVM Native Support 依赖的 Spring Boot 应用:

在 Spring Boot 中实现定时备份 MySQL 数据库

应用系统中最重要的东西就是 “数据”,定期备份数据的重要性就不言而喻了。本文将会带你了解如何在 Spring Boot 应用中实现定期备份 MySQL 数据库。 mysqldump MYSQL本身提供了一个工具 mysqldump,通过它可以完成数据库的备份。 简单来说就是一个命令,可以把数据库中的表结构和数据,以 SQL 语句的形式输出到标准输出: mysqldump -u[用户名] -p[密码] [数据库] > [备份的SQL文件] 注意,命令中的 > 符号在linux下是重定向符,在这里的意思就是把标准输出重定向到文件。 例如,备份 demo 库到 ~/mysql.sql,用户名和密码都是 root: mysqldump -uroot -proot demo > ~/mysql.sql mysqldump 的详细文档:https://dev.mysql.com/doc/refman/en/mysqldump.html 创建应用 创建任意 Spring Boot 应用,并添加 commons-exec 依赖。 <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-exec</artifactId> <version>1.3</version> </dependency> 由于我们的备份是通过新启动一个子进程调用 mysqldump 来完成,所以建议使用 apache 的 commons-exec 库。它的使用比较简单,且设计合理,包含了子进程超时控制,异步执行等等功能。 应用配置 spring: # 基本的数据源配置 datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true username: root password: root app: # 备份配置 backup: # 备份数据库 db: "demo" # 备份文件存储目录 dir: "backups" # 备份文件最多保留时间。如,5分钟:5m、12小时:12h、1天:1d max-age: 3m 如上,我们配置了基本的数据源。以及自定义的 “备份配置”,其中指定了备份文件的存储目录,要备份的数据库以及备份文件滚动存储的最大保存时间。

在 Spring Boot 中校验 Boolean 类型

1、简介 在本文中,我们将学习如何在 Spring Boot 中的不同层(如 controller 或 service)上验证布尔(Boolean)类型以及执行验证的各种方式。 2、编程式验证 Boolean 类提供了两个创建该类实例的基本方法:Boolean.valueOf() 和 Boolean.parseBoolean()。 Boolean.valueOf() 可接受 String 和 boolean 值。它会检查输入参数的值是 true 还是 false,并相应地提供一个 Boolean 对象。Boolean.parseBoolean() 方法只接受 String 值。 这些方法不区分大小写,例如,true、True、TRUE、false 和 FALSE 都是可以的。 通过单元测试来验证 String 到 Boolean 的转换: @Test void givenInputAsString_whenStringToBoolean_thenValidBooleanConversion() { assertEquals(Boolean.TRUE, Boolean.valueOf("TRUE")); assertEquals(Boolean.FALSE, Boolean.valueOf("false")); assertEquals(Boolean.TRUE, Boolean.parseBoolean("True")); } 验证从基本 boolean 值到 Boolean 封装类的转换: @Test void givenInputAsboolean_whenbooleanToBoolean_thenValidBooleanConversion() { assertEquals(Boolean.TRUE, Boolean.valueOf(true)); assertEquals(Boolean.FALSE, Boolean.valueOf(false)); } 3、使用自定义 Jackson Deserializer 进行验证 Spring Boot API 经常需要处理 JSON 数据,我们还要了解如何通过数据反序列化验证 JSON 到 Boolean 值的转换。我们可以使用自定义 deserializer 反序列化 boolean 值的自定义表示。

Spring Cloud Stream Kafka 实现 Apache Kafka 的 “仅一次” 语义

在之前的教程中,我们对 Spring Cloud Stream Kafka 应用程序中事务的工作原理进行了基本分析。现在,我们终于来到了一个关键问题:“仅一次” (Exactly Once)语义,这是流式应用程序中一个被广泛讨论和需要的特性。在本文中,我们将了解如何通过 Apache Kafka 事务在 Spring Cloud Stream 应用中实现 “仅一次” 语义。通过之前章节对事务工作原理的了解,相对容易理解 Spring Cloud Stream Kafka 应用如何实现 “仅一次” 语义。 这里需要注意的一点是,在实现 “仅一次” 语义时,除了我们在之前的系列教程文章中已经见过的代码,我们无需编写任何新的代码。本文介绍了在 Spring Cloud Stream Kafka 应用程序中支持 “仅一次” 语义所需的特定要求。 在分布式计算中实现 “仅一次” 语义是一项困难的任务。这超出了本系列教程的范围,在这里无法回顾所有技术细节,以了解为什么这是一项如此困难的任务。对于有兴趣了解 “仅一次” 语义的所有基础知识以及为什么在分布式系统中实现它如此困难的读者,可以参考相关的广泛文献。Confluent 的这篇 博客 是理解这些技术挑战以及 Apache Kafka 实现的解决方案的良好起点。 虽然我们不会深入探讨细节,但看看 Apache Kafka 提供的不同交付保证还是值得的。主要有三种交付保证: 至少一次(at-least-once) 最多一次(at-most-once) 仅一次(exactly-once) 在 “至少一次” 的交付语义中,应用程序可以一次或多次接收数据,但保证至少接收一次。在 “最多一次” 语义的交付保证中,应用程序可能接收零次或一次数据,这意味着有可能丢失数据。“仅一次” 语义,正如其名称所示,保证只交付一次。根据应用程序的使用情况,使用其中任何一种保证都是可以的。默认情况下,Apache Kafka 提供至少一次交付保证,这意味着一条记录可能会被交付多次。如果你的应用程序可以处理重复记录或无记录的后果,那么使用非一次保证可能也没问题。相反,如果你处理的是关键任务数据,如金融系统或医疗数据,你就必须保证 “仅一次” 准确交付和处理,以避免严重后果。由于 Apache Kafka 等系统的分布式特性,通常很难实现精确的 “仅一次” 语义,这是因为系统中存在许多部件。 Spring Cloud Stream Kafka 和 Exactly-Once 语义 在本教程系列的前几篇文章中,我们看到了许多不同的应用场景。Apache Kafka 中的 “仅一次” 义针对的是读-处理-写(或消费-转换-生产)应用。有时,我们会对 “一次” 到底在做什么感到困惑。是最初的消费、数据处理,还是最后的生产?Apache Kafka 保证整个 “读取->处理-写入”(read->process-write)序列的 “仅一次”(actly-once)语义。在这个序列中,读取和处理部分总是至少一次 - 例如,如果部分处理或写入因任何原因失败。如果依赖 “仅一次” 交付,事务就非常关键,这样才能成功完成或回滚数据的最终发布。一个潜在的副作用是,初始消费和处理可能会发生多次。例如,如果事务回滚,消费者偏移量不会更新,下一次轮询(如果是在 Spring Cloud Stream 中重试或应用程序重启)将重新交付相同的记录并再次进行处理。因此,在消费和处理(转换)部分中至少保证一次,这是需要理解的关键点。任何以 read_committed 隔离级别运行的下游消费者都只能从上游处理器获得一次信息。因此,我们必须明白,在 “仅一次” 交付的世界中,处理器和下游消费者必须协调才能从精确一次语义中获益。任何以 read_uncommitted 隔离级别运行的已生成主题的消费者都可能看到重复的数据。

Spring Cloud Stream 和 Apache Kafka 的事务回滚策略

在本系列教程的前几章中,我们分析了事务在 Spring Cloud Stream Kafka 应用中的工作原理。了解了事务发挥作用的不同环境,包括生产者和消费者应用,以及应用如何正确使用事务。现在,这些基本要素已经介绍完毕,让我们继续了解事务的另一个方面:在发生错误时回滚事务。当错误发生时,事务处理系统无法提交事务,事务管理器就会回滚事务,不会为下游消费者保留任何内容。如果应用能够决定回滚机制的工作方式,将大有裨益。Spring Cloud Stream 通过 Spring 对 Apache Kafla 的基本支持,为回滚定制提供了便利。在处理生产者和消费者(消费-处理-生产)事务性应用时,我们必须注意一些事情。接下来,让我们一起深入了解这些内容。 生产者发起的事务 下面是我们在 上一篇文章 中看到的代码片段。 @Transactional public void send(StreamBridge streamBridge) { for (int i = 0; i < 5; i++) { streamBridge.send("mySupplier-out-0", "my data: " + i); } } 如果事务方法抛出异常,我们应该怎么办?从 Spring Cloud Stream 的角度来看,我们不需要做任何操作。事务拦截器会启动回滚,最终由 Kafka 中的事务协调器中断事务。最后,异常会传播给调用者,然后调用者可以决定在错误是暂时的情况下重新触发事务方法。由于这是一个生产者发起的事务,框架不会进行重试。这种情况很简单,因为在事务回滚期间,从应用或框架的角度来看,我们不需要做任何操作。如果发生错误,它将被保证回滚。然而,需要注意的是,即使事务被回滚,Kafka 日志中可能存在未提交的记录。使用隔离级别为 read_uncommitted(默认级别)的消费者仍然会接收这些记录。因此,消费者应用程序必须确保使用 read_committed 隔离级别,这样才不会收到上游事务回滚的任何记录。 生产者发起的事务与外部事务同步 在本系列教程的上一章,我们看到了这种情况。与第一种情况一样,如果方法抛出异常并发生回滚,即使 Kafka 事务与数据库事务同步,应用也无需做任何事情来处理错误。数据库和 Kafka 发布的事务都会回滚。 消费者发起的事务回滚 如果生产者发起的事务回滚是如此简单,你可能会想知道为什么我们要专门为这个主题撰写一篇完整的文章。什么时候应用程序需要提供特定的回滚策略呢?当你有一个正在进行的消费者发起的事务时,这就有了意义,因为我们需要特别关注如何处理消费记录的状态和它们的偏移量。让我们重新看一下在系列中 之前的教程 中运行的消费者发起的事务方法代码。 public Consumer<PersonEvent> process(TxCode txCode) { return txCode::run; } @Component class TxCode { @Transactional void run(PersonEvent pe) { Person person = new Person(); person.