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

2025博客之星年度评选已开启 10w+人浏览 2.5k人参与

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());
                });
        };
    }
}

📚 学习建议

学习路径:

  1. 先学设计模式:模板方法、工厂、责任链
  2. 再学 Spring 特性:条件装配、属性绑定
  3. 最后学 AI 集成:向量计算、流式响应

实践建议:

  1. 从最小案例开始,逐步增加复杂度
  2. 每个学习点都写一个测试用例
  3. 尝试扩展框架,添加自己的实现
  4. 对比不同实现方案的优缺点

调试技巧:

// 开启详细日志
@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 的精髓,并应用到自己的项目中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder_Boy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值