你如果是个Java开发者,可能心里一直在犯嘀咕:“难道做RAG必须学Python?我Spring Boot的祖传手艺就不能用了?”

当然不是。Java生态在2025-2026年已经补上了RAG的课——Spring AI 2.0正式发布了VectorStore和RAG Advisor,LangChain4j 1.1.0也提供了完整的EmbeddingStore和RetrievalAugmentor。两个框架都能让你用纯Java代码把RAG跑起来。

这篇我把前10篇的Python概念映射到Java实现,对比Spring AI和LangChain4j两种方案的差异,帮你选对框架,少走弯路。

先理清一个关系:Python RAG概念到Java的映射

你可能在Python文章里见过这些组件,到了Java里叫什么?先看一张映射表:

| Python(LangChain)

|

Spring AI 2.0

|

LangChain4j 1.1.0

|

作用

TextSplitter

|

DocumentTransformer

|

DocumentSplitter

|

文档切分

| |

Embeddings

|

EmbeddingModel

|

EmbeddingModel

|

文本转向量

| |

VectorStore

|

VectorStore

|

EmbeddingStore

|

向量存储和检索

| |

Retriever

|

VectorStore.similaritySearch

|

EmbeddingStore.search

|

检索接口

| |

RetrievalQA

|

QuestionAnswerAdvisor

|

AiServices + RAG

|

检索+生成组合

| |

BM25Retriever

|

无内置

|

无内置

|

需自己实现或接Elasticsearch

| |

ContextualCompressionRetriever

|

无内置

|

无内置

|

需手动实现

|

你看出来没有?Java框架的抽象层次跟LangChain基本对齐了,但高级RAG(混合检索、Rerank、查询改写)需要你用更底层的能力自己拼。这也符合Java开发者的习惯——给你积木,你自己搭。

Java类比:就像Spring Data JPA给你Repository接口,MyBatis给你Mapper接口,两个框架都给你RAG的核心抽象(VectorStore/EmbeddingStore),但具体实现细节你需要自己控制。Spring AI的思路是"约定优于配置"(像Spring Boot的auto-configuration),LangChain4j的思路是"显式组合"(像Builder模式)。

Spring AI 2.0:Spring Boot原生RAG方案

依赖配置

Spring AI 2.0的Maven依赖,注意artifactId从milestone到GA有变化:

<span><span leaf="">&lt;</span><span><span leaf="">dependencies</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- Spring AI核心:OpenAI模型(含Chat和Embedding) --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.springframework.ai</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">spring-ai-starter-model-openai</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- 向量库:选一个就行 --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.springframework.ai</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">spring-ai-starter-vector-store-pgvector</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- 文档读取:PDF --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.springframework.ai</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">spring-ai-pdf-document-reader</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- 文档读取:Tika(Word、HTML等) --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.springframework.ai</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">spring-ai-tika-document-reader</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span><span leaf="">&lt;/</span><span><span leaf="">dependencies</span></span><span leaf="">&gt;</span></span><br>

配置文件:

<span><span leaf="">spring:</span></span><br><span leaf="">&nbsp;&nbsp;</span><span><span leaf="">ai:</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">openai:</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">api-key:</span></span><span leaf="">&nbsp;</span><span><span leaf="">${OPENAI_API_KEY}</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">chat:</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">options:</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">model:</span></span><span leaf="">&nbsp;</span><span><span leaf="">gpt-4o</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">embedding:</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">options:</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">model:</span></span><span leaf="">&nbsp;</span><span><span leaf="">text-embedding-3-small</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">vectorstore:</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">pgvector:</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">dimensions:</span></span><span leaf="">&nbsp;</span><span><span leaf="">1536</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">distance-type:</span></span><span leaf="">&nbsp;</span><span><span leaf="">cosine_distance</span></span><br>

阶段1:文档ETL(提取→转换→加载)

Spring AI把文档处理抽象成ETL流水线——跟你在Java里用的Spring Batch一个思路:

