Spring AI - 结构化输出

科学处理事物的片段和碎片,并假设存在连续性,而艺术则只关注事物的连续性,假设存在片段和碎片。- 罗伯特·M·皮尔西格 LLM(大型语言模型)生成结构化输出的能力对于依赖于可靠解析输出值的下游应用非常重要。开发人员希望将 AI 模型的结果快速转化为数据类型,如 JSON、XML 或 Java 类,以便传递给应用中的其他函数和方法。 Spring AI Structured Output Converter(结构化输出转换器)有助于将 LLM 输出转换为结构化格式。如下图所示,这种方法围绕 LLM 文本补全端点进行操作: 使用通用的补全 API 从大型语言模型(LLM)生成结构化输出需要对输入和输出进行仔细处理。结构化输出转换器在 LLM 调用之前和之后发挥着关键作用,确保实现所需的输出结构。 在进行 LLM 调用之前,转换器会将格式指令附加到提示中,为模型提供明确的指导,以生成所需的输出结构。这些指令充当蓝图,使模型的响应符合指定的格式。 在 LLM 调用之后,转换器会获取模型的输出文本,并将其转换为结构化类型的实例。转换过程包括解析原始文本输出,并将其映射到相应的结构化数据表示,如 JSON、XML 或特定领域(Domain)的数据结构。 注意,AI 模型不能保证按要求返回结构化输出。它可能不理解提示,也可能无法按要求生成结构化输出。 TIP: 如果你不想深入了解 API 的细节,可以过下一段,直接看 “使用转换器 ”部分。 1、结构化输出 API StructuredOutputConverter 接口定义如下: public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider { } 它以目标结构化类型 T 为参数,结合了 Spring Converter<String, T> 接口和 FormatProvider 接口: public interface FormatProvider { String getFormat(); } 下图说明了通过结构化输出 API 组件的数据流程。

解决 Spring Boot H2 JdbcSQLSyntaxErrorException “Table not found”

1、简介 H2 是一个简单的轻量级内存数据库,Spring Boot 可以自动对其进行配置,使开发人员可以轻松测试数据访问逻辑。 通常情况下,org.h2.jdbc.JdbcSQLSyntaxErrorException 是用于表示与 SQL 语法相关的错误的异常。“Table not found” 表示 H2 无法找到指定的表。 本文将带你了解 H2 抛出 JdbcSQLSyntaxErrorException 异常的原因以及解决办法。 2、示例 既然知道了异常背后的根本原因,来看看如何重现异常。 2.1、H2 配置 Spring Boot 会配置应用使用用户名 sa 和空密码连接到可嵌入的数据库 H2。将这些属性添加到 application.properties 文件中: spring.datasource.url=jdbc:h2:mem:mydb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= 现在,假设有一个名为 person 的表。在此,使用一个基本的 SQL 脚本为数据库添加数据。默认情况下,Spring Boot 会加载 data.sql 文件: INSERT INTO "person" VALUES (1, 'Abderrahim', 'Azhrioun'); INSERT INTO "person" VALUES (2, 'David', 'Smith'); INSERT INTO "person" VALUES (3, 'Jean', 'Anderson'); 2.2、对象关系映射 接下来,使用 JPA 注解将表 person 映射到一个实体。

一种极简单的 Spring Boot 单元测试方法

