教程

Spring AI 对 Ollama Tool 的支持

本周早些时候,Ollama 推出 了一项令人兴奋的新功能:对大型语言模型(LLM)的工具支持。 今天,我们非常高兴地宣布 Spring AI(1.0.0-SNAPSHOT)已完全支持这一强大功能,将 Ollama 的函数调用功能引入 Spring 生态系统。 Ollama 的工具支持允许模型决定何时调用外部函数以及如何使用返回的数据。这为访问实时信息到执行复杂计算等各种可能性打开了大门。Spring AI 采用了这一概念,并将其与 Spring 生态系统无缝集成,使 Java 开发人员能够非常轻松地在其应用程序中利用这一功能。Spring AI 的 Ollama 函数调用支持的主要功能包括: 轻松集成:将 Java 函数注册为 Spring Bean,并与 Ollama 模型一起使用。 灵活配置:多种注册和配置函数的方法。 支持多种函数:在一个聊天会话中注册和使用多种函数。 运行时函数选择:动态选择为每个提示(Prompt)启用哪些函数。 代码可移植性:使用不同的 LLM 提供商(如 OpenAI、Mistral、VertexAI、Anthropic、Groq),无需修改代码即可重复使用你的应用代码。 工作原理 定义自定义 Java 函数并将其注册到 Spring AI。 执行可能需要调用函数才能完成回答的聊天请求。 当 AI 模型确定需要调用一个函数时,它会生成一个包含函数名称和参数的 JSON 对象。 Spring AI 会拦截该请求,调用你的 Java 函数,并将结果返回给模型。 该模型将函数的输出纳入其响应中。 入门 先决条件 首先需要在本地计算机上运行 Ollama(0.2.8 以上)。请参阅官方的 Ollama 项目 README,开始在本地计算机上运行模型。然后调出支持模型的工具,例如 Llama 3.1、Mistral、Firefunction v2、Command-R +。。。支持的模型列表可在 模型页面的工具类别 下找到。 ollama pull mistral 依赖 要开始在 Spring AI 中使用 Ollama 函数调用,请在项目中添加以下依赖:

PostConstruct 和 PreDestroy 注解