<span><span leaf="">@Configuration</span></span><br><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">RagConfig</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">@Bean</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;DocumentReader&nbsp;</span><span><span leaf="">pdfReader</span></span><span><span leaf="">()</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">// 读取PDF文档,返回Document列表</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">new</span></span><span leaf="">&nbsp;PagePdfDocumentReader(</span><span><span leaf="">"classpath:company-handbook.pdf"</span></span><span leaf="">);</span><br><span leaf="">&nbsp; &nbsp; }</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">@Bean</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;DocumentTransformer&nbsp;</span><span><span leaf="">documentSplitter</span></span><span><span leaf="">()</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">// 递归切分,token大小800,重叠100</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">// 对应02篇讲的"递归字符切分"策略</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;TokenTextSplitter.builder()</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .chunkSize(</span><span><span leaf="">800</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .minChunkSizeChars(</span><span><span leaf="">350</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .minChunkLengthToEmbed(</span><span><span leaf="">5</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .maxNumChunks(</span><span><span leaf="">10000</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .keepSeparator(</span><span><span leaf="">true</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .build();</span><br><span leaf="">&nbsp; &nbsp; }</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">@Bean</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;VectorStore&nbsp;</span><span><span leaf="">vectorStore</span></span><span><span leaf="">(EmbeddingModel embeddingModel, JdbcTemplate jdbcTemplate)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;PgVectorStore.builder(jdbcTemplate, embeddingModel)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .dimensions(</span><span><span leaf="">1536</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .distanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .table_name(</span><span><span leaf="">"rag_vectors"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .build();</span><br><span leaf="">&nbsp; &nbsp; }</span><br><span leaf="">}</span><br>

ETL执行——把PDF切分后存入向量库:

<span><span leaf="">@Service</span></span><br><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">DocumentIngestionService</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">private</span></span><span leaf="">&nbsp;</span><span><span leaf="">final</span></span><span leaf="">&nbsp;DocumentReader documentReader;</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">private</span></span><span leaf="">&nbsp;</span><span><span leaf="">final</span></span><span leaf="">&nbsp;DocumentTransformer documentTransformer;</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">private</span></span><span leaf="">&nbsp;</span><span><span leaf="">final</span></span><span leaf="">&nbsp;VectorStore vectorStore;</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span leaf="">DocumentIngestionService</span></span><span><span leaf="">(DocumentReader documentReader,</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;DocumentTransformer documentTransformer,</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VectorStore vectorStore)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">this</span></span><span leaf="">.documentReader = documentReader;</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">this</span></span><span leaf="">.documentTransformer = documentTransformer;</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">this</span></span><span leaf="">.vectorStore = vectorStore;</span><br><span leaf="">&nbsp; &nbsp; }</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span leaf="">void</span></span><span leaf="">&nbsp;</span><span><span leaf="">ingestPdf</span></span><span><span leaf="">(String pdfPath)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">// 1. 读取PDF → Document列表</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; List&lt;Document&gt; docs =&nbsp;</span><span><span leaf="">new</span></span><span leaf="">&nbsp;PagePdfDocumentReader(pdfPath).get();</span><br><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">// 2. 切分文档 → 更小的Document列表</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; List&lt;Document&gt; chunks = documentTransformer.apply(docs);</span><br><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">// 3. 批量向量化并存储(VectorStore内部调EmbeddingModel)</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; vectorStore.add(chunks);</span><br><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(</span><span><span leaf="">"导入完成,共"</span></span><span leaf="">&nbsp;+ chunks.size() +&nbsp;</span><span><span leaf="">"个chunk"</span></span><span leaf="">);</span><br><span leaf="">&nbsp; &nbsp; }</span><br><span leaf="">}</span><br>

Java类比:这段代码的结构跟Spring Batch的ItemReader→ItemProcessor→ItemWriter一模一样。DocumentReader就是ItemReader,DocumentTransformer就是ItemProcessor,VectorStore就是ItemWriter。

阶段2+3:检索+生成

Spring AI的RAG核心是QuestionAnswerAdvisor——一个Advisor,自动在用户提问时检索相关文档并拼入提示:

<span><span leaf="">@RestController</span></span><br><span><span leaf="">@RequestMapping</span></span><span leaf="">(</span><span><span leaf="">"/api/rag"</span></span><span leaf="">)</span><br><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">RagController</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">private</span></span><span leaf="">&nbsp;</span><span><span leaf="">final</span></span><span leaf="">&nbsp;ChatClient chatClient;</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span leaf="">RagController</span></span><span><span leaf="">(ChatClient.Builder builder, VectorStore vectorStore)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">this</span></span><span leaf="">.chatClient = builder</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .defaultAdvisors(</span><span><span leaf="">new</span></span><span leaf="">&nbsp;QuestionAnswerAdvisor(vectorStore))</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .build();</span><br><span leaf="">&nbsp; &nbsp; }</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">@PostMapping</span></span><span leaf="">(</span><span><span leaf="">"/chat"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;String&nbsp;</span><span><span leaf="">chat</span></span><span><span leaf="">(@RequestBody String question)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;chatClient.prompt()</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .user(question)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .call()</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .content();</span><br><span leaf="">&nbsp; &nbsp; }</span><br><span leaf="">}</span><br>

就这么几行,RAG就跑起来了。QuestionAnswerAdvisor做了什么?它拦截用户的问题,先调vectorStore做相似检索,再把检索到的文档拼到系统提示里,最后发给LLM。

进阶:自定义检索策略

默认的QuestionAnswerAdvisor只做简单的相似检索。你想做02篇讲的混合检索(BM25+向量),需要自定义SearchRequest:

<span><span leaf="">// 自定义检索:topK=5,相似度阈值0.7</span></span><br><span leaf="">SearchRequest searchRequest = SearchRequest.builder()</span><br><span leaf="">&nbsp; &nbsp; .query(question)</span><br><span leaf="">&nbsp; &nbsp; .topK(</span><span><span leaf="">5</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .similarityThreshold(</span><span><span leaf="">0.7</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .filterExpression(</span><span><span leaf="">"source == 'company-handbook'"</span></span><span leaf="">) &nbsp;</span><span><span leaf="">// 元数据过滤</span></span><br><span leaf="">&nbsp; &nbsp; .build();</span><br><br><span><span leaf="">// 手动检索</span></span><br><span leaf="">List&lt;Document&gt; docs = vectorStore.similaritySearch(searchRequest);</span><br><br><span><span leaf="">// 把检索结果拼入提示</span></span><br><span leaf="">String context = docs.stream()</span><br><span leaf="">&nbsp; &nbsp; .map(Document::getText)</span><br><span leaf="">&nbsp; &nbsp; .collect(Collectors.joining(</span><span><span leaf="">"\n\n"</span></span><span leaf="">));</span><br><br><span leaf="">String response = chatClient.prompt()</span><br><span leaf="">&nbsp; &nbsp; .system(</span><span><span leaf="">"基于以下文档回答用户问题,如果文档中没有答案就说'未找到相关内容':\n\n"</span></span><span leaf="">&nbsp;+ context)</span><br><span leaf="">&nbsp; &nbsp; .user(question)</span><br><span leaf="">&nbsp; &nbsp; .call()</span><br><span leaf="">&nbsp; &nbsp; .content();</span><br>

Java类比:这就是Spring里常用的"模板方法模式"——框架给你默认实现,你不满意就覆盖某个步骤。QuestionAnswerAdvisor是默认模板,手动检索+自定义提示是你覆盖了模板。

LangChain4j 1.1.0:更灵活的RAG方案

LangChain4j的RAG抽象更细致,适合需要精确控制每一步的场景。

依赖配置

<span><span leaf="">&lt;</span><span><span leaf="">dependencies</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- LangChain4j核心 --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">dev.langchain4j</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">langchain4j</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf="">1.0.0</span><span><span leaf="">&lt;/</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- OpenAI集成 --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">dev.langchain4j</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">langchain4j-open-ai</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf="">1.0.0</span><span><span leaf="">&lt;/</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- 向量库:选一个 --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">dev.langchain4j</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">langchain4j-pgvector</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf="">1.0.0</span><span><span leaf="">&lt;/</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;!-- 文档加载器 --&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">dev.langchain4j</span><span><span leaf="">&lt;/</span><span><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">langchain4j-document-loaders</span><span><span leaf="">&lt;/</span><span><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf="">1.0.0</span><span><span leaf="">&lt;/</span><span><span leaf="">version</span></span><span leaf="">&gt;</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">&lt;/</span><span><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><br><span><span leaf="">&lt;/</span><span><span leaf="">dependencies</span></span><span leaf="">&gt;</span></span><br>

文档切分与向量化

<span><span leaf="">// 1. 加载文档</span></span><br><span leaf="">Document document = FileSystemDocumentLoader.loadDocument(</span><span><span leaf="">"company-handbook.pdf"</span></span><span leaf="">);</span><br><br><span><span leaf="">// 2. 切分——对应02篇的递归切分策略</span></span><br><span leaf="">DocumentSplitter splitter = DocumentSplitters.recursive(</span><span><span leaf="">800</span></span><span leaf="">,&nbsp;</span><span><span leaf="">100</span></span><span leaf="">);</span><br><span leaf="">List&lt;TextSegment&gt; segments = splitter.split(document);</span><br><br><span><span leaf="">// 3. 向量化</span></span><br><span leaf="">OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()</span><br><span leaf="">&nbsp; &nbsp; .apiKey(System.getenv(</span><span><span leaf="">"OPENAI_API_KEY"</span></span><span leaf="">))</span><br><span leaf="">&nbsp; &nbsp; .modelName(</span><span><span leaf="">"text-embedding-3-small"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .build();</span><br><br><span leaf="">Response&lt;List&lt;Embedding&gt;&gt; embeddings = embeddingModel.embedAll(</span><br><span leaf="">&nbsp; &nbsp; segments.stream().map(TextSegment::text).toList()</span><br><span leaf="">);</span><br><br><span><span leaf="">// 4. 存入向量库</span></span><br><span leaf="">PgVectorEmbeddingStore embeddingStore = PgVectorEmbeddingStore.builder()</span><br><span leaf="">&nbsp; &nbsp; .host(</span><span><span leaf="">"localhost"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .port(</span><span><span leaf="">5432</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .database(</span><span><span leaf="">"rag"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .table(</span><span><span leaf="">"documents"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .dimension(</span><span><span leaf="">1536</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .build();</span><br><br><span leaf="">embeddingStore.addAll(embeddings.content(), segments);</span><br>