本文主要介绍了一种单元测试方法,力求零基础人员可以从本文中受到启发,可以搭建一套好用的单元测试环境,并能切实提高交付代码的质量。极简体现在除了 POM 依赖和单元测试类之外,其他什么都不需要引入,只需要一个本地能启动的 Spring Boot 项目。 1、POM依赖 Springboot版本: 2.6.6 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.12.4</version> </dependency> 2、单元测试类示例 主要有两种。 第一种,偏集成测试 需要启动项目,需要连接数据库、RPC 注册中心等。 主要注解:@SpringBootTest + @RunWith(SpringRunner.class) + @Transactional + @Resource + @SpyBean + @Test @SpringBootTest + @RunWith(SpringRunner.class) 启动了一套 Spring Boot 的测试环境; @Transactional 对于一些修改数据库的操作,会执行回滚,能测试执行 sql,但是又不会真正的修改测试库的数据; @Resource 主要引入被测试的类; @SpyBean Spring Boot 环境下 mock 依赖的 Bean,可以搭配 Mockito.doAnswer(...).when(xxServiceImpl).xxMethod(any()) Mock 特定方法的返回值; @Test 标识一个测试方法; TIP:对于打桩有这几个注解 @Mock @Spy @MockBean @SpyBean,每一个都有其对应的搭配,简单说 @Mock 和 @Spy 要搭配 @InjectMocks 去使用,@MockBean 和 @SpyBean 搭配 @SpringBootTest + @RunWith(SpringRunner.

Spring JPA 从序列(SEQUENCE)中获取下一个值

1、简介 Sequence (序列)是用于生成唯一 ID 的数字生成器,可避免数据库中出现重复记录。Spring JPA 为大多数情况提供了自动处理序列的方法。不过,在某些特定情况下,我们可能需要在持久化实体之前手动检索下一个序列值。例如,在将订单(Order)详细信息保存到数据库之前,需要生成一个唯一的订单号。 本文将带你了解使用 Spring Data JPA 从数据库序列中获取下一个值的几种方法。 2、设置项目依赖 首先要在 Maven pom.xml 文件中添加 Spring Data JPA 和 PostgreSQL 驱动依赖,并在数据库中创建序列。 2.1、Maven 依赖 首先,在 pom.xml 中添加必要的依赖项: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> 2.2、测试数据 下面是我们在运行测试用例之前用来准备数据库的 SQL 脚本,可以将该脚本保存为 .sql 文件,并将其放在项目的 src/test/resources 目录中: DROP SEQUENCE IF EXISTS my_sequence_name; CREATE SEQUENCE my_sequence_name START 1; 该命令创建一个从 1 开始的序列,每调用一次 NEXTVAL 就递增一次。 然后,在测试类中使用 @Sql 注解,并将 executionPhase 属性设置为 BEFORE_TEST_METHOD,以便在每个测试方法执行之前将测试数据插入数据库: @Sql(scripts = "/testsequence.sql", executionPhase = Sql.

Spring WebClient 中的 exchange() 和 retrieve() 方法

1、概览 WebClient 是一个简化 HTTP 请求执行过程的接口。与 RestTemplate 不同,它是一个响应式非阻塞客户端,可以消费和操作 HTTP 响应。虽然它被设计为非阻塞型,但也可用于阻塞型场景。 本文将带你了解 WebClient 接口中的关键方法,包括 retrieve()、exchangeToMono() 和 exchangeToFlux(),以及它们之间的差异。 2、示例项目设置 首先,创建一个 Spring Boot 应用,在 pom.xml 中添加 spring-boot-starter-webflux 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>3.2.4</version> </dependency> 该依赖提供了 WebClient 接口,用于执行 HTTP 请求。 另外,来看看 https://jsonplaceholder.typicode.com/users/1 请求的 GET 响应示例: { "id": 1, "name": "Leanne Graham", // ... } 创建一个名为 User 的 POJO 类: class User { private int id; private String name; // 构造函数、Getter、Setter 方法省略 } 来自 JSONPlaceholder API 的 JSON 响应将被反序列化并映射到 User 类的实例。

在 Spring Boot 应用中设置默认时区(Timezone)

1、概览 有时,我们希望能够指定应用使用的时区。我们可以通过几种不同的方法来实现这一目标。一种方法是在执行应用时使用 JVM 参数。另一种方法是在启动生命周期的不同阶段以编程式在代码中进行更改。 本文将带你了解在 Spring Boot 应用中设置默认时区的几种方法。 2、主要概念 TimeZone 的默认值基于运行 JVM 的机器的操作系统。我们可以: 通过使用 user.timezone 参数传递 JVM 参数,可以根据运行任务或 JAR 的不同情况,以不同的方式传递参数。 在程序中使用 Bean 生命周期配置选项(在创建 Bean 时/创建 Bean 前),甚至在类内执行过程中使用这些选项。 在 Spring Boot 应用中设置默认 TimeZone 会影响不同的组件,如日志的时间戳、调度程序(Scheduler)、JPA/Hibernate 时间戳等。这意味着我们选择在何处执行取决于何时需要它生效。例如,是希望在创建某个 Bean 时生效,还是在初始化 WebApplicationContext 后生效? 精确地确定何时设置该值非常重要,因为这可能会导致不必要的应用行为。例如,警报服务可能会在时区更改生效前设置警报,从而导致警报在错误的时间启动。 在决定采用哪种方案之前,另一个需要考虑的因素是可测试性。使用 JVM 参数是比较简单的选择,但测试起来可能比较麻烦,也更容易出现错误。我们无法保证单元测试能以与生产部署相同的 JVM 参数运行。 3、设置 bootRun 任务的默认时区 如果使用 bootRun 任务运行应用,我们可以在命令行中使用 JVM 参数传递默认 TimeZone。 在这种情况下,我们设置的值从一开始执行就可用: mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Duser.timezone=Europe/Athens" 4、在执行 JAR 时设置默认时区 与运行 bootRun 任务类似,我们可以在执行 JAR 文件时在命令行中传递默认的 TimeZone 值。 同样,我们设置的值在执行之初就可用: java -Duser.timezone=Europe/Athens -jar spring-core-4-0.

解决 java.security.UnrecoverableKeyException: Cannot Recover Key

1、简介 本文将带你了解如 java.security.UnrecoverableKeyException 异常出现的原因以及如何解决该异常。 2、背景 在 Java 中,有一个 Keystore 的概念。它本质上是一个包含一些 secret 的文件。它可以包含证书链以及与之对应的私钥。由于证书只是一个带有公钥的 包装器,我们可以简单地说 Keystore 包含了一对非对称密钥。 通常,用密码(“password ”通 常也称为 “passphrase”)保护私钥是一种很好的做法。这不仅是 Java Keystore 的良好做法,也是网络安全的一般做法。实现这种保护的方法通常是使用对称密钥加密算法(如各种 AES 实例)对私钥和密码进行加密。 在这里对我们来说需要注意的是,Keystore 中的私钥可以使用密码进行加密,如上所述。这个特性并不是所有类型的 Keystore 都支持,例如,JKS Keystore 支持私钥密码保护,但 PKCS12 Keystore 不支持。在我们的示例中,我们需要密码保护功能,因此我们使用 JKS Keystore。 3、UnrecoverableKeyException java.security.UnrecoverableKeyException 通常发生在使用 KeyManagerFactory 时,特别是调用 init() 方法时。这是 JSSE 中的一个类,允许我们检索 KeyManager 实例。KeyManager 是一个接口,它代表了一个抽象概念,负责将我们作为客户端向对等方进行身份验证。 init() 方法需要两个参数 - 用于获取认证凭证的 Keystore 和用于私钥解密的密码。当 KeyManagerFactory 无法恢复证书链的私钥时,就会出现 java.security.UnrecoverableKeyException 异常。问题来了 - UnrecoverableKeyException 中的 “recover” 到底是什么意思?这意味着证书链的私钥无法用给定的密码解密。因此,java.security.UnrecoverableKeyException 最常见的原因是 Keystore 中的私钥密码错误。 总之,如果为 KeyManagerFactory 提供的私钥密码/口令不正确,那么 KeyManagerFactory 将无法解密密钥,因此会出现此异常。

Spring Boot 中 Spring Security 自动配置

1、概览 本文将带你了解 Spring Boot 中 Spring Security 的自动配置、默认安全配置,以及如何在需要时禁用或自定义它。 2、默认的 Spring Security 设置 首先添加 security starter 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 这包含初始/默认 Security 配置的 SecurityAutoConfiguration 类。 这里没有指定版本,因为项目使用了 spring-boot-starter-parent 作为 parent。 默认情况下,应用会启用身份验证,内容协商(Content Negotiation)用于确定应使用 basic 还是 formLogin。 有一些预定义的配置属性: spring.security.user.name= spring.security.user.password= 如果不使用预定义属性 spring.security.user.password 配置密码并启动应用,默认密码将随机生成并打印在控制台日志中: Using default security password: c8be15da-4489-4491-9dc6-fab3f91435c7 有关更多默认值,请参阅 Spring Boot 中文文档中的 属性配置。 3、禁用自动配置 要禁止 Security 自动配置并添加我们的自定义配置,需要排除 SecurityAutoConfiguration 自动配置类。 可以通过 @SpringBootApplication 注解中的 exclude 属性来实现: @SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) public class SpringBootSecurityApplication { public static void main(String[] args) { SpringApplication.

Spring Security 7 中的重大变化

虽然 Spring Security 7.0 尚未确定发布日期,但是我们还是需要提前做一些准备工作,因为在已知的信息中,在 Spring Security 7.0 中会有一大批大家熟悉的 API 被移除。这些 API 在 Spring Security 6 中已经处于废弃状态,但是还能用,但是到了 Spring Security 7.0,这些就被移除了,所以我们还是有必要来看看 Spring Security 7.0 中的一些比较典型的变化。 1、Lambda 配置 Lambda DSL 自 Spring Security 5.2 版本以来就存在,它允许使用 lambda 表达式配置 HTTP Security。 我们来看看使用 lambda 配置 HTTP 安全性与之前的配置风格相比有何差别: 使用 lambda 的配置 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/blog/**").permitAll() .anyRequest().authenticated() ) .formLogin(formLogin -> formLogin .loginPage("/login") .

Spring Data JPA 异常 “IllegalArgumentException: Not a Managed Type”

1、概览 使用 Spring Data JPA 时,应用启动出现异常。大致如下: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerAdapter' ... Caused by: java.lang.IllegalArgumentException: Not a managed type: ...OurEntity at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:583) at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:85) ... 大意是说,一些 Bean 创建失败了,导致应用启动失败。 根异常是 IllegalArgumentException:“Not a managed type”,本文将带你了解出现这个异常的原因,以及如何解决该异常。 2、缺少 @Entity 注解 出现这种异常的一个可能原因是,忘记使用 @Entity 注解来标记实体。 2.1、重现问题 假设有以下实体类: public class EntityWithoutAnnotation { @Id private Long id; } 及其对应的 Spring Data JPA repository: public interface EntityWithoutAnnotationRepository extends JpaRepository<EntityWithoutAnnotation, Long> { } 最后是 Application 启动类,它会扫描上面定义的所有类: @SpringBootApplication public class EntityWithoutAnnotationApplication { } 尝试使用此 Application 来启动 Spring Context: