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 实现的情况下抛出的,所以先不关心实现细节。首先来看看异常的一些潜在原因。
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> 值填充它们。
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 对象。
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.
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 的规范名称:
十六进制(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.
1、概览 JpaRepository 供了 CRUD 操作的基本方法。其中 getReferenceById(ID) 和 findById(ID) 是经常引起混淆的方法。这些方法是 getOne(ID)、findOne(ID) 和 getById(ID) 的新 API 名称。
本文将带你了解这些方法之间的区别,以及各自的适用场景。
2、findById() 这个方法按照其名称所示,根据给定的 ID 在 Repository 中查找实体:
@Override Optional<T> findById(ID id); 该方法返回一个 Optional。因此,如果传递了一个不存在的 ID,返回的 Optional 对象将为 empty。
该方法使用了急切加载功能,因此只要调用该方法,就会向数据库发送请求。
执行如下示例:
public User findUser(long id) { log.info("Before requesting a user in a findUser method"); Optional<User> optionalUser = repository.findById(id); log.info("After requesting a user in a findUser method"); User user = optionalUser.orElse(null); log.info("After unwrapping an optional in a findUser method"); return user; } 输出的日志如下:
1、概览 本文将带你了解如何解决 Hibernate 异常 QueryException: “named parameter not bound”。
2、理解异常 简而言之,在将 Hibernate 查询转换为 SQL 时,由于语法无效,Hibernate 会抛出 QueryException 来提示错误。“named parameter not bound” 表明 Hibernate 无法绑定特定查询中指定的命名参数。
通常情况下,命名参数的前缀是冒号(:),后面是一个占位符,表示在执行查询之前需要设置的实际值:
SELECT p FROM Person p WHERE p.firstName = :firstName; 造成异常的最常见原因之一是忘记为 Hibernate 查询中的命名参数赋值。
3、重现异常 理解了导致异常的原因后,通过一个实际的例子来重现这个异常。
创建如下 Person 实体类:
@Entity public class Person { @Id private int id; private String firstName; private String lastName; // 标准的 Get、Set } 如上,@Entity 注解表示类是一个实体,它映射了数据库中的一个表。此外,@Id 表示 id 属性代表主键。
现在,创建一个带有命名参数的 Hibernate 查询,并假装忘记为参数设置值:
@Test void whenNotSettingValueToNamedParameter_thenThrowQueryException() { Exception exception = assertThrows(QueryException.
1、简介 Apache Kafka 是一个分布式流平台,擅长处理海量实时数据流。Kafka 将数据组织成 Topic(主题),并进一步将 Topic 划分为 Partition(分区)。每个分区都是一个独立的 Channel(通道),可实现并行处理和容错。
本文将带你了解如何把数据发送到 Kafka 中特定的分区。
2、理解 Kafka 分区 首先来了解一下 Kafka 分区的基本概念。
2.1、什么是 Kafka 分区? 当生产者向 Kafka Topic 发送消息时,Kafka 会使用指定的分区策略将这些消息组织到分区中。分区是一个基本单元,代表了线性、有序的消息序列。消息一旦产生,就会根据所选的分区策略被分配到一个特定的分区。随后,消息会被附加到该分区中日志的末尾。
2.2、并行消费与消费组 一个 Kafka Topic 可分为多个分区,一个消费组(Consumer Group)可被分配到这些分区的一个子集。组内的每个消费者都会独立处理来自其分配分区的消息。这种并行处理机制提高了整体吞吐量和可扩展性,使 Kafka 能够高效地处理大量数据。
2.3、顺序保证 在单个分区中,Kafka 可确保按照接收到的相同顺序处理消息。这保证了依赖消息顺序的应用(如金融交易或事件日志)的顺序处理。不过,需要注意的是,由于网络延迟和其他操作因素,接收消息的顺序可能与最初发送消息的顺序不同。
在不同的分区中,Kafka 并不保证顺序。来自不同分区的消息可能会被并发处理,从而带来事件顺序变化的可能性。在设计依赖于严格消息顺序的应用时,需要考虑到这个特性。
2.4、容错和高可用 分区还有助于 Kafka 实现出色的容错能力。每个分区都可以在多个 Broker 之间复制。如果 Broker 发生故障,副本分区仍可被访问,并确保对数据的持续访问。
Kafka 集群可以将消费者无缝重定向到健康的 Broker,从而保持数据的可用性和系统的高可靠性。
3、为什么要将数据发送到特定分区? 3.1、数据亲和性 数据亲和性是指有意将相关数据归入同一分区。通过将相关数据发送到特定分区,可以确保这些数据一起处理,从而提高处理效率。
例如,考虑一个场景,我们可能希望确保客户的订单位于同一个分区中,以便进行订单追踪和分析。保证特定客户的所有订单都进入同一个分区可以简化追踪和分析过程。
3.2、负载均衡 此外,在分区之间均匀地分配数据有助于确保最佳的资源利用率。在分区之间平均分配数据有助于优化 Kafka 集群内的资源利用率。通过根据负载情况向分区发送数据,可以防止出现资源瓶颈,确保每个分区都能接收到可管理的均衡工作量。
3.3、优先顺序 在某些情况下,并非所有数据都具有相同的优先级或紧迫性。Kafka 的分区功能可将关键数据引导到专用分区进行快速处理,从而实现关键数据的优先级排序。与不太重要的数据相比,这种优先级排序可确保高优先级的消息得到及时关注和更快处理。
4、向特定分区发送数据的方式 Kafka 提供了将消息分配到分区的各种策略,从而提供了数据分布和处理的灵活性。下面是一些可用于将消息发送到特定分区的常用方法。
4.1、粘性分区器(Sticky Partitioner) 在 Kafka 2.4 及以上版本中,粘性分区器(Sticky Partitioner)的目的是将没有 Key 的消息保持在同一个分区中。不过,这种行为并不是绝对的,它会与批处理设置(如 batch.
概览 本文将带你了解各种 Spring 事务的最佳实践,以保证底层业务的数据完整性。
数据完整性至关重要。如果没有适当的事务处理,应用就很容易出现竞赛条件,从而给底层业务带来可怕的后果。
模拟竞赛条件 以一个实际问题为例,说明在构建基于 Spring 的应用时,应该如何处理事务。
使用以下 Service 层和 Dao 层组件来实现转账服务:
使用最简单的 Dao 层实现来说明不按业务要求处理事务会发生什么情况:
@Repository @Transactional(readOnly = true) public interface AccountRepository extends JpaRepository<Account, Long> { @Query(value = """ SELECT balance FROM account WHERE iban = :iban """, nativeQuery = true) long getBalance(@Param("iban") String iban); @Query(value = """ UPDATE account SET balance = balance + :cents WHERE iban = :iban """, nativeQuery = true) @Modifying @Transactional int addBalance(@Param("iban") String iban, @Param("cents") long cents); } getBalance 和 addBalance 方法都使用 Spring 的 @Query 注解来定义原生 SQL 查询,以检索或者修改用户的账户余额。