检索+生成:AiServices方式

LangChain4j用AiServices把RAG封装成接口调用——像MyBatis的Mapper代理一样优雅:

<span><span leaf="">// 定义接口(类似MyBatis的Mapper)</span></span><br><span><span><span leaf="">interface</span></span><span leaf="">&nbsp;</span><span><span leaf="">KnowledgeBaseAssistant</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">@SystemMessage</span></span><span leaf="">(</span><span><span leaf="">"你是一个企业知识库助手,基于检索到的文档回答用户问题。"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">String&nbsp;</span><span><span leaf="">answer</span></span><span><span leaf="">(@UserMessage String question)</span></span></span><span leaf="">;</span><br><span leaf="">}</span><br><br><span><span leaf="">// 配置RAG增强器</span></span><br><span leaf="">RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder()</span><br><span leaf="">&nbsp; &nbsp; .queryTransformer(QueryTransformer.builder()</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; .queryRouter(QueryRouter.builder()</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .embeddingStore(embeddingStore)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .embeddingModel(embeddingModel)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .maxResults(</span><span><span leaf="">5</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .minScore(</span><span><span leaf="">0.7</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .build())</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; .build())</span><br><span leaf="">&nbsp; &nbsp; .build();</span><br><br><span><span leaf="">// 创建代理实例</span></span><br><span leaf="">KnowledgeBaseAssistant assistant = AiServices.builder(KnowledgeBaseAssistant</span><span><span leaf="">.</span><span><span leaf="">class</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .</span><span><span leaf="">chatLanguageModel</span></span><span leaf="">(</span><span><span leaf="">chatModel</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .</span><span><span leaf="">retrievalAugmentor</span></span><span leaf="">(</span><span><span leaf="">augmentor</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; .</span><span><span leaf="">build</span></span><span leaf="">()</span></span><span leaf="">;</span><br><br><span><span leaf="">// 使用——就像调用普通Java接口</span></span><br><span leaf="">String answer = assistant.answer(</span><span><span leaf="">"公司的报销流程是什么?"</span></span><span leaf="">);</span><br>

Java类比:AiServices的机制跟MyBatis的@MapperProxy一模一样。你定义接口+注解,框架用动态代理在背后注入RAG逻辑。你写的是接口,框架负责检索、拼提示、调模型、解析结果。

两个框架怎么选?

| 维度

|

Spring AI 2.0

|

LangChain4j 1.1.0

与Spring Boot集成

|

原生(auto-configuration)

|

需手动配置Bean

| |

RAG封装层次

|

Advisor(高层次)

|

AiServices(接口代理)

| |

灵活性

|

中等(Advisor可自定义)

|

高(每步可替换)

| |

向量库支持

|

PgVector/Redis/Chroma等

|

30+种(比Spring AI多)

| |

学习曲线

|

Spring开发者上手快

|

需理解LangChain4j的抽象

| |

社区活跃度

|

Spring生态加持,增长快

|

独立项目,社区活跃

| |

适合场景

|

Spring Boot项目

|

独立Java服务

|

我的建议

  • 你的项目已经用了Spring Boot → Spring AI,无缝集成,配置最少

  • 你需要精确控制RAG每一步 → LangChain4j,灵活度更高

  • 两个都试一下 → 不冲突,可以同时引入

Spring AI vs LangChain4j:代码对比

同样做一个"文档导入+问答"的完整RAG,两个框架的代码量对比:

