臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

一、💡 一句话理解

第 4 天核心结论

ChatClient 是你在 Spring Boot 业务代码中调用大模型的客户端。今天只要学会:Spring Boot 自动提供 ChatClient.Builder,你把它 build() 成 ChatClient,再用 prompt(...).call().content() 得到模型回答。

本篇对应 0-学习路线 的第 4 天:Spring AI ChatClient。

今天完成后,你应该能做到:启动现有 Spring Boot 项目,访问一个 Java 接口,并看到模型返回的普通文本。

浏览器或 HTTP 请求
    ↓
Controller 使用 ChatClient 提问
    ↓
模型服务生成回答
    ↓
Controller 返回字符串

二、🧭 理论:它是什么

2.1 ChatClient 是什么

ChatClient 是 Spring AI 提供的高级调用客户端。它帮业务代码完成“组织本次提问 → 调用底层模型 → 取出回答”的过程。

可以把它理解成 Java 后端和大模型之间的一个操作面板:你给它问题,它负责把问题交给已经配置好的模型服务,再把回答交还给你的代码。

今天只关注普通文本问答,不学习模型角色、流式输出、记忆或工具调用。

2.2 ChatClient 和 ChatModel 的关系

组件现在先这样理解
ChatModel底层模型调用能力,负责和 DeepSeek、OpenAI 等服务通信。
ChatClient业务代码直接使用的客户端,调用方式更简洁。
ChatClient.Builder用来创建 ChatClient 的构建器。

在你的项目中,模型地址、模型名和 API Key 已经在 application.properties 与 IDEA 运行配置中准备好。Spring Boot 根据这些配置创建底层 ChatModel,再自动提供 ChatClient.Builder。

所以今天的业务代码不需要再次写 API Key、模型名或 HTTP 请求地址。

三、⚙️ 理论:它是怎么工作的

3.1 ChatClient 从哪里来

Spring Boot 启动后,会把自动配置好的 ChatClient.Builder 放进 Spring 容器。Controller 的构造方法请求这个对象,Spring 就会自动注入它。

private final ChatClient chatClient;
 
public AiController(ChatClient.Builder builder) {
    this.chatClient = builder.build();
}

这四行代码的过程是:

Spring Boot 自动配置模型
    ↓
创建 ChatClient.Builder
    ↓
注入到 AiController 构造方法
    ↓
builder.build() 得到可使用的 chatClient

chatClient 是 Controller 的成员变量,后续每个接口方法都可以使用它。

3.1.1 构造方法这一段逐行是什么意思

public AiController(ChatClient.Builder builder) {
    this.chatClient = builder.build();
}

先看结论:这不是你手动执行的代码。Spring Boot 启动时要创建 AiController 对象,发现它的构造方法需要一个 ChatClient.Builder,就从 Spring 容器中取出已经准备好的 Builder 传进来。

代码含义
public AiController(...)AiController 的构造方法。创建 Controller 对象时会执行一次。
ChatClient.Builder参数的类型。这个类型本身提供了 build() 方法。
builder参数变量名,只是当前方法里对 Builder 对象的称呼;也可以命名为 chatClientBuilder。
builder.build()调用 Builder 的创建方法,得到一个真正可调用模型的 ChatClient 对象。
this.chatClient当前 AiController 对象中的成员变量。this 的意思是“当前这个 Controller 自己”。
=把刚创建的 ChatClient 保存到成员变量中,供 test1() 等接口方法后续使用。

为什么 builder 能调用 build()?因为它的类型是 ChatClient.Builder,而 Spring AI 在这个类型中定义了 build() 方法。Java 判断“能不能调用某个方法”,看的是变量的类型,不是变量名字。

下面两个写法完全一样,只是参数变量名不同:

public AiController(ChatClient.Builder builder) {
    this.chatClient = builder.build();
}
 
public AiController(ChatClient.Builder chatClientBuilder) {
    this.chatClient = chatClientBuilder.build();
}

Spring Boot 创建 Controller 的完整过程是:

Spring Boot 启动
  → 读取 Spring AI Starter、模型地址、模型名和 API Key 配置
  → 自动创建 ChatModel 和 ChatClient.Builder
  → 扫描到 @RestController 的 AiController
  → 发现构造方法需要 ChatClient.Builder
  → 从 Spring 容器取出 Builder,作为 builder 参数传入
  → 执行 builder.build()
  → 得到并保存 chatClient

这叫“构造方法注入”:对象一创建,就把它依赖的对象传给它。这样 AiController 不用自己 new ChatClient,也不用自己处理模型配置。

3.2 最小调用链

最简单的普通问答是:

