教程

Spring Boot 使用 Redis 实现可靠的消息队列

在应用中把 Redis 当成消息队列来使用已经屡见不鲜了。我想主要原因是当代应用十有八九都会用到 Redis,因此不用再引入其他消息队列系统。而且 Redis 提供了好几种实现消息队列的方法,用起来也简单。 使用 Redis 实现消息队列的几种方式 Redis 提供了多种方式来实现消息队列。 Pub/Sub 订阅发布模式,发布者把消息发布到某个 Channel,该 Channel 的所有订阅者都会收到消息。但是这种方式最大的问题是 发布出去的消息,如果没有被监听消费,或者消费过程中宕机,那么消息就会永远丢失。适合用于临时通知之类的场景,对于需要保证数据不丢失的场景不能使用这种方式。 List List 是 Redis 提供的一种数据类型,底层是链表,可以用来实现队列、栈。 Stream Stream 是一个由 Redis 5 引入的,功能完善的消息队列。想必也是 Redis 官方团队看到太多人拿 Redis 当消息队列使,于是干脆就在 Redis 上设计出一个类似于 Kafka 的消息队列。 Steam 支持消费组消费,一条消息只能被消费组中的其中一个消费者消费。支持 消息确认、支持 回溯消费 还支持把未 ACK(确认)的消息转移给其他消费者进行重新消费,在进行转移的时候还会累计消息的转移次数,当次数达到一定阈值还没消费成功,就可以放入死信队列。 这也是 Redis 种最复杂的一种数据类型。如果你真的到了需要使用 Redis Steam 作为消息队列的地步,那不如直接使用 RabbitMQ 等更加成熟且稳定的消息队列系统。 使用 List 实现可靠的消息队列 目前来说,这是用得最多的一种方式,适用于大多数简单的消息队列应用场景。List 类型有很多指令,但是作为消息队列来说用到的只有几个个: LPUSH key element [element ...] 把元素插入到 List 的首部,如果 List 不存在,会自动创建。 BRPOPLPUSH source destination timeout 移除并且返回 List (source)尾部的最后一个元素,并且同时会把这个元素插入到另一个 List (destination)的首部。

Spring Cloud Gateway 全局异常处理

