今天尝试用java的方式去调通一次api,使用智谱模型的api key
1、单次调通
使用jdk17+maven,引入jackson依赖用于解析和构建json数据,代码编写如下:
package com.learning; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.List; import java.util.Map; public class Main { static final String API_URL = "https://open.bigmodel.cn/api/paas/v4/chat/completions"; static final String MODEL = "glm-5.3-flash"; public static void main(String[] args) throws Exception { String apiKey = System.getenv("ZHIPU_API_KEY"); if (apiKey == null || apiKey.isBlank()) { System.err.println("缺少环境变量 ZHIPU_API_KEY,见 Run Configuration 的配置方法"); return; } ObjectMapper mapper = new ObjectMapper(); // 1. 构造请求体 Map<String, Object> body = Map.of( "model", MODEL, "messages", List.of( Map.of("role", "system", "content", "你是一个简洁的 Java 技术顾问。"), Map.of("role", "user", "content", "用两句话解释什么是 Function Calling。") ), "temperature", 0.3 ); // 2. 发请求 HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(API_URL)) .header("Authorization", "Bearer " + apiKey) .header("Content-Type", "application/json") .timeout(Duration.ofSeconds(60)) .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); // 3. 打印完整响应 System.out.println("HTTP " + response.statusCode()); JsonNode root = mapper.readTree(response.body()); System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root)); if (response.statusCode() != 200) { System.err.println("\n调用失败,看上面的错误信息(常见原因:模型名不对 / Key 无效 / 余额不足)"); return; } // 4. 拆出需要的字段 System.out.println("\n===== 解析 ====="); System.out.println("finish_reason : " + root.at("/choices/0/finish_reason").asText()); System.out.println("回复内容 : " + root.at("/choices/0/message/content").asText()); System.out.println("输入 token : " + root.at("/usage/prompt_tokens").asInt()); System.out.println("输出 token : " + root.at("/usage/completion_tokens").asInt()); } }通过此方式把模型当做一个普通的http通信方式的接口去调用,得到的响应结果如下:
===== 解析 =====
finish_reason : stop
回复内容 : ## Transactional 失效的常见原因1. **同类自调用(最常见)**:`this.method()` 不经过代理,事务不生效。解决:注入自身 Bean、用 `AopContext.currentProxy()`、或拆分到另一个类。
2. **方法不是 public**:基于代理的 AOP 只拦截 public 方法。
3. **异常被 catch 吞掉**:切面捕获不到异常,自然不会回滚。
4. **异常类型不匹配**:默认只回滚 `RuntimeException` 和 `Error`,受检异常需指定 `@Transactional(rollbackFor = Exception.class)`。
5. **类未被 Spring 管理**:没有 `@Service`/`@Component` 等注解,不会生成代理对象。
6. **方法被 `final` 或 `static` 修饰**:CGLIB 无法重写 final 方法,static 方法不经过实例代理。
7. **数据库引擎不支持事务**:如 MySQL 的 MyISAM,需使用 InnoDB。
8. **传播行为配置错误**:如 `NOT_SUPPORTED`、`NEVER` 会以非事务方式执行。
9. **多线程调用**:事务上下文绑定 ThreadLocal,子线程中的操作不在同一事务。
10. **事务管理器未配置或配置错误**:非 Spring Boot 项目需 `@EnableTransactionManagement`;多数据源时事务管理器需与数据源对应。
**排查口诀**:先确认走的是代理对象,再确认异常能否抛到切面,最后确认异常类型和传播行为。
输入 token : 36
输出 token : 1272
完整响应信息 :{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "## Transactional 失效的常见原因\n\n1. **同类自调用(最常见)**:`this.method()` 不经过代理,事务不生效。解决:注入自身 Bean、用 `AopContext.currentProxy()`、或拆分到另一个类。\n\n2. **方法不是 public**:基于代理的 AOP 只拦截 public 方法。\n\n3. **异常被 catch 吞掉**:切面捕获不到异常,自然不会回滚。\n\n4. **异常类型不匹配**:默认只回滚 `RuntimeException` 和 `Error`,受检异常需指定 `@Transactional(rollbackFor = Exception.class)`。\n\n5. **类未被 Spring 管理**:没有 `@Service`/`@Component` 等注解,不会生成代理对象。\n\n6. **方法被 `final` 或 `static` 修饰**:CGLIB 无法重写 final 方法,static 方法不经过实例代理。\n\n7. **数据库引擎不支持事务**:如 MySQL 的 MyISAM,需使用 InnoDB。\n\n8. **传播行为配置错误**:如 `NOT_SUPPORTED`、`NEVER` 会以非事务方式执行。\n\n9. **多线程调用**:事务上下文绑定 ThreadLocal,子线程中的操作不在同一事务。\n\n10. **事务管理器未配置或配置错误**:非 Spring Boot 项目需 `@EnableTransactionManagement`;多数据源时事务管理器需与数据源对应。\n\n**排查口诀**:先确认走的是代理对象,再确认异常能否抛到切面,最后确认异常类型和传播行为。",
"reasoning_content": "The user is asking in Chinese about common reasons why Spring's Transactional annotation fails to work. This is a classic Java interview question and a practical development concern. The system prompt says I'm a professional Java technical consultant and should answer concisely (回答简洁).\n\nLet me think about all the common reasons Transactional fails:\n\n1. **方法不是 public** - Transactional only works on public methods (in proxy-based AOP). Non-public methods won't be intercepted (though this changed somewhat with AspectJ mode).\n\n2. **自调用** - Calling a Transactional method from another method within the same class bypasses the proxy, so the transaction annotation doesn't take effect. This is the most common cause.\n\n3. **异常被捕获吞掉** - If you catch the exception inside the method and don't rethrow it, Spring never sees the exception and won't roll back.\n\n4. **异常类型不匹配** - By default, Spring only rolls back on RuntimeException and Error. Checked exceptions (like Exception) don't trigger rollback unless you specify `rollbackFor = Exception.class`.\n\n5. **类没有被 Spring 管理** - The class isn't a Spring bean (no @Service, @Component, etc.), so no proxy is created.\n\n6. **数据库引擎不支持事务** - e.g., MySQL MyISAM engine doesn't support transactions; need InnoDB.\n\n7. **传播行为配置错误** - e.g., using NOT_SUPPORTED, NEVER, or nested transaction issues where inner transaction with REQUIRES_NEW fails but expectation is outer rollback.\n\n8. **多线程调用** - Transaction context is bound to ThreadLocal; if you spawn new threads inside a transactional method, those threads don't participate in the transaction.\n\n9. **final/static 方法** - Can't be overridden by CGLIB proxy (final methods), static methods can't be proxied at all.\n\n10. **事务管理器未配置或配置错误** - Missing EnableTransactionManagement (though Spring Boot auto-configures), wrong PlatformTransactionManager for the datasource.\n\n11. **catch 后又手动 return 或没有抛出** - similar to #3.\n\n12. **propagation 设置问题** - like using SUPPORTS when no transaction exists.\n\n13. **编程式事务混用** issues.\n\n14. **Spring 版本/代理方式问题** - e.g., using JDK dynamic proxy with interface injection issues causing wrong instance.\n\nLet me organize the most common ones concisely with brief explanations and possibly fixes.\n\nFormat considerations: The user asked a technical question in Chinese. The system prompt says to be concise (回答简洁). So I should use a structured list format with the reason and brief explanation/solution. Markdown list is appropriate here since this is likely rendered in a chat interface. I should keep each item brief - one line or two. Maybe group them or just list ~8-10 most common ones.\n\nI'll answer in Chinese since the question is in Chinese.\n\nLet me draft:\n\n## Transactional 失效常见原因\n\n1. **方法非 public**:基于代理的实现只拦截 public 方法\n2. **同类自调用(最常见)**:this.method() 不走代理,事务不生效。解决方案:注入自身 / AopContext.currentProxy() / 拆分类\n3. **异常被 catch 吞掉**:事务切面捕获不到异常,不会回滚\n4. **异常类型不匹配**:默认只回滚 RuntimeException 和 Error,受检异常需指定 `rollbackFor = Exception.class`\n5. **类未被 Spring 管理**:没有 Service 等注解,无代理对象\n6. **方法被 final/static 修饰**:CGLIB 无法重写 final 方法,static 方法不通过实例调用\n7. **数据库引擎不支持事务**:如 MySQL 的 MyISAM\n8. **传播行为配置错误**:如 PROPAGATION_NOT_SUPPORTED、NEVER\n9. **多线程调用**:事务基于 ThreadLocal,子线程不在同一事务中\n10. **未启用事务管理 / 事务管理器配置错误**:非 Boot 项目需 @EnableTransactionManagement\n\nMost common is self-invocation. I could add a quick note about the fix for self-invocation.\n\nShould I include code? Maybe a tiny example for self-invocation would be helpful but the prompt says concise. I'll keep it list-based, maybe with minimal inline code for the fix. Keep it tight.\n\nI'll aim for ~10 items, each one line. That's a good balance of completeness and conciseness.",
"role": "assistant"
}
}
],
"created": 1793690293,
"id": "202611031517483e76838b5a7942d2",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "202611031517483e76838b5a7942d2",
"usage": {
"completion_tokens": 1272,
"completion_tokens_details": {
"reasoning_tokens": 935
},
"prompt_tokens": 36,
"prompt_tokens_details": {
"cached_tokens": 0
},
"total_tokens": 1308
}
}
可以看到到这里我们已经成功调用了一次模型,模型也给出了响应结果,接下来,尝试将问题作为控制台输入,循环调用模型,模拟我们日常使用ai时的多轮对话。
2、多轮对话
编写此程序时要注意的点在于:每次模型的回复都要作为“assistant”角色的content,构造在请求体中进行下一轮对话请求,可以看到上面的模型中会额外返回一个字段叫做“reasoning_content”,这是调用的glm-5.3是思考模型,这个字段相当于它思考的草稿纸,也会消耗token,但在回填请求的时候,这段思考内容不应添加进请求体中,否则会放大token的消耗,经我改进后的多轮对话demo代码如下:
package com.zsj.learning; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Scanner; public class Main { // 智谱api key static final String API_KEY = "自己的api_key"; @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); // 定义结束标记 boolean endFlag = false; // 初始请求体 List<Map<String, String>> messages = new ArrayList<>(); messages.add(Map.of("role", "system", "content", "你是一个专业的 Java 技术顾问,回答简洁。")); Map<String, Object> reqMap = Map.of( "model", "glm-5.3-flash", "messages", messages, "temperature", 0.3 ); // 追加用户输入信息到请求体中 while (!endFlag) { // 获取用户输入信息 System.out.print("\n请输入问题:"); String info = scanner.nextLine(); if ("exit".equals(info)) { endFlag = true; } else { messages.add(Map.of("role", "user", "content", info)); // 发送请求 HttpClient httpClient = HttpClient.newHttpClient(); ObjectMapper objectMapper = new ObjectMapper(); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://open.bigmodel.cn/api/paas/v4/chat/completions")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + API_KEY) .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(reqMap))) .timeout(Duration.ofSeconds(60)) .build(); HttpResponse<String> res = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); // 解析结果,并将结果信息追加到请求中 JsonNode node = objectMapper.readTree(res.body()); System.out.println("\n===== 解析 ====="); System.out.println("finish_reason : " + node.at("/choices/0/finish_reason").asText()); System.out.println("回复内容 : " + node.at("/choices/0/message/content").asText()); System.out.println("输入 token : " + node.at("/usage/prompt_tokens").asInt()); System.out.println("输出 token : " + node.at("/usage/completion_tokens").asInt()); System.out.println("完整响应 : " + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node)); messages.add(Map.of("role", "assistant", "content", node.at("/choices/0/message/content").asText(null))); } } } }此段为自己编写的测试demo,校验逻辑并不完善,正常应该优先去校验http状态码是否是200,再进行后续操作,经验证,此demo可以正常运行,且得到的结果示例如下:
请输入问题:你好
===== 解析 =====
finish_reason : stop
回复内容 : 你好!我是 Java 技术顾问。有什么 Java 相关的问题,随时问我。
输入 token : 26
输出 token : 84
完整响应 :
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "你好!我是 Java 技术顾问。有什么 Java 相关的问题,随时问我。",
"reasoning_content": "The user just said \"你好\" (Hello) in Chinese. The system prompt says I'm a professional Java technical consultant and should answer concisely (回答简洁).\n\nI should greet them back briefly in Chinese since they're speaking Chinese, and perhaps invite them to ask their Java-related question. Keep it concise as instructed.",
"role": "assistant"
}
}
],
"created": 1793697925,
"id": "202611031725197c847776b9e64ed0",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "202611031725197c847776b9e64ed0",
"usage": {
"completion_tokens": 84,
"completion_tokens_details": {
"reasoning_tokens": 65
},
"prompt_tokens": 26,
"prompt_tokens_details": {
"cached_tokens": 0
},
"total_tokens": 110
}
}请输入问题:介绍一下你自己
===== 解析 =====
finish_reason : stop
回复内容 : 你好!我是一个专业的 Java 技术顾问。
**我能帮你:**
- 解答 Java 核心知识(JVM、集合、并发、IO 等)
- 分析代码问题、排查 Bug
- 讲解主流框架 - 提供架构设计、性能优化建议
- 解答面试相关技术问题
我的回答风格是**简洁、专业、直击要点**。
有什么问题尽管问!
输入 token : 49
输出 token : 156
完整响应 :
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "你好!我是一个专业的 Java 技术顾问。\n\n**我能帮你:**\n- 解答 Java 核心知识(JVM、集合、并发、IO 等)\n- 分析代码问题、排查 Bug\n- 讲解主流框架\n- 提供架构设计、性能优化建议\n- 解答面试相关技术问题\n\n我的回答风格是**简洁、专业、直击要点**。\n\n有什么问题尽管问!",
"reasoning_content": "The user is asking me to introduce myself. I'm set up as a professional Java technical consultant who answers concisely. The conversation is in Chinese, so I should respond in Chinese.\n\nI should keep the introduction brief since the system prompt says answers should be concise (回答简洁). Let me introduce myself as a Java technical consultant.",
"role": "assistant"
}
}
],
"created": 1793697935,
"id": "20261103172530ea7beffc926244a2",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "20261103172530ea7beffc926244a2",
"usage": {
"completion_tokens": 156,
"completion_tokens_details": {
"reasoning_tokens": 67
},
"prompt_tokens": 49,
"prompt_tokens_details": {
"cached_tokens": 0
},
"total_tokens": 205
}
}请输入问题:exit
进程已结束,退出代码为 0
这里我设定用户输入exit就认为退出结束对话,此demo可以正常运行,到这里也算是初步学习完了怎么去简单调用模型。