作为Java开发者,我们终于迎来了属于自己的AI时代。Spring AI 2.0的发布彻底改变了Java生态在大模型应用领域的尴尬处境,让Java开发者能够用熟悉的Spring方式构建企业级AI应用。本文将带你深入探索Spring AI 2.0的核心特性,并手把手教你如何零代码接入大模型、构建RAG知识库,最终实现生产级部署。
Spring AI 2.0采用了分层架构设计,从下到上分为四层:
基础设施层:提供对主流AI服务的连接支持,包括OpenAI、Anthropic、Google Gemini等云端服务,以及Ollama等本地模型运行环境
核心抽象层:定义了统一的API接口和模型抽象,包括:
企业功能层:提供RAG、语义缓存、结构化输出等企业级特性
应用集成层:与Spring生态深度集成,支持自动配置、健康检查、监控等
这种架构设计使得开发者无需关心底层实现细节,只需通过简单的配置就能切换不同的AI服务提供商。
开发环境推荐配置:
软件依赖:
使用Spring Initializr创建项目时,建议选择以下依赖:
对于生产环境,推荐使用Gradle构建工具,配置如下:
gradle复制plugins {
id 'java'
id 'org.springframework.boot' version '4.0.0'
id 'io.spring.dependency-management' version '1.1.0'
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.ai:spring-ai-bom:2.0.0-M2'
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
implementation 'org.springframework.ai:spring-ai-pgvector-store'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
注意:由于Spring AI 2.0目前仍处于里程碑版本阶段,建议在生产环境使用前进行全面测试。对于关键业务系统,可以考虑等待正式版发布。
ChatClient是Spring AI 2.0最核心的接口,其设计遵循了Spring的"约定优于配置"原则。它提供了两种调用方式:
在application.yml中,我们可以对ChatClient进行深度定制:
yaml复制spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4-turbo
temperature: 0.7
top-p: 0.9
max-tokens: 1000
presence-penalty: 0.5
frequency-penalty: 0.5
response-format:
type: json_object
这些参数的含义如下:
Spring AI 2.0的结构化输出功能非常强大,支持多种复杂场景:
定义复杂的数据结构:
java复制public record Author(String name, String email) {}
public record Article(
String title,
String content,
Author author,
List<String> tags,
LocalDate publishDate
) {}
调用方式:
java复制@GetMapping("/generate-article")
public Article generateArticle(@RequestParam String topic) {
return chatClient.prompt()
.system("你是一位技术专家,请按照要求生成技术文章")
.user("请生成一篇关于" + topic + "的技术文章,包含标题、内容、作者信息和标签")
.call()
.entity(Article.class);
}
对于返回数组的情况,可以使用ParameterizedTypeReference:
java复制@GetMapping("/generate-tags")
public List<String> generateTags(@RequestParam String topic) {
return chatClient.prompt()
.user("为关于" + topic + "的文章生成5个合适的标签")
.call()
.entity(new ParameterizedTypeReference<List<String>>() {});
}
Spring AI 2.0支持多种向量存储方案,各有优缺点:
| 存储类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| PGVector | 支持完整SQL查询,ACID事务 | 需要PostgreSQL扩展 | 企业级生产环境 |
| Redis | 高性能,低延迟 | 内存消耗大 | 高并发查询场景 |
| Chroma | 轻量级,易于部署 | 功能相对简单 | 开发测试环境 |
| Weaviate | 支持混合搜索 | 需要额外维护 | 复杂搜索需求 |
生产环境推荐使用PGVector,配置示例:
yaml复制spring:
datasource:
url: jdbc:postgresql://localhost:5432/vectordb
username: postgres
password: postgres
ai:
vectorstore:
pgvector:
dimensions: 1536
index-type: ivfflat
lists: 100
initialize-schema: true
使用递归分割策略处理复杂文档:
java复制@Bean
public TextSplitter documentSplitter() {
return new RecursiveCharacterTextSplitter(
1000, // 块大小
200, // 重叠大小
List.of(
new MarkdownHeaderTextSplitter(), // 按Markdown标题分割
new SentenceTextSplitter(), // 按句子分割
new TokenTextSplitter() // 按token分割
)
);
}
为文档片段添加上下文信息:
java复制public void ingestDocument(Resource resource) {
List<Document> documents = pdfReader.read(resource);
documents.forEach(doc -> {
// 添加文档来源信息
doc.getMetadata().put("source", resource.getFilename());
doc.getMetadata().put("ingest_time", Instant.now().toString());
// 提取并添加文档标题
String title = extractTitle(doc.getContent());
doc.getMetadata().put("title", title);
});
List<Document> chunks = splitter.split(documents);
vectorStore.add(chunks);
}
结合语义搜索和关键词搜索:
java复制@Bean
public DocumentRetriever hybridRetriever(VectorStore vectorStore) {
// 语义检索组件
VectorStoreDocumentRetriever vectorRetriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.7)
.topK(2)
.build();
// 关键词检索组件
KeywordSearchDocumentRetriever keywordRetriever = KeywordSearchDocumentRetriever.builder()
.index(vectorStore)
.topK(2)
.build();
// 混合检索器
return new HybridDocumentRetriever(vectorRetriever, keywordRetriever);
}
对检索结果进行过滤和排序:
java复制@Bean
public RetrievalAugmentationAdvisor ragAdvisor(DocumentRetriever retriever) {
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(retriever)
.postProcessor(docs -> {
// 过滤低质量结果
return docs.stream()
.filter(doc -> !doc.getMetadata().containsKey("sensitive"))
.sorted(Comparator.comparingDouble(doc -> (Double)doc.getMetadata().get("confidence")))
.limit(3)
.collect(Collectors.toList());
})
.build();
}
Java 21虚拟线程的优化配置:
yaml复制spring:
threads:
virtual:
enabled: true
scheduler:
parallelism: 1
max-pool-size: 1000
针对AI API调用的连接池配置:
yaml复制spring:
ai:
openai:
connection:
pool:
max-size: 100
idle-timeout: 30s
keep-alive: 60s
max-life-time: 10m
配置Micrometer指标:
java复制@Bean
public MeterRegistryCustomizer<MeterRegistry> aiMetrics() {
return registry -> {
registry.config().meterFilter(
new MeterFilter() {
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
if (id.getName().startsWith("spring.ai")) {
return DistributionStatisticConfig.builder()
.percentiles(0.5, 0.95, 0.99)
.build()
.merge(config);
}
return config;
}
}
);
};
}
AI调用的详细日志配置:
yaml复制logging:
level:
org.springframework.ai: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n"
防止Prompt注入攻击:
java复制@RestControllerAdvice
public class AiControllerAdvice {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
@ExceptionHandler(InvalidPromptException.class)
public ResponseEntity<String> handleInvalidPrompt(InvalidPromptException ex) {
return ResponseEntity.badRequest().body("Invalid prompt: " + ex.getMessage());
}
public static class PromptValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return String.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
String prompt = (String) target;
if (prompt.contains("system") || prompt.contains("root")) {
errors.rejectValue("prompt", "forbidden.keyword", "Prompt contains forbidden keywords");
}
}
}
}
全局API限流配置:
java复制@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/ai/**").hasRole("USER")
.anyExchange().permitAll()
)
.httpBasic(withDefaults())
.addFilterAt(rateLimitFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.build();
}
@Bean
public RateLimiter rateLimiter() {
return RateLimiter.create(100); // 每秒100个请求
}
@Bean
public WebFilter rateLimitFilter() {
return (exchange, chain) -> {
if (rateLimiter().tryAcquire()) {
return chain.filter(exchange);
}
return Mono.fromRunnable(() -> {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders().set("Retry-After", "1");
});
};
}
}
实现智能模型路由:
java复制@Bean
public ModelRouter modelRouter() {
return ModelRouter.builder()
.route(
RoutePredicate.contentContains("代码"),
RouteDestination.to("openai").withOptions(
OpenAiChatOptions.builder().model("gpt-4-code").build()
)
)
.route(
RoutePredicate.contentLength(100),
RouteDestination.to("ollama").withOptions(
OllamaOptions.builder().model("llama3").build()
)
)
.defaultRoute(
RouteDestination.to("anthropic").withOptions(
AnthropicChatOptions.builder().model("claude-3").build()
)
)
.build();
}
扩展AI能力到业务系统:
java复制@Bean
public FunctionCallback inventoryCheckFunction() {
return FunctionCallback.builder()
.function("checkInventory", (InventoryRequest request) -> {
// 调用库存服务
return inventoryService.checkStock(
request.productId(),
request.quantity()
);
})
.description("检查产品库存情况")
.inputType(InventoryRequest.class)
.build();
}
public record InventoryRequest(String productId, int quantity) {}
实现多轮对话上下文:
java复制@Service
public class ConversationService {
private final Map<String, List<Message>> conversations = new ConcurrentHashMap<>();
public String continueConversation(String conversationId, String userMessage) {
List<Message> messages = conversations.computeIfAbsent(
conversationId,
id -> new ArrayList<>()
);
messages.add(new Message("user", userMessage));
String response = chatClient.prompt()
.messages(messages)
.call()
.content();
messages.add(new Message("assistant", response));
// 限制对话历史长度
if (messages.size() > 10) {
messages = messages.subList(messages.size() - 10, messages.size());
conversations.put(conversationId, messages);
}
return response;
}
}
在实际项目中,我们可以根据业务需求进一步扩展这些基础功能。Spring AI 2.0的强大之处在于它的可扩展性,几乎每个组件都可以通过实现特定接口来进行定制。