Spring报错但瞎报
1.1场景说明
我的第一个controller
package com.javasm.controller;
import com.javasm.config.demoaitool.CalculatorTools;
import com.javasm.config.demoaitool.DateTimeTools;
import com.javasm.config.demoaitool.WeatherTools;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 方法内部调用工具类
*/
@RestController
@RequestMapping("/tool")
@RequiredArgsConstructor
public class Tool1Controller {
private final ChatClient chatClient;
private final WeatherTools weatherTools;
private final DateTimeTools dateTimeTools;
private final CalculatorTools calculatorTools;
/**
* 单工具调用
*/
@GetMapping("/weather")
public String weather(@RequestParam String msg) {
return chatClient.prompt()
.tools(MethodToolCallbackProvider.builder().toolObjects(weatherTools).build())
.user(msg).call().content();
}
/**
* 方法内部多工具调用
*/
@GetMapping("/multi2")
public String multiTools(@RequestParam String input) {
return chatClient.prompt()
.tools(
weatherTools,
dateTimeTools,
calculatorTools
)
.user(input).call().content();
}
}
我的第二个controller
package com.javasm.controller;
import com.javasm.config.demoaitool.CalculatorTools;
import com.javasm.config.demoaitool.DateTimeTools;
import com.javasm.config.demoaitool.WeatherTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 构造时注入 ChatClient,通过 .defaultTools 注册 ToolCallbackProvider
*/
@RestController
@RequestMapping("/tool")
public class ToolController {
private final ChatClient chatClient;
public ToolController(ChatClient.Builder builder,
WeatherTools weatherTools,
DateTimeTools dateTimeTools,
CalculatorTools calculatorTools) {
//单工具多工具都这样配置
this.chatClient = builder
.defaultTools(weatherTools, dateTimeTools, calculatorTools)
.build();
}
@GetMapping("/weather")
public String weather(@RequestParam String msg) {
return chatClient.prompt().user(msg).call().content();
}
}
1.2问题说明
因为他俩在一个项目中,并且/tool/weather地址冲突,运行项目的时候本应该报错显示Spring 启动时发现一个地址对应了两个方法,不知道调用哪个,但我在第一次调用的时候显示的却是找不到@tool注解,这让人很费解,并且我询问ai后,ai给我的答案也是胡言乱语:
//甚至ai让我把
.tools(
weatherTools,
dateTimeTools,
calculatorTools
)
//改成这个鸟样子
.tools(
MethodToolCallbackProvider.builder().toolObjects(weatherTools).build(),
MethodToolCallbackProvider.builder().toolObjects(dateTimeTools).build(),
MethodToolCallbackProvider.builder().toolObjects(calculatorTools).build()
)
显然没有解决问题,还是报错,ai依旧胡言乱语。
controller地址冲突依旧没有解决
1.3问题解决
经过问专业java人士,了解到是因为controller中的地址冲突,所以我就把错误改好之后,由于我好奇之前的错误是什么原因是什么,我就又复原错误的代码,运行的栈轨迹和我第一次错误的栈轨迹完全不一致
第一次显示的是
Caused by: java.lang.IllegalStateException: No @Tool annotated methods found in [com.javasm.config.demoaitool.WeatherTools@35cec305, com.javasm.config.demoaitool.DateTimeTools@cbd40c1, com.javasm.config.demoaitool.CalculatorTools@491cafec].Did you mean to pass a ToolCallback or ToolCallbackProvider? If so, you have to use .toolCallbacks() instead of .tool()
我修改完bug后,又恢复到原来的错误前的样子的栈轨迹
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'toolController' method
com.javasm.controller.ToolController#weather(String)
to {GET [/tool/weather]}: There is already 'tool1Controller' bean method
显然,相同的错误,两次打印的错误信息完全不一致。这让人很疑惑。
1.4问题分析
我不明白,为什么两次相同的错误,反而栈轨迹报错信息不一致,所以我就打开deepseek了
我问:有时第一次报错 的信息找不到错误原因怎么办,显示找不到@tool,最后找到的是controller地址重复和使用的是MethodToolCallbackProvider.builder().toolObjects(weatherTools).build(),反而报错找不到tool
deepseek回答:你这个问题问得非常深刻!这恰恰是 Spring 框架异常层层包装 导致的最让人头疼的情况——真正的错误被埋在最底层,表面上看到的异常信息完全指向了另一个方向。我来还原一下你当时的场景,帮你理清楚为什么会这样,以及以后怎么快速找到"真凶"。
deepseek分析
第一次启动:
① 创建 ToolController → ② 构造函数执行 → ③ 扫描 @Tool 失败
→ ④ 抛出 "No @Tool annotated methods found"
→ ⑤ 启动失败(URL 冲突还没被发现)
第二次启动(修改完成后,又修改回去,由于缓存的原因):
① 创建 ToolController → ② 构造函数执行 → ③ 扫描 @Tool 成功
→ ④ 继续初始化 → ⑤ 注册 URL 映射 → ⑥ 发现 URL 冲突
→ ⑦ 抛出 "Ambiguous mapping"
→ ⑧ 启动失败
地址冲突,导致相同的地址方法两个都无法执行,所以在.tools()扫描的时候根本扫不到工具方法里的@Tool
注意:因为controller没有执行---所以controller构造方法没有执行---所以没有执行.tool()---所以找不到工具类中的@Tool注解的方法---所以报错
chatClient.prompt()
.tools(
weatherTools,
dateTimeTools,
calculatorTools
)
.user(input).call().content();
1.5bug栈轨迹查看思路
首先出现错误,要看最后一个Caused by
如果caused by找不到原因,就要看caused by 下 at 下是自己com.javasm.controller路径下的最后一个信息
Caused by: java.lang.IllegalStateException: No @Tool annotated methods found in [com.javasm.config.demoaitool.WeatherTools@35cec305, com.javasm.config.demoaitool.DateTimeTools@cbd40c1, com.javasm.config.demoaitool.CalculatorTools@491cafec].Did you mean to pass a ToolCallback or ToolCallbackProvider? If so, you have to use .toolCallbacks() instead of .tool()
at org.springframework.ai.tool.method.MethodToolCallbackProvider.assertToolAnnotatedMethodsPresent(MethodToolCallbackProvider.java:77) ~[spring-ai-model-1.1.2.jar:1.1.2]
at org.springframework.ai.tool.method.MethodToolCallbackProvider.<init>(MethodToolCallbackProvider.java:61) ~[spring-ai-model-1.1.2.jar:1.1.2]
at org.springframework.ai.tool.method.MethodToolCallbackProvider$Builder.build(MethodToolCallbackProvider.java:153) ~[spring-ai-model-1.1.2.jar:1.1.2]
at org.springframework.ai.support.ToolCallbacks.from(ToolCallbacks.java:34) ~[spring-ai-model-1.1.2.jar:1.1.2]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec.tools(DefaultChatClient.java:923) ~[spring-ai-client-chat-1.1.2.jar:1.1.2]
at org.springframework.ai.chat.client.DefaultChatClientBuilder.defaultTools(DefaultChatClientBuilder.java:174) ~[spring-ai-client-chat-1.1.2.jar:1.1.2]
at com.javasm.controller.ToolController.<init>(ToolController.java:30) ~[classes/:na]← 这里!
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[na:na]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:210) ~[spring-beans-6.2.18.jar:6.2.18]
... 23 common frames omitted
Process finished with exit code 1
虽然说也是指向扫描@tool找不到配置方法的@Tool注解,但是因为自己的业务逻辑和构造逻辑没分清导致的
1.6解决方案
核心原因:不是因为框架在 Controller 层做了扫描,而是因为 你自己的代码调用了扫描方法!,这也是为什么要把业务和构造分开的主要原因,因为这是测试代码,所以没太注重分开写,就出现了这种混乱的错误,在实际的工作项目中,一定要把业务逻辑和构造方法分开写,不仅可以更清晰的找到错误,还可以方便后期的维护
1.不要在controller里做业务逻辑
| 问题 | 说明 |
|---|---|
| Bean 未完全初始化 | 构造函数执行时,Spring 可能还没完成所有 Bean 的注入和初始化 |
| 异常处理复杂 | 构造函数中抛出的异常会导致整个 Bean 创建失败,难以定位 |
| 违反单一职责 | Controller 应该只负责接收请求,不应该负责初始化业务组件 |
| 测试困难 | 构造函数里做了很多事情,单元测试时难以 mock |
如果一定要在controller中执行,用 @PostConstruct注解
@RestController
@RequestMapping("/tool")
public class ToolController {
private final ChatClient.Builder chatClientBuilder;
private final WeatherTools weatherTools;
private ChatClient chatClient;
public ToolController(ChatClient.Builder chatClientBuilder, WeatherTools weatherTools) {
// ✅ 构造函数只做赋值,不做业务逻辑
this.chatClientBuilder = chatClientBuilder;
this.weatherTools = weatherTools;
}
@PostConstruct // ← 在 Bean 初始化完成后执行
public void init() {
// ✅ 在这里做 Tool 扫描,此时所有 Bean 都已准备好
var toolProvider = MethodToolCallbackProvider.builder()
.toolObjects(weatherTools)
.build();
this.chatClient = chatClientBuilder
.defaultTools(toolProvider)
.build();
}
}