1、简介 Spring 允许我们在 Bean 的创建和销毁时执行自定义操作。例如,可以让 Bean 实现 InitializingBean 和 DisposableBean 接口,从而在创建和销毁时触发回调方法。 本文将带你了解第二种实现方式,即使用 @PostConstruct 和 @PreDestroy 注解。 2、@PostConstruct Spring 只会在 Bean 属性初始化后调用一次注解了 @PostConstruct 的方法。即使没有任何属性需要初始化,这些方法也会运行。 使用 @PostConstruct 注解的方法可以用任何访问级别,但不能是静态的。 @PostConstruct 一个可能的用途是填充数据。例如,在开发过程中,我们想创建一些默认用户: @Component public class DbInit { @Autowired private UserRepository userRepository; @PostConstruct private void postConstruct() { // 初始化默认用户 User admin = new User("admin", "admin password"); User normalUser = new User("user", "user password"); userRepository.save(admin, normalUser); } } 上述示例将首先初始化 UserRepository 属性,然后运行 @PostConstruct 方法。 3、@PreDestroy 在 Spring 将 Bean 从 Application Context 中删除之前,会运行一次注解了 @PreDestroy 的方法。

Java 和 Guava 中的线程池

1、概览 本文将带你了解 Java 中的线程池。首先介绍 Java 中的标准库,然后介绍 Google 的 Guava 库。 2、线程池 在 Java 中,线程被映射到系统级线程,而系统级线程是操作系统的资源。如果不加控制地创建线程,这些资源可能很快就会耗尽。 操作系统也会在线程之间进行上下文切换,以模拟并行处理。一个简单的观点是,创建的线程越多,每个线程实际工作的时间就越少。 线程池模式有助于在多线程应用中节省资源,并将并行性控制在某些预定义的范围内。 使用线程池时,我们将并发代码编写为并行任务的形式,并将它们提交给线程池实例执行。这个实例控制着多个可重复使用的线程来执行这些任务。 该模式允许我们控制应用创建的线程数量及其生命周期。还能调度任务的执行,并将接收到的任务保存在队列中。 3、Java 中的线程池 3.1、Executors、Executor 和 ExecutorService Executors 工具类包含多个用于创建预配置线程池实例的方法。这些类是一个很好的起点。如果不需要进行任何自定义微调,就可以使用它们。 在 Java 中,我们使用 Executor 和 ExecutorService 接口来处理不同的线程池实现。通常,应该使代码与线程池的实际实现解耦,并在整个应用中使用这些接口。 3.1.1、Executor Executor 接口有一个 execute 方法,用于提交实现了 Runnable 接口的实例以供执行。 来看一个快速示例,演示如何使用 Executors API 获取由单线程池和无界队列支持的 Executor 实例,以便按顺序执行任务。 如下,运行一个任务,只需在屏幕上打印 “Hello World” 即可。我们以 lambda(Java 8 的一个特性)的形式提交任务,它被推断为 Runnable: Executor executor = Executors.newSingleThreadExecutor(); executor.execute(() -> System.out.println("Hello World")); 3.1.2、ExecutorService ExecutorService 接口包含大量用于控制任务进度和管理服务终止的方法。使用该接口,我们可以提交任务以供执行,还可以使用返回的 Future 实例控制任务的执行。 现在,创建一个 ExecutorService,提交一项任务,然后使用返回的 Future 的 get 方法等待提交的任务完成并返回结果值:

处理 Spring Boot H2 Exception:“Schema not found”

1、概览 H2 是一个开源的 SQL 数据库,在 Java 中通常用于测试。它是一个内存数据库,不会将任何数据持久化到磁盘,因此速度非常快。 在与 Spring Boot 整合时,我们可能会遇到 “Schema not found” 异常,本文将带你了解出现此异常的原因,以及如何解决该异常。 2、理解异常的原因 H2 的默认 Schema 是 PUBLIC。如果我们映射的 JPA 实体类不使用 PUBLIC Schema,则必须确保在 H2 上创建了 Schema。当目标 Schema 不存在时,Spring Boot 会抛出异常 “Schema not found”。 模拟一下这个场景。在 Spring Boot 应用中创建以下实体类和 Repository。 @Entity @Table(name = "student", schema = "test") public class Student { @Id @Column(name = "student_id", length = 10) private String studentId; @Column(name = "name", length = 100) private String name; // 构造函数、Getter\Setter 方法 } 如上 @Table 注解指定了实体映射到 test Schema 下的 student 表的映射细节。

Spring Data MongoDB 构建多个条件的查询

1、简介 本文将带你了解如何使用 Spring Data JPA 在 MongoDB 中创建具有多个 Criteria(条件)的查询。 2、项目设置 首先,在 pom.xml 文件中添加 Spring Data MongoDB Starter 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version>3.3.1</version> </dependency> 通过该依赖,我们可以在 Spring Boot 项目中使用 Spring Data MongoDB 的功能。 2.1、定义 MongoDB Document 和 Repository 接下来,定义一个 MongoDB Document(文档),它是一个用 @Document 注解的 Java 类。该类映射到 MongoDB 中的一个 Collection(集合)。 例如,创建一个 Product Document: @Document(collection = "products") public class Product { @Id private String id; private String name; private String category; private double price; private boolean available; // Getter 和 Setter 方法 } 在 Spring Data MongoDB 中,我们可以创建 Repository 来定义自己的查询方法。通过注入 MongoTemplate,可以对 MongoDB 数据库执行高级操作。该类为执行查询、聚合数据和有效处理 CRUD 操作提供了丰富的方法集:

JPA、Hibernate 和 Spring Data JPA 中的数据库审计

1、概览 就 ORM 而言,数据库审计指的是跟踪和记录与实体相关的事件,或者简单地说是实体版本管理。受 SQL 触发器的启发,这些事件是对实体的插入、更新和删除操作。数据库审计的好处类似于源代码版本控制。 本文将带你了解在应用中使用审计的三种方法。首先介绍来自于 JPA 标准的审计实现、然后再介绍由 Hibernate 和 Spring Data 分别提供的审计的扩展实现。 下面是本文中使用的相关实体 Bar 和 Foo 示例: 2、JPA 审计 JPA 并没有明确包含审计 API,但我们可以通过使用实体生命周期事件来实现这一功能。 2.1、@PrePersist、@PreUpdate 和 @PreRemove 在 JPA 实体类中,我们可以使用 @PrePersist、@PreUpdate 和 @PreRemove 注解指定一个方法作为回调,以在相应的 DML 操作前执行它。 @Entity public class Bar { @PrePersist public void onPrePersist() { ... } @PreUpdate public void onPreUpdate() { ... } @PreRemove public void onPreRemove() { ... } } 实体内部的回调方法应是非 static 的,返回类型为 void,且不带参数。访问权限任意。 注意,JPA 中的 @Version 注解与本文的主题没有关系,它是一种乐观锁,与审计数据无关。

在 Spring Data JPA 中使用 Stream(流式)查询

简介 本文将带你了解在 Spring Data JPA 中使用 Stream(流式)查询的最佳方式。 当需要获取较大的结果集时,使用 Java Stream 的好处是可以逐步迭代查询结果集,避免一次性获取所有数据可能导致的内存溢出异常。 JPA Stream 方法 自 2.2 版起,你可以使用 JPA 的 getResultStream 方法以 Stream 的形式处理结果集。 getResultStream 使用 JDBC ResultSet 对给定查询返回的记录进行流式处理。特别是在处理大结果集的时候,这种方法很有效率。 Spring Data JPA Stream 查询方法 如果要对查询结果集进行流式处理,则需要在 Spring Data JPA 查询方法中返回 Java Stream 类型,如下例所示: @Repository public interface PostRepository extends BaseJpaRepository<Post, Long> { @Query(""" select p from Post p where date(p.createdOn) >= :sinceDate """ ) @QueryHints( @QueryHint(name = AvailableHints.HINT_FETCH_SIZE, value = "25") ) // 返回 Stream Stream<Post> streamByCreatedOnSince(@Param("sinceDate") LocalDate sinceDate); } 对于 PostgreSQL 和 MySQL,指定 FETCH_SIZE JPA QueryHint 是必要的,它指示 JDBC 驱动每次迭代的时候最多预取 25 条记录。否则,PostgreSQL 和 MySQL JDBC 驱动会在遍历底层 ResultSet 之前预取所有查询结果。

Spring Security 6.3 中的新特性

1、简介 Spring Security 6.3 版在框架中引入了一系列安全增强功能。本文将带你了解其中一些最值得注意的特性,重点介绍它们的优点和用法。 2、被动式的 JDK 序列化支持 Spring Security 6.3 包含被动式的 JDK 序列化支持(Passive JDK Serialization Support),首先先来了解一下相关的问题和困扰。 2.1、Spring Security 的序列化设计 在 6.3 版本之前,Spring Security 对其类在不同版本之间通过 JDK Serialization 进行序列化和反序列化有严格的策略。这个限制是框架的一个有意设计决策,旨在确保安全性和稳定性。其理由是防止使用不同版本的Spring Security 反序列化在一个版本中序列化的对象时出现不兼容性和安全漏洞。 这一设计的一个关键方面是在整个 Spring Security 项目中使用全局 serialVersionUID。在 Java 中,序列化和反序列化过程使用唯一标识符 serialVersionUID 来验证加载的类是否与序列化对象完全一致。 通过为 Spring Security 的每个发布版本维护一个唯一的全局 serialVersionUID,该框架可确保一个版本的序列化对象无法使用另一个版本进行反序列化。这种方法有效地创建了一个版本屏障,防止了序列化版本 serialVersionUID 值不匹配的对象被反序列化。 例如,Spring Security 中的 SecurityContextImpl 类表示 Security Context 信息。该类的序列化版本包含该版本特有的 serialVersionUID。当尝试在不同版本的 Spring Security 中反序列化该对象时,serialVersionUID 不匹配会阻止该过程成功进行。 2.2、序列化设计带来的困扰 在优先增强安全性的同时,这种设计策略也带来了一些困扰。开发人员通常将 Spring Security 与 Spring Session 等其他 Spring 库集成,以管理用户登录会话(Session)。这些会话包含重要的用户身份认证和 Security Context 信息,通常通过 Spring Security 类实现。此外,为了优化用户体验并提高应用的可扩展性,开发人员通常会在各种持久存储解决方案(包括数据库)中存储这些会话数据。

使用 Spring Validator 接口进行数据校验

1、概览 Spring Validator 接口为验证对象提供了一种灵活且可定制的方式。本文将带你了解如何在 Spring 应用中使用 Validator 接口验证对象。 2、Spring Validator 接口 Validator 接口是 Spring 框架的一部分,它提供了一种验证对象的方法。 这是一个简单的接口,定义了两个方法:supports() 和 validate(),用于确定 Validator 是否能验证对象并执行验证逻辑。 2.1、supports(Class clazz) 方法 Validator 接口中的 supports() 方法决定 Validator 能否验证特定类的实例。该方法的参数 Class<?> clazz 代表被验证对象的类。它是一个泛型类(Class<?>),可以灵活用于不同的对象类型。 具体来说,Spring 利用 isAssignableFrom() 方法来检查对象是否可以合法地转换为 Validator 支持类的对象。因此,如果 Validator 能处理所提供的 clazz 对象,它就会返回 true,否则就会返回 false,以表示应使用另一个 Validator。 @Override public boolean supports(Class<?> clazz) { return User.class.isAssignableFrom(clazz); } 在上例中,Validator 配置为仅支持验证类型为 User 或其子类的对象。isAssignableFrom() 方法通过继来承验证兼容性 - 对于 User 及其子类,它返回 true,对于任何其他类类型,它返回 false。 2.2、validate(Object target, Errors errors) 方法 validate() 方法则用于为 Validator 支持的对象定义自定义验证逻辑。

Spring Boot 中使用 GraphQL 实现文件上传

1、简介 GraphQL 改变了开发人员与 API 交互的方式,为传统的 REST 方法提供了一个精简、强大的替代方案。 然而,在 Java 中使用 GraphQL 处理文件上传,特别是在 Spring Boot 应用中,由于 GraphQL 对二进制数据的处理方式,需要进行一些设置。本文将带你了解如何在 Spring Boot 应用中使用 GraphQL 上传文件。 2、GraphQL文件上传 与 HTTP 文件上传 在使用 Spring Boot 开发 GraphQL API 时,遵循最佳实践通常涉及利用标准的 HTTP 请求来处理文件上传。 通过专用的 HTTP 端点管理文件上传,然后通过 URL 或 ID 等标识符将这些上传链接到 GraphQL Mutation,开发人员可以有效地将直接嵌入 GraphQL Query 的文件上传的复杂性和处理开销降至最低。这种方法不仅简化了上传过程,还有助于避免与文件大小限制和序列化需求相关的潜在问题,从而有助于实现更加精简和可扩展的应用结构。 不过,在某些情况下,有必要将文件上传直接整合到 GraphQL 查询中。在这种情况下,需要进行一些特别的定制,在用户体验和应用程序性能之间取得平衡。因此,我们需要定义一种专门的量 Scalar Type 来处理上传。此外,这种方法还需要部署特定的机制来验证输入,并将上传的文件正确映射到 GraphQL 操作中的变量。此外,文件上传需要使用 Content Type 为 multipart/form-data 的请求体,因此需要实现一个自定义的 HttpHandler。 3、 在 GraphQL 中实现文件上传 首先,需要使用 Spring Boot 官方的 GraphQL Starter。