Spring AI 核心源码值得学习的 7 大亮点

🎯 一、Spring Boot 自动配置的完美应用
1. 学习点:条件装配(Conditional)
场景:根据配置自动启用/禁用不同 AI 服务
// 案例:模拟 Spring AI 的条件装配
@Configuration
public class AiAutoConfiguration {
// 1. 只有配置了 openai.api-key 才创建 OpenAI 客户端
@Bean
@ConditionalOnProperty(name = "spring.ai.openai.api-key")
public ChatClient openAiChatClient(OpenAiProperties properties) {
System.out.println("✅ OpenAI 客户端已创建");
return new OpenAiChatClient(properties);
}
// 2. 只有配置了 azure.api-key 才创建 Azure 客户端
@Bean
@ConditionalOnProperty(name = "spring.ai.azure.api-key")
public ChatClient azureChatClient(AzureProperties properties) {
System.out.println("✅ Azure 客户端已创建");
return new AzureChatClient(properties);
}
// 3. 如果上面两个都没有,创建模拟客户端(用于测试)
@Bean
@ConditionalOnMissingBean(ChatClient.class)
public ChatClient mockChatClient() {
System.out.println("⚠️ 没有配置 AI 服务,使用模拟客户端");
return new MockChatClient();
}
}
使用示例:
// 测试代码
@SpringBootTest
public class ConditionalTest {
@Test
void testConditional() {
// 场景1:只配置了 OpenAI
System.setProperty("spring.ai.openai.api-key", "sk-test");
// 只会创建 OpenAI 客户端
// 场景2:两个都配置
System.setProperty("spring.ai.openai.api-key", "sk-test");
System.setProperty("spring.ai.azure.api-key", "az-test");
// 两个客户端都会创建
// 场景3:都没配置
// 创建模拟客户端
}
}
2. 学习点:配置属性绑定
// 案例:优雅的属性绑定
@ConfigurationProperties(prefix = "spring.ai.openai")
@Data
public class OpenAiProperties {
// 基本配置
private String apiKey;
private String baseUrl = "https://api.openai.com/v1";
// 聊天配置
private ChatOptions chat = new ChatOptions();
private EmbeddingOptions embedding = new EmbeddingOptions();
// HTTP 客户端配置
private HttpClientOptions http = new HttpClientOptions();
@Data
public static class ChatOptions {
private String model = "gpt-3.5-turbo";
private Double temperature = 0.7;
private Integer maxTokens = 1000;
private List<String> stop = List.of();
}
@Data
public static class HttpClientOptions {
private Duration connectTimeout = Duration.ofSeconds(10);
private Duration readTimeout = Duration.ofSeconds(30);
private Integer maxConnections = 50;
}
}
配置文件:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com/v1
chat:
model: gpt-4
temperature: 0.8
max-tokens: 2000
http:
connect-timeout: 5s
read-timeout: 60s
max-connections: 100
🔧 二、优雅的异常处理设计
3. 学习点:统一的异常体系
// 案例:Spring AI 风格的异常处理
// 1. 基础异常
public abstract class AiException extends RuntimeException {
private final String requestId;
private final Instant timestamp = Instant.now();
public AiException(String message, String requestId, Throwable cause) {
super(message, cause);
this.requestId = requestId;
}
}
// 2. 特定异常
public class RateLimitException extends AiException {
private final Duration retryAfter;
public RateLimitException(String message, String requestId,
Duration retryAfter) {
super(message, requestId, null);
this.retryAfter = retryAfter;
}
}
public class AuthenticationException extends AiException {
public AuthenticationException(String message, String requestId) {
super(message, requestId, null);
}
}
// 3. 异常处理器
@RestControllerAdvice
@Slf4j
public class AiExceptionHandler {
@ExceptionHandler(AiException.class)
public ResponseEntity<ErrorResponse> handleAiException(AiException e) {
log.error("AI 服务异常: {}", e.getMessage(), e);
ErrorResponse response = new ErrorResponse(
e.getClass().getSimpleName(),
e.getMessage(),
e.getRequestId(),
e.getTimestamp()
);
// 特殊处理限流异常
if (e instanceof RateLimitException) {
RateLimitException rle = (RateLimitException) e;
response.setRetryAfter(rle.getRetryAfter().toSeconds());
return ResponseEntity.status(429)
.header("Retry-After", String.valueOf(rle.getRetryAfter().toSeconds()))
.body(response);
}
// 认证异常
if (e instanceof AuthenticationException) {
return ResponseEntity.status(401).body(response);
}
return ResponseEntity.status(500).body(response);
}
}
使用效果:
// 限流时返回
{
"error": "RateLimitException",
"message": "Rate limit exceeded",
"requestId": "req_123456",
"timestamp": "2024-01-15T10:30:00Z",
"retryAfter": 60
}
🔄 三、模板方法模式的应用
4. 学习点:抽象模板类
// 案例:HTTP 客户端的模板方法
public abstract class AbstractHttpClient {
// 模板方法:定义标准流程
public final <T> T execute(HttpRequest request, Class<T> responseType) {
long startTime = System.currentTimeMillis();
try {
// 1. 前置处理
preHandle(request);
// 2. 执行请求(由子类实现)
HttpResponse response = doExecute(request);
// 3. 处理响应
T result = handleResponse(response, responseType);
// 4. 后置处理
postHandle(request, response, startTime);
return result;
} catch (Exception e) {
// 5. 异常处理
return handleException(request, e, startTime);
}
}
// 钩子方法1:前置处理
protected void preHandle(HttpRequest request) {
// 可以记录日志、添加header等
log.debug("开始请求: {}", request.getUri());
}
// 抽象方法:子类必须实现
protected abstract HttpResponse doExecute(HttpRequest request);
// 钩子方法2:处理响应
protected <T> T handleResponse(HttpResponse response, Class<T> responseType) {
// 默认的JSON解析
String body = response.getBody();
return objectMapper.readValue(body, responseType);
}
// 钩子方法3:后置处理
protected void postHandle(HttpRequest request, HttpResponse response, long startTime) {
long duration = System.currentTimeMillis() - startTime;
log.info("请求完成: {} {} - {}ms",
request.getMethod(),
request.getUri(),
duration);
}
// 钩子方法4:异常处理
protected <T> T handleException(HttpRequest request, Exception e, long startTime) {
long duration = System.currentTimeMillis() - startTime;
log.error("请求失败: {} {} - {}ms",
request.getMethod(),
request.getUri(),
duration, e);
throw new AiException("HTTP请求失败", e);
}
}
// 具体实现
@Component
public class OkHttpClientImpl extends AbstractHttpClient {
private final OkHttpClient client = new OkHttpClient();
@Override
protected HttpResponse doExecute(HttpRequest request) {
// 使用 OkHttp 实现具体逻辑
okhttp3.Request okRequest = convertRequest(request);
okhttp3.Response response = client.newCall(okRequest).execute();
return convertResponse(response);
}
// 还可以重写钩子方法
@Override
protected void preHandle(HttpRequest request) {
super.preHandle(request);
// 添加认证头
request.addHeader("Authorization", "Bearer " + getApiKey());
}
}
🔄 四、响应式编程支持
5. 学习点:Flux 流式处理
// 案例:流式响应处理
@Service
public class StreamAiService {
public Flux<AiChunk> streamChat(String message) {
return Flux.create(sink -> {
// 模拟流式返回
String[] words = message.split(" ");
for (int i = 0; i < words.length; i++) {
// 每 500ms 发送一个词
try {
Thread.sleep(500);
} catch (InterruptedException e) {
sink.error(e);
return;
}
AiChunk chunk = AiChunk.builder()
.index(i)
.content(words[i] + " ")
.isLast(i == words.length - 1)
.build();
sink.next(chunk);
}
sink.complete();
});
}
// WebFlux 控制器
@RestController
public class StreamController {
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<AiChunk>> streamChat(@RequestParam String message) {
return streamAiService.streamChat(message)
.map(chunk -> ServerSentEvent.builder(chunk)
.event("message")
.id(String.valueOf(chunk.getIndex()))
.build())
.doOnComplete(() -> log.info("流式响应完成"))
.doOnError(e -> log.error("流式响应错误", e));
}
}
}
前端使用:
// 前端接收流式响应
const eventSource = new EventSource('/chat/stream?message=你好世界');
eventSource.onmessage = (event) => {
const chunk = JSON.parse(event.data);
console.log('收到片段:', chunk.content);
if (chunk.isLast) {
console.log('流结束');
eventSource.close();
}
};
📦 五、向量数据库的优雅实现
6. 学习点:统一的存储接口
// 案例:向量存储抽象
public interface VectorStore {
// 添加文档
void add(List<Document> documents);
// 相似度搜索
List<SearchResult> similaritySearch(String query, int topK);
// 带过滤的搜索
List<SearchResult> similaritySearch(SearchRequest request);
// 删除文档
void delete(List<String> ids);
}
// 内存实现
@Component
@ConditionalOnProperty(name = "vector.store.type", havingValue = "memory", matchIfMissing = true)
public class InMemoryVectorStore implements VectorStore {
private final Map<String, VectorDocument> store = new ConcurrentHashMap<>();
private final EmbeddingClient embeddingClient;
@Override
public void add(List<Document> documents) {
// 1. 向量化
List<String> texts = documents.stream()
.map(Document::getContent)
.collect(toList());
List<Embedding> embeddings = embeddingClient.embed(texts);
// 2. 存储
for (int i = 0; i < documents.size(); i++) {
VectorDocument vd = new VectorDocument(
UUID.randomUUID().toString(),
documents.get(i),
embeddings.get(i)
);
store.put(vd.getId(), vd);
}
}
@Override
public List<SearchResult> similaritySearch(String query, int topK) {
// 1. 向量化查询
Embedding queryEmbedding = embeddingClient.embed(query);
// 2. 计算相似度
List<SearchResult> results = store.values().stream()
.map(vd -> new SearchResult(
vd,
cosineSimilarity(queryEmbedding, vd.getEmbedding())
))
.sorted(Comparator.comparing(SearchResult::getScore).reversed())
.limit(topK)
.collect(toList());
return results;
}
}
// Redis 实现
@Component
@ConditionalOnProperty(name = "vector.store.type", havingValue = "redis")
public class RedisVectorStore implements VectorStore {
private final RedisTemplate<String, byte[]> redisTemplate;
private final StringRedisTemplate stringRedisTemplate;
@Override
public void add(List<Document> documents) {
// 使用 Redis 的向量搜索功能
Map<byte[], byte[]> vectorMap = new HashMap<>();
for (Document doc : documents) {
String id = UUID.randomUUID().toString();
Embedding embedding = embeddingClient.embed(doc.getContent());
// 存储向量
vectorMap.put(id.getBytes(), serialize(embedding));
// 存储元数据
Map<String, String> metadata = new HashMap<>();
metadata.put("content", doc.getContent());
metadata.putAll(doc.getMetadata());
stringRedisTemplate.opsForHash().putAll("doc:" + id, metadata);
}
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
vectorMap.forEach((id, vector) -> {
connection.set(id, vector);
});
return null;
});
}
}
🔧 六、灵活的拦截器机制
7. 学习点:责任链模式
// 案例:可插拔的拦截器
public interface AiInterceptor {
// 前置处理
default boolean preHandle(AiRequest request) {
return true;
}
// 后置处理
default void postHandle(AiRequest request, AiResponse response) {
}
// 完成处理
default void afterCompletion(AiRequest request, AiResponse response, Exception ex) {
}
}
// 拦截器链
@Component
public class AiInterceptorChain {
private final List<AiInterceptor> interceptors;
public AiResponse execute(AiRequest request) {
// 1. 前置处理
for (AiInterceptor interceptor : interceptors) {
if (!interceptor.preHandle(request)) {
throw new AiException("请求被拦截器拒绝");
}
}
AiResponse response = null;
Exception exception = null;
try {
// 2. 执行实际逻辑
response = doExecute(request);
// 3. 后置处理
for (AiInterceptor interceptor : interceptors) {
interceptor.postHandle(request, response);
}
} catch (Exception e) {
exception = e;
throw e;
} finally {
// 4. 完成处理
for (AiInterceptor interceptor : interceptors) {
interceptor.afterCompletion(request, response, exception);
}
}
return response;
}
}
// 具体拦截器实现
@Component
@Order(1) // 执行顺序
public class LoggingInterceptor implements AiInterceptor {
@Override
public boolean preHandle(AiRequest request) {
log.info("AI请求开始: {}", request);
return true;
}
@Override
public void afterCompletion(AiRequest request, AiResponse response, Exception ex) {
if (ex != null) {
log.error("AI请求失败: {}", request, ex);
} else {
log.info("AI请求成功: {}", request);
}
}
}
@Component
@Order(2)
public class RateLimitInterceptor implements AiInterceptor {
private final RateLimiter rateLimiter = RateLimiter.create(10); // 10 QPS
@Override
public boolean preHandle(AiRequest request) {
if (!rateLimiter.tryAcquire()) {
log.warn("请求被限流: {}", request);
return false;
}
return true;
}
}
@Component
@Order(3)
public class MetricsInterceptor implements AiInterceptor {
private final MeterRegistry meterRegistry;
@Override
public void postHandle(AiRequest request, AiResponse response) {
// 记录指标
meterRegistry.counter("ai.requests.total").increment();
}
}
🎯 七、工厂模式的灵活应用
8. 学习点:工厂模式创建客户端
// 案例:智能客户端工厂
@Component
public class AiClientFactory {
private final Map<String, Supplier<AiClient>> clientSuppliers = new HashMap<>();
public AiClientFactory() {
// 注册所有支持的客户端
registerClient("openai", this::createOpenAiClient);
registerClient("azure", this::createAzureClient);
registerClient("local", this::createLocalClient);
}
public AiClient getClient(String provider) {
Supplier<AiClient> supplier = clientSuppliers.get(provider);
if (supplier == null) {
throw new IllegalArgumentException("不支持的AI提供商: " + provider);
}
return supplier.get();
}
public AiClient getClientForModel(String model) {
// 根据模型名称智能选择
if (model.startsWith("gpt-")) {
return getClient("openai");
} else if (model.contains("azure")) {
return getClient("azure");
} else {
return getClient("local");
}
}
private void registerClient(String provider, Supplier<AiClient> supplier) {
clientSuppliers.put(provider, supplier);
}
private AiClient createOpenAiClient() {
return new OpenAiClient(
environment.getProperty("spring.ai.openai.api-key"),
environment.getProperty("spring.ai.openai.base-url", "https://api.openai.com/v1")
);
}
// 动态注册新客户端
public void registerCustomClient(String provider, Supplier<AiClient> supplier) {
clientSuppliers.put(provider, supplier);
}
}
📁 八、完整学习项目示例
项目结构:
spring-ai-learning/
├── src/main/java/com/example/ai/
│ ├── annotation/
│ │ └── EnableAiClient.java
│ ├── config/
│ │ ├── AiAutoConfiguration.java
│ │ ├── AiProperties.java
│ │ └── Condition/
│ │ └── OnAiProviderCondition.java
│ ├── client/
│ │ ├── AiClient.java
│ │ ├── ChatClient.java
│ │ ├── OpenAiClient.java
│ │ └── AzureAiClient.java
│ ├── interceptor/
│ │ ├── AiInterceptor.java
│ │ ├── LoggingInterceptor.java
│ │ └── MetricsInterceptor.java
│ ├── model/
│ │ ├── AiRequest.java
│ │ ├── AiResponse.java
│ │ └── AiChunk.java
│ ├── vector/
│ │ ├── VectorStore.java
│ │ ├── InMemoryVectorStore.java
│ │ └── RedisVectorStore.java
│ └── SpringAiLearningApplication.java
├── src/main/resources/
│ └── application.yml
└── pom.xml
完整示例代码:启动类
@SpringBootApplication
@EnableAiClient
@Slf4j
public class SpringAiLearningApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiLearningApplication.class, args);
}
@Bean
public CommandLineRunner demo(AiClient aiClient, VectorStore vectorStore) {
return args -> {
// 1. 测试聊天
String response = aiClient.chat("你好,Spring AI!");
log.info("AI回复: {}", response);
// 2. 测试向量存储
Document doc = new Document("Spring AI 是一个强大的框架");
vectorStore.add(List.of(doc));
List<SearchResult> results = vectorStore.similaritySearch("Spring AI", 5);
log.info("搜索结果: {}", results);
// 3. 测试流式响应
aiClient.streamChat("请介绍Java")
.subscribe(chunk -> {
log.info("收到片段: {}", chunk.getContent());
});
};
}
}
📚 学习建议
学习路径:
- 先学设计模式:模板方法、工厂、责任链
- 再学 Spring 特性:条件装配、属性绑定
- 最后学 AI 集成:向量计算、流式响应
实践建议:
- 从最小案例开始,逐步增加复杂度
- 每个学习点都写一个测试用例
- 尝试扩展框架,添加自己的实现
- 对比不同实现方案的优缺点
调试技巧:
// 开启详细日志
@Configuration
public class DebugConfig {
@Bean
public AiInterceptor debugInterceptor() {
return new AiInterceptor() {
@Override
public boolean preHandle(AiRequest request) {
System.out.println("=== 请求开始 ===");
System.out.println("请求ID: " + request.getId());
System.out.println("请求内容: " + request.getContent());
return true;
}
@Override
public void afterCompletion(AiRequest request, AiResponse response, Exception ex) {
System.out.println("=== 请求结束 ===");
if (response != null) {
System.out.println("响应内容: " + response.getContent());
}
if (ex != null) {
System.out.println("异常: " + ex.getMessage());
}
}
};
}
}
这些案例展示了 Spring AI 源码中最值得学习的设计思想和实现技巧。通过实际编写这些代码,你能深入理解 Spring AI 的精髓,并应用到自己的项目中。
440

被折叠的 条评论
为什么被折叠?