1、概览 本文将带你了解如何在 Spring Cloud Gateway 中实现全局异常处理。 在现代软件开发中,尤其是在微服务中,API 的高效管理至关重要。这正是 Spring Cloud Gateway 作为 Spring 生态系统的关键组件发挥重要作用的地方。它就像一个门卫,将流量和请求引导到适当的微服务,并提供跨切面的关注点,如安全性、监控/指标和弹性。 然而,在如此复杂的环境中,由于网络故障、服务宕机或应用错误而产生的异常是必然的,这就需要一个强大的异常处理机制。Spring Cloud Gateway 的全局异常处理可确保在所有服务中采用一致的错误处理方法,并增强整个系统的弹性和可靠性。 2、全局异常处理的必要性 Spring Cloud Gateway 是 Spring 生态系统中的一个项目,旨在作为微服务架构中的 API 网关,其主要作用是根据预先制定的规则将请求路由到相应的微服务。网关提供安全(认证和授权)、监控和熔断(Circuit Breakers)等功能。通过处理请求并将其导向适当的后端服务,它有效地管理了诸如安全性和流量管理等横切关注点。 在微服务等分布式系统中,异常可能来自多个方面,如网络问题、服务不可用、下游服务错误和应用级错误,这些都是常见的罪魁祸首。在这种环境下,以本地化方式(即在每个服务内)处理异常可能会导致零散和不一致的错误处理。这种不一致性会使调试工作变得繁琐,并降低用户体验: 全局异常处理通过提供集中的异常管理机制,确保所有异常(无论其来源如何)都能得到一致的处理,并提供标准化的错误响应,从而应对这一挑战。 这种一致性对于系统恢复能力、简化错误跟踪和分析至关重要。它还能提供精确一致的错误格式,帮助用户了解出错的原因,从而提升用户体验。 3、在 Spring Cloud Gateway 中实现全局异常处理 在 Spring Cloud Gateway 中实现全局异常处理涉及几个关键步骤,每个步骤都能确保建立一个强大而高效的异常管理系统。 3.1、创建自定义全局异常处理器 全局异常处理器对于捕获和处理网关中发生的异常至关重要。为此,需要继承 AbstractErrorWebExceptionHandler 并将其添加到 Spring Context 中。这样,就创建了一个能拦截所有异常的集中式处理器。 @Component public class CustomGlobalExceptionHandler extends AbstractErrorWebExceptionHandler { // 构造函数和其他方法 } 该类应能处理各种类型的异常,从 NullPointerException 等一般异常到 HttpClientErrorException 等更具体的异常。目标是涵盖各种可能的错误。该类的核心方法如下所示。 @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.

Spring WebFlux Multipart 文件上传

1、概览 Spring WebFlux 是一个响应式 Web 框架,它提供了一个非阻塞 Event Loop,可异步处理 I/O 操作。此外,它还使用 Mono 和 Flux Reactive Stream Publisher 在订阅时发布数据。 这种响应式方式可帮助应用处理大量请求和数据,而无需分配大量资源。 本文将带你了解如何在 Spring WebFlux 中处理 Multipart 文件上传。 2、项目设置 创建一个简单的响应式 Spring Boot 项目,将 Multipart 文件上传到一个目录。 为简单起见,使用项目的根目录来存储文件。在生产中,可以使用 云厂商 OSS、Minio 存储等文件系统。 2.1、Maven 依赖 首先,在 pom.xml 中添加 spring-boot-starter-webflux 依赖,以启动 Spring WebFlux 应用: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>3.2.0</version> </dependency> 它提供核心 Spring WebFlux API 和嵌入式 Netty 服务器,用于构建响应式 Web 应用。 另外,还要在 pom.xml 文件中添加 spring-boot-starter-data-r2dbc 和 H2 数据库依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>com.

在 Spring Authorization Server 中将 Authorities 作为自定义 Claim 添加到 JWT Access Token 中。

1、概览 在许多情况下,可以在 JWT Access Token 添加自定义 Claim,从而在 Token Payload 中包含更多信息。 本文将带你了解如何在 Spring Authorization Server 中为 JWT Access Token 添加资源所有者授权。 2、Spring Authorization Server Spring Authorization Server 是 Spring 生态系统中的一个新项目,旨在为 Spring 应用提供授权服务器支持。它通过 Spring 编程模型简化 OAuth 2.0 和 OpenID Connect(OIDC) 授权服务器的实现。 2.1、Maven 依赖 首先,在 pom.xml 中导入 spring-boot-starter-web、spring-boot-starter-security、spring-boot-starter-test 和 spring-security-oauth2-authorization-server 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>0.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>2.5.4</version> </dependency> 或者,可以在 pom.xml 文件中添加 spring-boot-starter-oauth2-authorization-server 依赖:

解决 JUnit 5 中的 ParameterResolutionException 异常

1、概览 JUnit 5 引入了一些强大的功能,包括支持参数化测试(parameterized testing)。编写参数化测试可以节省大量时间,而且在许多情况下,只需简单组合注解就能启用参数化测试。 然而,错误配置可能会导致难以调试的异常,例如:ParameterResolutionException。 org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter ... 本文将带你了解如何解决 ParameterResolutionException 异常。 2、JUnit 5 的 ParameterResolver 异常信息中表示缺少 ParameterResolver。 这是 JUnit 5 中引入的 ParameterResolver 接口,允许开发人员扩展 JUnit 的基本功能,编写可接受任何类型参数的测试。 来看一个简单的 ParameterResolver 实现: public class FooParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { // 是否支持参数类型的逻辑 } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { // 参数解析逻辑 } } 该类有两个主要方法: supportsParameter():确定是否支持参数类型 resolveParameter():返回执行测试的参数 因为 ParameterResolutionsException 是在没有 ParameterResolver 实现的情况下抛出的,所以先不关心实现细节。首先来看看异常的一些潜在原因。

Spring AI 简介

1、概览 Spring 通过 Spring AI 项目正式启用了 AI(人工智能)生成提示功能。本文将带你了解如何在 Spring Boot 应用中集成生成式 AI,以及 Spring AI 如何与模型互动。 2、Spring AI 的主要概念 首先回顾一下一些关键的领域术语和概念。 Spring AI 最初专注于处理语言输入和生成语言输出的模型。该项目的理念是为开发人员提供一个抽象接口,为将生成式 AI 作为独立组件纳入应用奠定基础。 接口 AiClient 就是这样一个抽象,它有两个基本实现:OpenAI 和 Azure OpenAI。 public interface AiClient { default String generate(String message); AiResponse generate(Prompt prompt); } AiClient 为生成函数提供了两个选项。简化版生成函数:generate(String message),使用 String 作为输入和输出,可以避免使用 Prompt 和 AiResponse 类的额外复杂性。 2.1、高级的 Prompt 和 AiResponse 在 AI 领域,Prompt(提示)是指提供给 AI 的文本信息。它由上下文和问题组成,该模型用于生成答案。 从 Spring AI 项目的角度来看,Prompt 是一个参数化 Message 列表。 public class Prompt { private final List<Message> messages; // 构造函数和其他方法 } public interface Message { String getContent(); Map<String, Object> getProperties(); MessageType getMessageType(); } Prompt 使开发人员能够对文本输入进行更多控制。Prompt 模板就是一个很好的例子,它使用预定义文本和占位符集构建。然后,可以使用传递给 Message 构造函数的 Map<String, Object> 值填充它们。

在 Spring Boot 中把 YAML 属性绑定到 Map

1、概览 本文将带你了解如何在 Spring Boot 中把 YAML 属性注入到 Map。 2、Spring 中的 YAML 文件 使用 YAML 文件存储外部配置数据是 Spring 开发人员的常见做法。Spring 支持使用 YAML 作为 Properties 的替代。 Spring 底层使用 SnakeYAML 来解析 YAML。 话不多说,来看看典型的 YAML 文件是什么样的: server: port: 8090 application: name: myapplication url: http://myapplication.com 如你所见,YAML 文件不言自明,而且更易于人阅读。YAML 提供了一种精美而简洁的方式来存储层次化的配置数据。 默认情况下,Spring Boot 会在应用启动时从 application.properties 或 application.yml 中读取配置属性。不过,可以使用 @PropertySource 来加载自定义 YAML 文件。 熟悉了 YAML 文件后,来看看如何在 Spring Boot 中将 YAML 属性注入到 Map 中。 3、将 YAML 属性注入到 Map 通过 @ConfigurationProperties 注解,Spring Boot 可轻松地将配置文件中的外部属性直接注入 Java 对象。

Spring 配置 Kafka 死信队列

1、简介 本文将带你了解如何在 Spring 中为 Apache Kafka 配置死信队列。 2、死信队列 死信队列(Dead Letter Queue,DLQ)用于存储由于各种原因无法正确处理的消息,例如间歇性系统故障、无效的消息模式或损坏的内容。这些消息可以稍后从 DLQ 中移除,以进行分析或重新处理。 下图是 DLQ 机制的简化流程: 通常情况下,使用 DLQ 是一个不错的主意,但也有一些情况下应该避免使用 DLQ。例如,在对消息的精确顺序很重要的队列中,不建议使用 DLQ,因为重新处理 DLQ 消息会打乱消息的到达顺序。 3、Spring Kafka 中的死信队列 在 Spring Kafka 中,与 DLQ 概念相对应的是死信 Topic(DLT)。 接下来,我们通过一个简单的支付系统来介绍 DLT 应该如何使用。 3.1、Model 类 从 Model 类开始: public class Payment { private String reference; private BigDecimal amount; private Currency currency; // Get、Set 方法省略 } 再实现一个用于创建事件的方法: static Payment createPayment(String reference) { Payment payment = new Payment(); payment.setAmount(BigDecimal.valueOf(71)); payment.

在 Spring Boot 中通过 @Value 绑定属性到枚举(不分区大小写)

1、概览 Spring 提供了自动配置功能,可以用它来绑定组件、配置 Bean 以及从属性源中设置值。 当我们不想硬编码值,而希望使用 properties 文件或系统环境提供值时,@Value 注解就非常有用。 本文将带你了解如何通过 Spring 自动配置将属性值映射到 Enum 实例,不区分大小写。 2、Converter<F,T> Spring 使用 Converter 将 @Value 注解中的 String 值映射到所需的类型。一个专用的 BeanPostProcessor 会遍历所有组件,并检查它们是否需要额外的配置或注入。然后,找到一个合适的 Converter,并将源 Converter 中的数据绑定目标。Spring 提供了一个内置的 String 到 Enum 类型的 Converter,让我们来详细了解一下。 2.1、LenientToEnumConverter 顾名思义,该 Converter 在转换过程中可以自由解释数据。最初,它假定提供的数值是正确的: @Override public E convert(T source) { String value = source.toString().trim(); if (value.isEmpty()) { return null; } try { return (E) Enum.valueOf(this.enumType, value); } catch (Exception ex) { return findEnum(value); } } 不过,如果无法将源映射到 Enum,它就会尝试另一种方法。它会获取 Enum 和 value 的规范名称:

使用 HexFormat 来格式化和解析十六进制字符串

十六进制(Hexadecimal)是一种数制系统,它使用 16 个数字来表示数值,分别是 0 到 9 和 A 到 F。 十六进制经常用于表示字节数据。在十六进制表示中,一个字节可以用两个十六进制数字表示。例如,字节的取值范围是 0 到 255,可以用 00 到 FF 来表示。其中,00 表示二进制的 00000000,FF 表示二进制的 11111111。这在 Socket 通信协议的定义中很常见。 简单来说,对于一些较短的二进制数据,可以把它序列化为十六进制字符串,其中每 2 个字符,表示一个字节。同样,也可以把十六进制的字符串解析为字节数组。最常见的场景就是把 Hash 计算的结果表示为十六进制字符串。 通常我们可以选择使用第三方的 commons-codec 库来实现格式化和解析十六进制字符串。可能是这个功能需求太常见,于是从JDK 17 开始,标准库中提供了一个 HexFormat 工具类,用于格式化和解析十六进制字符串。 简单地编码和解码 简单地把字节数组编码为十六进制字符串,以及把十六进制字符串解析为字节数组。 package cn.springdoc.demo.test; import java.util.HexFormat; public class Main { public static void main(String[] args) throws Exception { HexFormat format = HexFormat.of(); String hex = format.formatHex("hello springdoc.cn".getBytes()); System.out.println("Hex=" + hex); byte[] bytes = format.parseHex(hex); System.