Spring AI源码精读:自动配置原理与核心流程深度走读
Spring AI源码精读:自动配置原理与核心流程深度走读
适读人群:有1-5年Java开发经验,想向AI工程师方向转型的开发者 阅读时长:约20分钟 文章价值:① 彻底搞懂Spring AI自动配置的底层机制 ② 理解ChatClient/ChatModel等核心对象的创建链路 ③ 掌握自定义扩展Spring AI的正确姿势
小赵最近在准备AI工程师面试,发消息问我:
"老张,面试官让我讲一下Spring AI的自动配置原理,我用了好几个月了,但真的不知道背后是怎么跑的。你加一行配置,一个@Autowired ChatClient就能用,这中间发生了什么?"
这个问题问得好。很多人会用,但说不清楚原理。说不清楚原理,遇到问题只能靠百度;说清楚了,遇到问题能自己找。
今天我们就打开Spring AI的源码,把这个链路从头走到尾。
从一行配置说起
你在application.yml里加了这个:
spring:
ai:
openai:
api-key: sk-xxx然后在代码里这样用:
@Autowired
private ChatClient chatClient;就能直接调用OpenAI了。这中间发生了什么?答案藏在三个地方:AutoConfiguration、Spring Factories、Builder模式。
Spring AI自动配置全链路
源码走读
第一步:AutoConfiguration注册
打开spring-ai-openai-spring-boot-starter的jar包,找到:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports内容是:
org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration这就是Spring Boot SPI机制——通过这个文件告诉Spring Boot,你需要加载这个AutoConfiguration类。Spring Boot启动时会扫描所有starter里的这个文件,把里面列的类注册为Bean候选。
第二步:OpenAiAutoConfiguration做了什么
// 简化版,保留核心逻辑
@AutoConfiguration
@ConditionalOnClass({OpenAiApi.class})
@EnableConfigurationProperties({OpenAiConnectionProperties.class, OpenAiChatProperties.class})
@Import({SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class})
public class OpenAiAutoConfiguration {
/**
* 创建OpenAiApi:封装了对OpenAI REST接口的HTTP调用
*/
@Bean
@ConditionalOnMissingBean
public OpenAiApi openAiApi(OpenAiConnectionProperties connectionProperties) {
return OpenAiApi.builder()
.baseUrl(connectionProperties.getBaseUrl())
.apiKey(connectionProperties.getApiKey())
.build();
}
/**
* 创建OpenAiChatModel:Spring AI的ChatModel实现
* 这是真正执行LLM调用的对象
*/
@Bean
@ConditionalOnMissingBean
public OpenAiChatModel openAiChatModel(
OpenAiApi openAiApi,
OpenAiChatProperties chatProperties,
RetryTemplate retryTemplate,
ObjectProvider<ObservationRegistry> observationRegistry) {
return OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(chatProperties.getOptions())
.retryTemplate(retryTemplate)
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
.build();
}
/**
* 创建ChatClient.Builder
* 注意:这里用的是ChatModel,不是OpenAiChatModel
* 这就是Spring AI的抽象层:上层代码与具体模型解耦
*/
@Bean
@Scope("prototype") // 注意是prototype,每次注入都是新Builder
public ChatClient.Builder chatClientBuilder(
ChatModel chatModel,
SpringAiProperties springAiProperties,
ObjectProvider<ChatClientCustomizer> customizerProvider) {
ChatClient.Builder builder = ChatClient.builder(chatModel);
// 应用所有Customizer(这是扩展点!)
customizerProvider.orderedStream()
.forEach(customizer -> customizer.customize(builder));
return builder;
}
}关键点:ChatClient.Builder是prototype作用域。这意味着每次注入Builder都会得到新实例,但不同Builder共享同一个ChatModel(ChatModel是singleton)。
第三步:ChatClient的核心设计
// ChatClient是门面类,链式API都在这里定义
public interface ChatClient {
// 静态工厂方法
static ChatClient create(ChatModel chatModel) {
return builder(chatModel).build();
}
static Builder builder(ChatModel chatModel) {
return new DefaultChatClientBuilder(chatModel);
}
// 核心方法:构建请求
ChatClientRequestSpec prompt();
ChatClientRequestSpec prompt(String content);
ChatClientRequestSpec prompt(Prompt prompt);
}
// 实际实现类 DefaultChatClient(简化版)
public class DefaultChatClient implements ChatClient {
private final ChatModel chatModel;
private final ChatClientRequest defaultRequest; // 默认配置
@Override
public ChatClientRequestSpec prompt() {
// 每次调用创建新的请求构建器
// 默认配置 + 本次请求参数合并
return new DefaultChatClientRequestSpec(
this.chatModel,
this.defaultRequest.copy()
);
}
}第四步:ChatModel.call 的完整调用链
// OpenAiChatModel.call 的核心逻辑(简化版)
public class OpenAiChatModel implements ChatModel {
private final OpenAiApi openAiApi;
private final OpenAiChatOptions defaultOptions;
private final RetryTemplate retryTemplate;
@Override
public ChatResponse call(Prompt prompt) {
// 1. 合并Options(默认配置 + 本次请求配置)
OpenAiChatOptions mergedOptions = mergeOptions(prompt.getOptions());
// 2. 构建API请求对象
ChatCompletionRequest request = buildRequest(prompt.getInstructions(), mergedOptions);
// 3. 执行HTTP调用(带重试)
ChatCompletion response = retryTemplate.execute(ctx ->
openAiApi.chatCompletionEntity(request).getBody()
);
// 4. 转换响应格式
return toChatResponse(response);
}
private OpenAiChatOptions mergeOptions(ChatOptions requestOptions) {
// 运行时Options覆盖默认Options(高优先级覆盖低优先级)
if (requestOptions instanceof OpenAiChatOptions runtimeOptions) {
return OpenAiChatOptions.builder()
.model(Optional.ofNullable(runtimeOptions.getModel())
.orElse(defaultOptions.getModel()))
.temperature(Optional.ofNullable(runtimeOptions.getTemperature())
.orElse(defaultOptions.getTemperature()))
// ... 其他参数
.build();
}
return defaultOptions;
}
private ChatCompletionRequest buildRequest(
List<Message> messages, OpenAiChatOptions options) {
// 将Spring AI的Message列表转换为OpenAI API格式
List<ChatCompletionMessage> apiMessages = messages.stream()
.map(this::toApiMessage)
.toList();
return new ChatCompletionRequest(
apiMessages,
options.getModel(),
options.getTemperature(),
options.getMaxTokens(),
options.getStream() != null && options.getStream()
);
}
}扩展点:如何自定义Spring AI
理解了源码,就能找到扩展点。最重要的三个:
扩展点1:ChatClientCustomizer(全局默认配置)
/**
* 给所有ChatClient注入默认的系统提示和配置
* 实现这个接口,Spring AI会自动应用到所有ChatClient.Builder
*/
@Component
public class GlobalChatClientCustomizer implements ChatClientCustomizer {
@Override
public void customize(ChatClient.Builder builder) {
builder
.defaultSystem("你是一个专业的企业AI助手,回答要专业、简洁、中文。")
.defaultOptions(OpenAiChatOptions.builder()
.temperature(0.7)
.maxTokens(2048)
.build());
}
}扩展点2:替换ChatModel(切换模型提供商)
/**
* 如果你不用OpenAI,想用自己部署的模型
* 只需实现ChatModel接口,Spring的@ConditionalOnMissingBean会自动跳过默认配置
*/
@Component
@Primary // 设为主要Bean,覆盖自动配置的OpenAiChatModel
public class LocalLlamaChatModel implements ChatModel {
private final RestTemplate restTemplate;
private final String localApiUrl = "http://localhost:11434/api/chat";
@Override
public ChatResponse call(Prompt prompt) {
// 调用本地Ollama API
Map<String, Object> request = Map.of(
"model", "llama3",
"messages", convertMessages(prompt.getInstructions()),
"stream", false
);
Map<String, Object> response = restTemplate.postForObject(
localApiUrl, request, Map.class);
return convertResponse(response);
}
@Override
public ChatOptions getDefaultOptions() {
return ChatOptions.builder().build();
}
}扩展点3:Advisor(AOP拦截AI调用)
/**
* Spring AI 1.0的Advisor机制:类似AOP,可以拦截AI调用
* 用于日志记录、缓存、内容过滤等横切关注点
*/
@Component
public class LoggingAdvisor implements CallAroundAdvisor {
private static final Logger log = LoggerFactory.getLogger(LoggingAdvisor.class);
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest,
CallAroundAdvisorChain chain) {
long startTime = System.currentTimeMillis();
String userId = (String) advisedRequest.adviseContext().getOrDefault("userId", "unknown");
log.info("AI调用开始,userId={}, prompt长度={}",
userId,
advisedRequest.userText().length());
// 调用下一个Advisor或最终的ChatModel
AdvisedResponse response = chain.nextAroundCall(advisedRequest);
long elapsed = System.currentTimeMillis() - startTime;
log.info("AI调用完成,userId={}, 耗时={}ms, tokens={}",
userId, elapsed,
response.response().getMetadata().get("usage"));
return response;
}
@Override
public String getName() {
return "LoggingAdvisor";
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最先执行
}
}
// 使用Advisor
@Service
public class AiService {
private final ChatClient chatClient;
public AiService(ChatClient.Builder builder,
LoggingAdvisor loggingAdvisor,
CachingAdvisor cachingAdvisor) {
this.chatClient = builder
.defaultAdvisors(loggingAdvisor, cachingAdvisor)
.build();
}
}用一张图总结整个链路
理解这个链路之后,你就能自信地回答:
"Spring AI通过SPI机制加载AutoConfiguration,AutoConfiguration按照条件注解创建ChatModel和ChatClient.Builder,ChatClient通过Advisor链和ChatModel的call方法最终发出HTTP请求,整个过程通过接口抽象与具体模型解耦,可以通过实现ChatClientCustomizer、ChatModel或Advisor接口来扩展行为。"
面试时说出这段话,面试官会刮目相看。
