友友们,咱们本篇就介绍ChatClient的相关使用
SpringAI对我们调用大模型服务器进行了的封装,那么它里面有两个对象:ChatClient和ChatModel,其中ChatClient是对ChatModel的进一步封装,整体的格式是链式调用,偏我们的现实思维,更加易用。
一、ChatClient的使用
1. 直接查阅文档并复制
我们怎么去使用呢?🤔,直接啥都不知道就写代码吗?🐒
最简单的办法就是查阅官方使用文档👉ChatClientFluentAPI
查阅之后 如下:

2. 在我们的idea中创建一个控制器去调用大模型
java
@RestController
@RequestMapping("/chat")
public class ChatClientController {
private final ChatClient chatClient;
//对ChatClient对象的注入使用的是构造方法注入
public ChatClientController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String generation(String userInput) {
//提示词
return this.chatClient.prompt()
//用户提示词
.user(userInput)
//调用
.call()
//返回响应
.content();
}
}
⚠️注意哦:yml配置文件得格式得正确,我们的spring才读取得到
yml
spring:
application:
name: spring-ai-demo
ai:
openai:
# 下面是我们的DeepSeek的api相关配置
api-key: #你自己的API秘钥
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat # 模型
temperature: 0.7 # 模型的参数0-2之间 数值越高,那么结果更加随机 数值越低,那么数值就越聚焦

3. 代码解释

ChatClient = 对 ChatModel 的高级封装 + 链式调用 + 贴近人类思维的调用方式
那么你想,我们使用构造方法注入的话,那我们的ChatClient对象只能在本例中使用了呀,那其他类要使用怎么办呢?
😄还记得SpringIOC中有一个简单的道理吗?就是说你把第三方的东西交给我们的spring容器管理起来,然后你使用的时候直接打上一个标签@Autowired 注入进来就行了,那么我们怎么做❓
我们写一个配置类,然后让Spring先管理这个配置类(@Configuration) 我们再将我们的对象通过方法返回交给Spring管理起来(@Bean)
咱们开始实践:👇
4. 将ChatClient对象配置交给Spring管理
咱先写一个配置类
java
@Configuration //让Spring对这个配置类进行管理
public class ChatClientConfiguration {
}
在这个配置类中使用方法,方法返回一个对象,对方法打上@Bean注解将这个返回的对象注入到spring中
java
@Configuration
public class ChatClientConfiguration {
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
.build();
}
}
图解:

5. 从IOC容器中获取ChatClient对象
那么获取就很简单啦🎉,直接使用@Autowired注解
java
@RestController
@RequestMapping("/chat")
public class ChatClientController {
@Autowired //将ChatClient注入进来
private ChatClient chatClient;
@GetMapping("/ai")
String generation(String userInput) {
//提示词
return this.chatClient.prompt()
//用户提示词
.user(userInput)
//调用
.call()
//返回响应
.content();
}
}

OK,此时我们运行程序,进行测试:

二、角色预设
那么我们现在可以知道了,得出的结果如下所示:

1. 设置系统提示词指定角色预设
java
@Configuration
public class ChatClientConfiguration {
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
//在此处指定一个系统提示词进行角色预设
.defaultSystem("你叫谈简特,是tan的一名助手,精通聊天话术,主要的工作就是提供情绪价值")
.build();
}
}
加入系统提示词:

2. 结果

三、结构化输出
我们上述返回的都是String类型,那么我们可不可以返回结构化对象呢??
SpringAI⽀持将ChatModel/ChatClient ⽅法的返回类型从String更改为其他类型。
我们通过 entity() ⽅法将模型输出转为⾃定义实体。
但是我们得确保输出格式符合JSON规范。
如下所示👇:
1. 需求
我们生成一个菜单

2. 创建一个实体类
借助JDK16提供的新关键词record来定义⼀个实体类
record 就是 Java 官方给你做的「极简实体类模板」,一行代码 = 一个完整的实体类!
java
/**
* 等价于手动写一个超级完整的 Java 实体类,
* 但你不用写 getter、equals、* hashCode、toString、构造器!
*/
record Recipe(String dish, List<String> ingredients) {}
@GetMapping("/ai")
public Recipe generation(String userInput) {
//提示词
return this.chatClient.prompt()
//用户提示词
.user(String.format("请帮我生成%s的食谱", userInput))
//调用
.call()
//返回响应:使用结构化对象
entity(Recipe.class);
}
3. 解释

4. 结果
它很智能,直接就给我们将数据给填充好了,如下所示👇

四、流式输出
你会发现哈,上述都是我们的模型将结果处理好之后,一次性给你返回的,那么我们怎么做到让他一个字一个字的蹦出来,有着流式体验呢?
SpringAI使⽤ChatClient 的stream() ⽅法⽣成Flux 流。
如下所示👇
1.代码
java
@GetMapping("/ai")
public Flux<String> generation(String userInput) {
//提示词
return this.chatClient.prompt()
//用户提示词
.user( userInput)
//调用 次数使用call()是一次性返回 使用stream()流式输出
.stream()
//返回响应:使用结构化对象
.content();
}
2.对比之前

3. 乱码问题
你使用流式输出 就会出现问题:
比如:如下👇乱码

4. 解决乱码问题
java
@GetMapping(value = "/ai", produces = "text/html;charset=utf-8")
public Flux<String> generation(String userInput) {
//提示词
return this.chatClient.prompt()
//用户提示词
.user( userInput)
//调用 次数使用call()是一次性返回 使用stream()流式输出
.stream()
//返回响应:使用结构化对象
.content();
}

5.结果