String answer = chatClient
        .prompt("Spring Boot 和 Spring AI 有什么关系?")
        .call()
        .content();
调用当前作用
prompt("...")放入本次要问模型的文字。第 5 天会专门学习 Prompt、System Prompt 和模板。
call()声明使用普通的一次性调用方式。
content()真正取出模型生成的文本;结果类型是 String。

调用流程是:

问题文本
  → ChatClient 组织本次调用
  → ChatModel 请求已配置的模型服务
  → 模型生成回答
  → content() 取出回答字符串

四、🚀 实践:从准备到验证

4.1 前置准备

本篇不重复第 1 天的项目创建和第 3 天的模型 API 配置。开始今天的代码前,只确认下面三件事已经完成。

4.1.1 已有 Spring Boot 项目和 OpenAI 兼容模型配置

你的当前项目已经使用 DeepSeek 的 OpenAI 兼容接口,配置应类似:

spring.ai.openai.api-key=${DEEPSEEK_API_KEY}
spring.ai.openai.base-url=https://api.deepseek.com
spring.ai.openai.chat.model=deepseek-v4-flash

DEEPSEEK_API_KEY 的真实值放在 IntelliJ IDEA 的 AiApplication 运行配置的环境变量中,不写进这个文件。

如果还不理解 API Key、模型名和 base-url,先回看 2.大模型 API、请求参数与响应结构。

4.1.2 已添加 Spring AI OpenAI Starter

pom.xml 中需要有:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

它的作用是让 Spring Boot 能够创建底层模型和 ChatClient.Builder。

4.1.3 启动后端项目

打开 AiApplication,点击 IntelliJ IDEA 的绿色运行按钮。成功后保持项目运行,并确认服务监听 8080 端口。

今天用的是普通同步调用,不需要先学习 SSE,也不需要启动前端项目。

4.2 可以拿来干什么

4.2.1 做一次普通文本问答

用途:后端向模型发送固定问题,拿到一段普通字符串回答。这是后续聊天接口、Prompt、结构化输出、流式输出等能力的共同起点。

前置:已完成 4.1,并且已经通过构造方法创建了 chatClient。

String answer = chatClient
        .prompt("Spring Boot 和 Spring AI 有什么关系?")
        .call()
        .content();

输入是问题字符串;输出是模型生成的 String。今天先把这个字符串直接返回给 HTTP 调用方,下一步再学习怎样控制问题格式和回答风格。

4.3 完整实践:第一个 ChatClient 接口

目标:在当前项目中新增一个 /test1 接口。访问它后,后端调用模型,并把模型回答直接返回。

4.3.1 在 Controller 中写入代码

文件位置:

src/main/java/com/afyke/ai/controller/AiController.java

如果文件已经存在,只需要确认其中有下面的 chatClient 字段、构造方法和 test1 方法;不要重复创建两个同名 Controller。

package com.afyke.ai.controller;
 
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class AiController {
 
    private final ChatClient chatClient;
 
    public AiController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
 
    @GetMapping("/test1")
    public String test1() {
        return chatClient
                .prompt("Spring Boot 和 Spring AI 有什么关系?")
                .call()
                .content();
    }
}

输入:代码中固定的问题“Spring Boot 和 Spring AI 有什么关系?”。

处理:test1() 调用 chatClient,模型生成文本,content() 取回文本。

输出:接口返回模型生成的一段普通文字。

4.3.2 用 HTTP Client 验证

在后端项目根目录的 request.http 中添加:

### 第 4 天:ChatClient 普通文本调用
GET http://localhost:8080/test1

启动 AiApplication 后,点击请求左侧的绿色运行按钮。

预期结果:HTTP 状态为 200,响应正文是一段模型对 Spring Boot 和 Spring AI 关系的回答。

至此,今天的链路已经跑通:

GET /test1
  → AiController.test1()
  → chatClient.prompt(...).call().content()
  → DeepSeek
  → 返回文本

五、📌 总结

  • 本篇对应学习路线第 4 天,只学习 ChatClient 的普通文本调用。
  • Spring Boot 自动提供 ChatClient.Builder,构造方法注入后调用 build() 得到 ChatClient。
  • prompt(...).call().content() 是最小调用链,结果是 String。
  • 今天的验收是 GET /test1 能返回模型回答。
  • 第 5 天再学习 Prompt、System Prompt 和 Prompt 模板。
  • 第 6 天再学习结构化输出;第 7 天学习 SSE;第 8 天学习会话 ID、聊天记录和 Redis。
  • RAG、Tool Calling 和 Agent 工作流属于后续阶段,不混入本篇。

记忆句:

ChatClient = 注入 Builder → build() → prompt() → call() → content()

官方资料: