第1863篇:Spring AI的自动配置原理——深入AutoConfiguration实现细节
第1863篇:Spring AI的自动配置原理——深入AutoConfiguration实现细节
有一件事我一直觉得值得聊:很多用 Spring AI 的工程师,能把项目跑起来、能调接口、能做 RAG,但如果你问他"Spring AI 的 ChatClient 是怎么被自动注入的",往往就答不上来了。
这不是什么大问题,业务能跑就行。但当你遇到奇怪的 bean 冲突、多模型配置出错、某个自动配置没生效的时候,不懂原理就只能靠蒙。而搞清楚自动配置机制,这类问题基本都能自己排查。
今天就带着大家把 Spring AI 的 AutoConfiguration 扒开来看看,从 Spring Boot 的自动配置机制讲起,一路到 Spring AI 的具体实现。
一、先把 Spring Boot AutoConfiguration 机制搞清楚
Spring AI 的自动配置是建立在 Spring Boot 的 AutoConfiguration 机制上的,所以先把这个基础讲清楚。
Spring Boot 的自动配置工作流程如下:
核心机制:每个自动配置类上都有各种 @Conditional 注解,决定这个配置类是否生效。常见的条件:
@ConditionalOnClass:classpath 上有某个类才生效@ConditionalOnMissingBean:容器里没有对应 bean 才生效@ConditionalOnProperty:某个配置属性满足条件才生效
这三个条件的组合,决定了自动配置的"按需启用"行为。
二、Spring AI 自动配置的入口在哪里
用 IDE 搜一下 spring-ai-openai-spring-boot-starter 这个 artifact,找到它的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件(Spring Boot 3.x 的新入口,取代了 2.x 时代的 spring.factories):
org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration
org.springframework.ai.autoconfigure.openai.OpenAiChatAutoConfiguration
org.springframework.ai.autoconfigure.openai.OpenAiEmbeddingAutoConfiguration
org.springframework.ai.autoconfigure.openai.OpenAiImageAutoConfiguration
org.springframework.ai.autoconfigure.openai.OpenAiAudioTranscriptionAutoConfiguration每个 provider 对应一组 AutoConfiguration 类。以 OpenAI 为例,核心的有三个:OpenAiAutoConfiguration(基础配置)、OpenAiChatAutoConfiguration(ChatModel)、OpenAiEmbeddingAutoConfiguration(EmbeddingModel)。
三、OpenAiAutoConfiguration 做了什么
@AutoConfiguration
@ConditionalOnClass(OpenAiApi.class)
@EnableConfigurationProperties(OpenAiConnectionProperties.class)
@Import({ OpenAiChatAutoConfiguration.class,
OpenAiEmbeddingAutoConfiguration.class,
OpenAiImageAutoConfiguration.class })
public class OpenAiAutoConfiguration {
// 这个类本身主要负责引入其他配置
}关键点:
@ConditionalOnClass(OpenAiApi.class):只有 classpath 有spring-ai-openai依赖时才生效@EnableConfigurationProperties:激活配置属性类OpenAiConnectionProperties,让spring.ai.openai.*配置绑定生效@Import:组合引入子配置类
四、OpenAiChatAutoConfiguration 的实现细节
这个类负责注册 OpenAiChatModel 和 ChatClient.Builder,是最核心的部分:
@AutoConfiguration
@ConditionalOnClass(OpenAiApi.class)
@ConditionalOnProperty(
prefix = OpenAiChatProperties.CONFIG_PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = true // 默认启用
)
@EnableConfigurationProperties({
OpenAiConnectionProperties.class,
OpenAiChatProperties.class
})
public class OpenAiChatAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OpenAiApi openAiApi(OpenAiConnectionProperties connectionProperties) {
return new OpenAiApi(
connectionProperties.getBaseUrl(),
connectionProperties.getApiKey()
);
}
@Bean
@ConditionalOnMissingBean
public OpenAiChatModel openAiChatModel(
OpenAiApi openAiApi,
OpenAiChatProperties chatProperties) {
return new OpenAiChatModel(
openAiApi,
chatProperties.getOptions()
);
}
@Bean
@ConditionalOnMissingBean
public ChatClient.Builder chatClientBuilder(
ChatModel chatModel,
ObjectProvider<List<Advisor>> advisors) {
ChatClient.Builder builder = ChatClient.builder(chatModel);
advisors.ifAvailable(list -> list.forEach(builder::defaultAdvisors));
return builder;
}
}这段伪代码揭示了关键逻辑:
1. @ConditionalOnMissingBean 的妙用
openAiApi、openAiChatModel、chatClientBuilder 三个 bean 都加了 @ConditionalOnMissingBean。这意味着:如果你在自己的配置类里定义了这些 bean,自动配置就不会覆盖你的定义。这是 Spring Boot 自动配置的"约定优于配置"原则的体现。
2. ChatClient.Builder 而不是 ChatClient
注意自动配置注册的是 ChatClient.Builder,不是 ChatClient。因为 ChatClient 的实例可能需要在运行时动态配置(比如加不同的 Advisor),所以提供一个 Builder,让开发者自己 .build()。
3. ObjectProvider 的使用
ObjectProvider<List<Advisor>> 是一种懒加载注入方式,即使容器里没有任何 Advisor bean 也不会报错。这让 Advisor 成为可选依赖。
五、配置属性类的绑定原理
OpenAiConnectionProperties 是怎么把 application.yml 里的配置绑定到 Java 对象的?
@ConfigurationProperties(prefix = OpenAiConnectionProperties.CONFIG_PREFIX)
public class OpenAiConnectionProperties extends ConnectionProperties {
public static final String CONFIG_PREFIX = "spring.ai.openai";
private String apiKey;
private String baseUrl = "https://api.openai.com";
// getter/setter...
}@ConfigurationProperties 注解告诉 Spring Boot:把配置文件中 spring.ai.openai.* 前缀下的属性,按字段名自动绑定到这个类的字段上。
OpenAiChatProperties 则负责绑定 chat 相关的配置:
@ConfigurationProperties(prefix = OpenAiChatProperties.CONFIG_PREFIX)
public class OpenAiChatProperties {
public static final String CONFIG_PREFIX = "spring.ai.openai.chat";
private boolean enabled = true;
private OpenAiChatOptions options = OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.7f)
.build();
// getter/setter...
}所以 application.yml 里写的:
spring:
ai:
openai:
api-key: sk-xxx
chat:
options:
model: gpt-4o
temperature: 0.7最终会被绑定到这两个 Properties 类的字段上,然后传给 OpenAiApi 和 OpenAiChatModel。
六、多模型场景下的 bean 命名和优先级
这是实际项目里最容易踩坑的地方。假设你同时接了 OpenAI 和通义千问两个模型:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-dashscope-spring-boot-starter</artifactId>
</dependency>此时容器里会有两个 ChatModel bean:
openAiChatModel(由 OpenAI AutoConfiguration 注册)dashscopeChatModel(由 DashScope AutoConfiguration 注册)
还会有两个 ChatClient.Builder,注册时用不同的 bean 名字区分。
问题来了:如果你直接 @Autowired ChatClient.Builder chatClientBuilder,Spring 不知道该给你哪个,会报 NoUniqueBeanDefinitionException。
解决方案一:用 @Qualifier
@Bean
public ChatClient openAiChatClient(
@Qualifier("openAiChatClientBuilder") ChatClient.Builder builder) {
return builder.build();
}
@Bean
public ChatClient dashscopeChatClient(
@Qualifier("dashscopeChatClientBuilder") ChatClient.Builder builder) {
return builder.build();
}解决方案二:用 @Primary 指定主要 bean
@Bean
@Primary
public ChatClient primaryChatClient(
@Qualifier("openAiChatClientBuilder") ChatClient.Builder builder) {
return builder.build();
}加了 @Primary,直接 @Autowired ChatClient 就会注入这个 bean。
七、自定义 AutoConfiguration:扩展 Spring AI 的正确姿势
理解了原理之后,我们可以自己写一个 AutoConfiguration,来扩展 Spring AI 的行为。
比如,你想让所有的 ChatClient 都默认带上你自己的 TokenBudgetAdvisor(控制每次对话不超过 N 个 token):
第一步:写 Advisor
@Component
public class TokenBudgetAdvisor implements CallAroundAdvisor {
private final int maxTokensPerCall;
public TokenBudgetAdvisor(@Value("${app.ai.max-tokens-per-call:1000}")
int maxTokensPerCall) {
this.maxTokensPerCall = maxTokensPerCall;
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest request,
CallAroundAdvisorChain chain) {
// 检查请求的 maxTokens 设置,超过预算则覆盖
AdvisedRequest modified = request;
// 实际项目中可以修改 options
return chain.nextAroundCall(modified);
}
@Override
public String getName() { return "TokenBudgetAdvisor"; }
@Override
public int getOrder() { return 10; }
}第二步:写 AutoConfiguration
@AutoConfiguration(after = OpenAiChatAutoConfiguration.class)
@ConditionalOnClass(ChatClient.class)
@ConditionalOnProperty(
prefix = "app.ai.token-budget",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public class TokenBudgetAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TokenBudgetAdvisor tokenBudgetAdvisor(
@Value("${app.ai.max-tokens-per-call:1000}") int maxTokens) {
return new TokenBudgetAdvisor(maxTokens);
}
}第三步:注册到自动配置入口
在 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 里添加:
com.example.aiapp.config.TokenBudgetAutoConfiguration这样任何引入你这个 jar 包的项目,都会自动获得 TokenBudgetAdvisor 的能力,不需要手动配置。这就是企业内部 AI 公共库的正确设计方式。
八、调试自动配置:如何排查"为什么这个 bean 没有被创建"
遇到 AI 相关的 bean 没有按预期注入时,有几个排查手段:
方法1:开启自动配置报告
# 在 application.properties 里加
debug=true启动日志里会出现 CONDITIONS EVALUATION REPORT,列出每个自动配置类是否生效、不生效的原因。
方法2:用 Actuator 查看 bean 列表
curl http://localhost:8080/actuator/beans | python3 -m json.tool | grep -i "chat"方法3:在代码里打印 bean 列表
@Component
public class BeanInspector implements ApplicationRunner {
@Autowired
private ApplicationContext context;
@Override
public void run(ApplicationArguments args) {
String[] beanNames = context.getBeanNamesForType(ChatModel.class);
Arrays.stream(beanNames).forEach(name ->
log.info("ChatModel bean: {} -> {}", name,
context.getBean(name).getClass().getName()));
}
}九、条件注解的优先级和覆盖顺序
一个经常被问到的问题:如果我在自己的配置类里定义了 ChatModel bean,Spring AI 的自动配置会不会覆盖我的?
答案是不会,因为自动配置类上有 @ConditionalOnMissingBean。但有一个前提:你的配置类要比自动配置类先加载,或者和自动配置类同级别加载。
Spring Boot 的 bean 注册顺序:
@ComponentScan扫描到的类(你的业务代码)- 通过
@Import引入的配置类 - AutoConfiguration 类(最后处理)
所以正常情况下,你在业务代码里定义的 ChatModel bean 会先注册,AutoConfiguration 里的 @ConditionalOnMissingBean 就会判定为"已有 bean,跳过",不会覆盖你的定义。
但有一种场景会出问题:如果两个自动配置类之间存在竞争,比如两个 starter 都想注册同名的 bean,这时需要用 @AutoConfiguration(after = ...) 来控制顺序。
十、总结:理解原理的价值
搞清楚 Spring AI 的自动配置原理,直接的好处有三个:
第一,排错能力大幅提升。NoUniqueBeanDefinitionException、NoSuchBeanDefinitionException 这类报错,一眼就能判断是哪个配置出了问题。
第二,扩展能力。知道怎么写自己的 AutoConfiguration,就能把 AI 相关的公共能力(限流、审计、token 预算)封装成可复用的 starter,在团队内共享。
第三,配置调优。知道哪些配置属性对应哪些 bean,就能精确控制模型参数,而不是靠试错。
原理这东西,不懂的时候觉得没用,用到的时候发现它是真正的"底层竞争力"。