五、⽇志打印
SpringAI借助Advisors来实现⽇志打印的功能。、
1. 核心本质
- Advisors = Spring AI 的 AOP 拦截器
- 链式执行,可拦截请求 + 拦截响应
- 每个 Advisor 都能在对话前后加自定义逻辑
- 作用:统一处理、增强功能、不侵入业务代码
2. 常用功能场景
- 敏感词过滤
- 聊天历史记录
- 对话上下文管理
- 日志打印
3. SimpleLoggerAdvisor(内置日志工具)
- 作用:自动打印 AI 对话的请求 + 响应日志
- 可配置:日志级别、日志格式
4. 两种使用方式
① 全局生效(所有对话都打印)
java
@Bean
public ChatClient chatClient(ChatClient.Builder builder){
return builder
.defaultSystem("系统提示词")
.defaultAdvisors(new SimpleLoggerAdvisor()) // 全局
.build();
}
例如我们的代码:
java
@Configuration
public class ChatClientConfiguration {
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
//在此处指定一个系统提示词进行角色预设
.defaultSystem("你叫谈简特,是tan的一名助手,精通聊天话术,主要的工作就是提供情绪价值")
.defaultAdvisors(new SimpleLoggerAdvisor())//全局
.build();
}
}

② 单次生效(仅当前请求打印)
优先级 高于全局
java
chatClient.prompt()
.user(userInput)
.advisors(new SimpleLoggerAdvisor()) // 单次
.call()
.content();
5. 开启日志配置
yaml
logging:
level:
org.springframework.ai.chat.client.advisor: debug
为什么必须开启日志级别?
宝贝,这个超级关键,我用最简单的话告诉你👇
⭐因为 :
SimpleLoggerAdvisor 输出的是 DEBUG 级别的日志
而 Spring Boot 默认只打印 INFO 级别及以上 ,不打印 DEBUG。
也就是说:
- 你不开启 debug → 日志被屏蔽 → 看不见任何输出
- 你开启 debug → 日志放行 → 能看到请求/响应详情
打个比方:
日志就像喇叭
- INFO = 正常说话(默认能听见)
- DEBUG = 小声嘀咕(默认听不见)
SimpleLoggerAdvisor 就是用**小声嘀咕(debug)**说话的。
你必须告诉系统:
"我要听小声嘀咕!"
它才会输出。
6. 日志结果
请求:

控制台日志:
bash
2026-04-14T10:59:09.425+08:00 DEBUG 2892 --- [spring-ai-demo] [nio-8080-exec-1] o.s.a.c.c.advisor.SimpleLoggerAdvisor : request: AdvisedRequest[chatModel=OpenAiChatModel [defaultOptions=OpenAiChatOptions: {"streamUsage":false,"model":"deepseek-chat","temperature":0.7}], userText=你是谁, systemText=你叫谈简特,是tan的一名助手,精通聊天话术,主要的工作就是提供情绪价值, chatOptions=OpenAiChatOptions: {"streamUsage":false,"model":"deepseek-chat","temperature":0.7}, media=[], functionNames=[], functionCallbacks=[], messages=[], userParams={}, systemParams={}, advisors=[org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1@39eb2d2c, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$2@4f0e1ac7, SimpleLoggerAdvisor, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1@4e479638, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$2@162b58b3], advisorParams={}, adviseContext={}, toolContext={}]
2026-04-14T10:59:12.346+08:00 DEBUG 2892 --- [spring-ai-demo] [oundedElastic-1] o.s.a.c.c.advisor.SimpleLoggerAdvisor : response: {
"result" : {
"metadata" : {
"finishReason" : "STOP",
"contentFilters" : [ ],
"empty" : true
},
"output" : {
"messageType" : "ASSISTANT",
"metadata" : {
"role" : "ASSISTANT",
"messageType" : "ASSISTANT",
"finishReason" : "STOP",
"refusal" : "",
"index" : 0,
"id" : "599d877f-f300-4bb8-9a5b-fb558d23e0e0"
},
"toolCalls" : [ ],
"media" : [ ],
"text" : "我是谈简特,专门为你提供情绪支持和聊天陪伴的助手。有什么想聊的,或者需要我帮忙的吗? 😊"
}
},
"metadata" : {
"id" : "599d877f-f300-4bb8-9a5b-fb558d23e0e0",
"model" : "deepseek-chat",
"rateLimit" : {
"requestsReset" : 0.0,
"tokensLimit" : 0,
"requestsLimit" : 0,
"tokensRemaining" : 0,
"tokensReset" : 0.0,
"requestsRemaining" : 0
},
"usage" : {
"promptTokens" : 27,
"completionTokens" : 28,
"totalTokens" : 55,
"nativeUsage" : {
"promptTokens" : 27,
"totalTokens" : 55,
"completionTokens" : 28
},
"generationTokens" : 28
},
"promptMetadata" : [ ],
"empty" : true
},
"results" : [ {
"metadata" : {
"finishReason" : "STOP",
"contentFilters" : [ ],
"empty" : true
},
"output" : {
"messageType" : "ASSISTANT",
"metadata" : {
"role" : "ASSISTANT",
"messageType" : "ASSISTANT",
"finishReason" : "STOP",
"refusal" : "",
"index" : 0,
"id" : "599d877f-f300-4bb8-9a5b-fb558d23e0e0"
},
"toolCalls" : [ ],
"media" : [ ],
"text" : "我是谈简特,专门为你提供情绪支持和聊天陪伴的助手。有什么想聊的,或者需要我帮忙的吗? 😊"
}
} ]
}
觉得有用的小伙伴别忘了 点赞、收藏、关注 哦,下期继续分享 Spring AI 入门干货,我们下期见!👋