<span><span leaf="">// === Spring AI方式 ===</span></span><br><span><span leaf="">// 配置(auto-config自动创建ChatClient和VectorStore)</span></span><br><span><span leaf="">// 只需要写Controller</span></span><br><span><span leaf="">@RestController</span></span><br><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">RagController</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">private</span></span><span leaf="">&nbsp;</span><span><span leaf="">final</span></span><span leaf="">&nbsp;ChatClient chatClient;</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span leaf="">RagController</span></span><span><span leaf="">(ChatClient.Builder b, VectorStore vs)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">this</span></span><span leaf="">.chatClient = b.defaultAdvisors(</span><span><span leaf="">new</span></span><span leaf="">&nbsp;QuestionAnswerAdvisor(vs)).build();</span><br><span leaf="">&nbsp; &nbsp; }</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">@PostMapping</span></span><span leaf="">(</span><span><span leaf="">"/ask"</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;String&nbsp;</span><span><span leaf="">ask</span></span><span><span leaf="">(@RequestBody String q)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;chatClient.prompt().user(q).call().content();</span><br><span leaf="">&nbsp; &nbsp; }</span><br><span leaf="">}</span><br><br><span><span leaf="">// === LangChain4j方式 ===</span></span><br><span><span leaf="">// 需要手动配置EmbeddingStore、RetrievalAugmentor、AiServices</span></span><br><span><span leaf="">@Configuration</span></span><br><span><span leaf="">public</span></span><span leaf="">&nbsp;</span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">RagConfig</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">@Bean</span></span><br><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">public</span></span><span leaf="">&nbsp;KnowledgeBaseAssistant&nbsp;</span><span><span leaf="">assistant</span></span><span><span leaf="">(ChatModel model, EmbeddingStore store)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder()</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">// ... 配置检索流程</span></span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .build();</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;AiServices.builder(KnowledgeBaseAssistant</span><span><span leaf="">.</span><span><span leaf="">class</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .</span><span><span leaf="">chatLanguageModel</span></span><span leaf="">(</span><span><span leaf="">model</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .</span><span><span leaf="">retrievalAugmentor</span></span><span leaf="">(</span><span><span leaf="">augmentor</span></span><span leaf="">)</span><br><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .</span><span><span leaf="">build</span></span><span leaf="">()</span></span><span leaf="">;</span><br><span leaf="">&nbsp; &nbsp; }</span><br><span leaf="">}</span><br>

Spring AI的代码更短,因为Advisor把RAG逻辑封装了;LangChain4j的代码更多,但你能看到每一步在做什么。这跟Spring Data JPA vs MyBatis的区别是一样的。

我踩过的坑

  1. Spring AI版本依赖冲突:1.0 GA跟Spring Boot 4.0.x是绑定的,如果你项目用的是3.2.x或3.3.x,要么升级Spring Boot,要么别用Spring AI。版本不匹配会出现auto-configuration失效的诡异问题。

  2. LangChain4j的EmbeddingStore没有BM25:两个框架的向量库都只支持向量相似检索,不支持BM25关键词检索。想做04篇讲的混合检索,需要自己集成Elasticsearch或Lucene,手动做RRF融合。

  3. Rerank模型缺失:Spring AI和LangChain4j都没有内置Rerank模型。你需要在检索后手动调Cohere Rerank API或本地部署bge-reranker。Spring AI可以在Advisor的before阶段插入Rerank逻辑。

  4. 中文切分效果差:TokenTextSplitter基于token切分,中文场景下容易在词语中间断开。建议用递归字符切分,LangChain4j的DocumentSplitters.recursive对中文更友好。

  5. LangChain4j的queryRouter配置复杂:DefaultRetrievalAugmentor的Builder嵌套很深(augmentor→queryTransformer→queryRouter→embeddingStore),容易写错层级。建议先跑官方demo再改。

本篇要点

| 要点

|

内容

Python→Java映射

|

TextSplitter→DocumentTransformer/DocumentSplitter,VectorStore→VectorStore/EmbeddingStore

| |

Spring AI核心

|

QuestionAnswerAdvisor(自动检索+拼提示),配置最少

| |

LangChain4j核心

|

AiServices+RetrievalAugmentor(接口代理+检索增强),灵活度高

| |

选型原则

|

Spring Boot项目选Spring AI,独立服务选LangChain4j

| |

共同短板

|

都不内置BM25和Rerank,需自己集成

| |

版本要求

|

Spring AI需Spring Boot 4.0+,LangChain4j无限制

|

下篇预告

下一篇我们把前10篇的理论知识+这篇的Java实战串起来,从0到1搭建一个完整的企业知识库问答系统——包括文档批量导入、混合检索、Rerank、评估和部署,给你一个能直接跑的项目骨架。


你用Spring AI还是LangChain4j做RAG?体验怎么样?评论区聊聊。

觉得有用就点个在看,下一篇是从0到1搭建企业知识库问答系统——RAG端到端实战。