Spring+LangChain4j工程搭建
Spring+LangChain4j工程搭建
文章目录
- Spring+LangChain4j工程搭建
- 环境准备
- SpringAI & LangChain4j
- Spring AI 与 LangChain4j 的区别、优缺点及适用场景
- 工程搭建
- 项目结构
- 后端架构
- pom 依赖
- API KEY 申请
- DeepSeek
- Qwen-Max
- 配置文件
- 配置读取
- 配置注入
- 定义模型对话接口
- 模型Service
- Controller层
- 测试
环境准备
JDK 21
SpringBoot 3.4.2
Maven 3.6.9
模型API_KEY : DeepSeek / Qwen
Postman
Idea
SpringAI & LangChain4j
Spring AI 与 LangChain4j 的区别、优缺点及适用场景
在 Java 生态中集成大语言模型 (LLM) 已成为企业智能化转型的关键路径。 目前, Spring AI 与 LangChain4j 是两个最受关注的 Java AI 开发框架,它们分别代表了 企业级集成 与 模块化灵活构建 两种不同的技术理念。
一、核心定位与设计理念
| 维度 | Spring AI | LangChain4j |
|---|---|---|
| 定位 | Spring 官方推出的 企业级 AI 集成框架(2025 年 5 月发布 1.0) | LangChain 的 Java 实现,轻量级、模块化的 LLM 应用构建工具 |
| 设计理念 | “约定大于配置”,深度融入 Spring Boot/Cloud 生态,强调 标准化、可观测性与安全性 | “链式组合、按需集成”,强调 灵活性、可扩展性与快速原型开发 |
| 抽象层级 | 提供统一高层抽象(如 ChatClient、TextGenerator),屏蔽底层模型差异 | 提供细粒度组件(如 PromptTemplate、Chain、ToolProvider),支持自由编排 |
二、功能特性对比
- 模型支持与切换能力
- Spring AI
通过ModelProvider接口统一接入 OpenAI、Hugging Face、阿里云百炼等模型,支持 运行时无感切换。开发者只需修改配置即可更换底层模型,无需修改业务代码,适合多模型灰度发布或灾备场景。 - LangChain4j
支持更广泛的模型生态(包括 OpenAI、Anthropic、Azure、LLaMA、Falcon、通义千问等),并允许通过ModelExecutor在代码中 动态选择主备模型,适用于对模型控制粒度要求更高的场景(如成本敏感型应用)。
- 提示工程与上下文管理
- Spring AI
提供PromptTemplate实现参数化提示,但链式提示编排能力较弱。上下文管理依赖ChatMemory与Advisor机制,需显式配置。 - LangChain4j
内置强大的 Chain 机制,可将多个提示、工具调用、解析器串联为可视化工作流,天然支持复杂推理流程(如“需求分析 → 代码生成 → 单元测试”)。对话历史管理自动集成,开箱即用。
- 工具调用(Function Calling)
- Spring AI
通过@Tool注解与FunctionCallHandler实现函数注册,与 Spring 依赖注入无缝集成,支持结构化参数校验与安全控制。 - LangChain4j
使用ToolProvider机制,支持同时注册多个工具,并可结合OutputParser对模型返回结果进行后处理,灵活性更高,但需手动处理异常与权限。
- RAG(检索增强生成)支持
- Spring AI
开箱即用的 RAG 管道:内置DocumentReader、TextSplitter、VectorStore(如 PgVector)、QuestionAnswerAdvisor等组件,通过 Spring 配置即可构建完整 RAG 应用。 - LangChain4j
需手动组合DocumentLoader、EmbeddingModel、VectorStore等模块,虽灵活但集成成本较高,适合需要定制检索逻辑的场景。
- 企业级能力
- Spring AI
原生集成 Spring Security、Micrometer 监控、Actuator 健康检查,提供/actuator/ai端点实时追踪 AI 调用指标,符合企业生产环境要求。 - LangChain4j
本身不包含企业级中间件支持,需依赖 Spring Boot 或第三方库实现监控、认证等功能,更适合轻量级或实验性项目。
三、优缺点总结
| 框架 | 优点 | 缺点 |
|---|---|---|
| Spring AI | ✅ 企业级特性完备 ✅ 与 Spring 生态无缝集成 ✅ 结构化输出(POJO 映射) ✅ 内置 RAG 与工具调用 ✅ 可观测性强 | ❌ 学习曲线较陡 ❌ 灵活性较低,定制成本高 ❌ 社区生态尚在成长 |
| LangChain4j | ✅ 上手简单,注解驱动(@AiService) ✅ 模块化设计,组合自由 ✅ 支持复杂链式工作流 ✅ 多模型兼容性极强 | ❌ 企业级能力需自行补充 ❌ RAG 需手动搭建 ❌ 缺乏统一监控方案 |
四、适用场景建议
- 选择 Spring AI,如果:
- 你正在构建 生产级、高可靠性的企业 AI 应用(如智能客服、风控决策、文档分析系统);
- 项目已基于 Spring Boot/Spring Cloud 构建,希望最小化技术栈变更;
- 需要 结构化输出、审计日志、性能监控 等企业级保障;
- 希望通过配置而非代码实现 模型切换与灰度发布。
- 选择 LangChain4j,如果:
- 你需要 快速验证 AI 想法(MVP 开发),30 行代码即可接入 LLM;
- 应用涉及 复杂多步骤推理(如代码生成、多工具协同);
- 需要 同时调用多个模型或本地开源模型;
- 对 提示工程、工作流编排 有高度定制需求。
工程搭建
- 后端技术栈: Spring Boot 3 + LangChain4j + WebFlux
- 前端技术栈: React 18 + TypeScript + Vite
- AI 模型: DeepSeek、Qwen(可扩展)
- 核心特性: 流式对话、对话记忆、工具调用、动态模型加载
项目结构
LangChain4jAgent/
├── doc/ # 项目文档目录
├── front/ # 前端项目目录
├── model-provider/ # 后端项目目录
├── pom.xml # Maven 父项目配置
├── README.md # 项目说明文档
后端架构
org.xjl.model.provider/
├── common/ # 公共模块
│ ├── constants/ # 常量定义
│ │ ├── ModelBuildParamConstant.java # 模型构建参数常量
│ │ └── ModelTypeConstant.java # 模型类型常量
│ └── utils/ # 工具类(预留)
│
├── config/ # 配置模块
│ ├── ModelConfig.java # 模型 Bean 动态注册配置
│ └── ModelProperties.java # 模型配置属性绑定类
│
├── controller/ # 控制器层
│ └── ChatClient.java # AI 对话 REST 接口控制器
│
├── core/ # 核心业务模块
│ ├── dao/ # 数据访问层(预留)
│ ├── facade/ # 门面层(预留)
│ ├── handler/ # 处理器
│ │ └── SseEmitterStreamingResponseHandler.java # SSE 流式响应处理器
│ ├── memory/ # 记忆管理
│ │ └── Assistant.java # AI 助手接口定义
│ ├── model/ # 数据模型(预留)
│ ├── service/ # 业务服务层
│ │ ├── ChatClientService.java # 对话服务接口
│ │ └── impl/
│ │ └── ChatClientServiceImpl.java # 对话服务实现
│ └── tools/ # AI 工具集
│ ├── CalculatorTool.java # 计算器工具
│ ├── DateTimeTool.java # 日期时间工具
│ ├── StringUtilTool.java # 字符串处理工具
│ ├── SystemInfoTool.java # 系统信息工具
│ └── WeatherTool.java # 天气查询工具
│
└── ModelProviderApplication.java # Spring Boot 启动类
model-provider/
├── src/
│ ├── main/
│ │ ├── java/org/xjl/model/provider/ # Java 源代码
│ │ └── resources/ # 配置文件和静态资源
│ └── test/ # 测试代码(未使用)
├── pom.xml # Maven 项目配置
pom 依赖
- 父项
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.2</version><relativePath/></parent>
- 版本管理
<properties><maven.compiler.source>21</maven.compiler.source><maven.compiler.target>21</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><!-- 版本由 BOM 管理 --><dependencyManagement><dependencies><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-bom</artifactId><version>1.8.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
- 依赖项
<dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.52</version></dependency><!-- Web + Controller 必备 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- LangChain4j 核心能力 --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!-- Streaming 支持(用于 SSE 流式返回) --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId></dependency><!-- JAX-RS SSE 所需(含 SseEventSink) --><dependency><groupId>org.glassfish.jersey.core</groupId><artifactId>jersey-server</artifactId></dependency><dependency><groupId>org.glassfish.jersey.media</groupId><artifactId>jersey-media-sse</artifactId></dependency><!-- 监控 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- 链路追踪 --><dependency><groupId>io.micrometer</groupId><artifactId>micrometer-tracing-bridge-otel</artifactId></dependency><dependency><groupId>io.opentelemetry</groupId><artifactId>opentelemetry-exporter-zipkin</artifactId></dependency><!-- AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- MapDB --><dependency><groupId>org.mapdb</groupId><artifactId>mapdb</artifactId><version>3.0.9</version></dependency><!-- for Flux<String> support --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>
- 编译
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
API KEY 申请
DeepSeek
https://platform.deepseek.com/api_keys

点击这里创建API KEY,然后需要充值额度。
Qwen-Max
https://bailian.console.aliyun.com/?tab=model#/model-market

阿里云百炼平台每天有2000Token额度可以免费使用
配置文件
注意将自己的API_KEY保存至项目环境变量中,以保证数据安全。



langchain4j:models:- name: deepseekapi-key: ${LANGCHAIN4J_OPEN_AI_DEEPSEEK_API_KEY}model-name: deepseek-chatendpoint: "https://api.deepseek.com/v1"log-requests: falselog-responses: truemaxTokens: 500temperature: 0.7timeOut: 60- name: qwenapi-key: ${LANGCHAIN4J_OPEN_AI_QWEN_API_KEY}model-name: qwen3-maxendpoint: "https://dashscope.aliyuncs.com/compatible-mode/v1"log-requests: falselog-responses: truemaxTokens: 500temperature: 0.7timeOut: 60
配置读取
package org.xjl.model.provider.config;import jakarta.annotation.PostConstruct;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;@Data
@Configuration
@ConfigurationProperties(prefix = "langchain4j")
public class ModelProperties {private List<ModelConfig> models;public Set<String> modelName;@Datapublic static class ModelConfig {private String name;private String apiKey;private String modelName;private String endpoint;private Boolean logRequests;private Boolean logResponses;private Integer maxTokens;private Double temperature;private Integer timeOut;}@PostConstructpublic void init() {if(models != null){modelName = models.stream().map(ModelConfig::getName).collect(Collectors.toSet());}}}
配置注入
package org.xjl.model.provider.config;import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;import java.time.Duration;@Slf4j
@Configuration
public class ModelConfig implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {private Environment environment;private static final String MODEL_CONFIG_PREFIX = "langchain4j";@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {log.info("Starting to register model beans...");try {// 使用 Binder API 手动绑定配置ModelProperties modelProperties = Binder.get(environment).bind(MODEL_CONFIG_PREFIX, ModelProperties.class).orElse(null);if (modelProperties == null || modelProperties.getModels() == null || modelProperties.getModels().isEmpty()) {log.warn("No models configured in langchain4j.models");return;}log.info("Found {} model configurations", modelProperties.getModels().size());modelProperties.getModels().forEach(modelConfig -> {log.info("Registering model bean: name={}, modelName={}", modelConfig.getName(), modelConfig.getModelName());GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(OpenAiStreamingChatModel.class);beanDefinition.setInstanceSupplier(() -> OpenAiStreamingChatModel.builder().apiKey(modelConfig.getApiKey()).modelName(modelConfig.getModelName()).baseUrl(modelConfig.getEndpoint()).logRequests(modelConfig.getLogRequests()).logResponses(modelConfig.getLogResponses()).maxTokens(modelConfig.getMaxTokens()).temperature(modelConfig.getTemperature()).timeout(Duration.ofSeconds(modelConfig.getTimeOut())).strictJsonSchema(true).build());// 使用 name 作为 Bean 名称(如 "qwen", "deepseek")registry.registerBeanDefinition(modelConfig.getName(), beanDefinition);log.info("Successfully registered bean: {}", modelConfig.getName());});log.info("Model bean registration completed");} catch (Exception e) {log.error("Failed to register model beans", e);throw new RuntimeException("Failed to register model beans", e);}}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 不需要实现}
}
定义模型对话接口
package org.xjl.model.provider.core.memory;import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import reactor.core.publisher.Flux;/*** AI助手接口 - 支持多会话对话*/
public interface Assistant {/*** 同步对话接口* @param memoryId 会话ID(用于区分不同用户/会话)* @param userMessage 用户消息* @return AI回复*/
// @SystemMessage("你是一个复读机,接下来请你重复回答:{{memoryId}}.")String chat(@MemoryId int memoryId, @UserMessage String userMessage);/*** 流式对话接口* @param memoryId 会话ID(用于区分不同用户/会话)* @param userMessage 用户消息* @return AI回复*/Flux<String> chatStream(@MemoryId int memoryId, @UserMessage String userMessage);
}
模型Service
package org.xjl.model.provider.core.service;import reactor.core.publisher.Flux;public interface ChatClientService {Flux<String> chatStream(int sessionId, String message,String modelName);}
package org.xjl.model.provider.core.service.impl;import com.alibaba.fastjson2.JSON;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.xjl.model.provider.core.memory.Assistant;
import org.xjl.model.provider.core.service.ChatClientService;
import org.xjl.model.provider.core.tools.*;
import reactor.core.publisher.Flux;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Slf4j
@Service
public class ChatClientServiceImpl implements ChatClientService {@Autowiredprivate ApplicationContext applicationContext;// 工具注入@Autowiredprivate CalculatorTool calculatorTool;@Autowiredprivate DateTimeTool dateTimeTool;@Autowiredprivate WeatherTool weatherTool;@Autowiredprivate StringUtilTool stringUtilTool;@Autowiredprivate SystemInfoTool systemInfoTool;public ConcurrentHashMap<String,Assistant> assistantMap = new ConcurrentHashMap<>();@PostConstructpublic void init() {Map<String,OpenAiStreamingChatModel> modelContext = applicationContext.getBeansOfType(OpenAiStreamingChatModel.class);log.info("Model Bean List = {}", JSON.toJSONString(modelContext));if(!modelContext.isEmpty()){modelContext.forEach((k,v)->{Assistant a = AiServices.builder(Assistant.class).streamingChatModel(v).chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)).build();assistantMap.put(k,a);});}log.info("AI Assistants initialized successfully!!!");}@Overridepublic Flux<String> chatStream(int sessionId, String message, String modelName) {return assistantMap.get(modelName).chatStream(sessionId,message);}
}
Controller层
package org.xjl.model.provider.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import org.xjl.model.provider.config.ModelProperties;
import org.xjl.model.provider.core.service.ChatClientService;
import reactor.core.publisher.Flux;import java.util.Set;import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;@Slf4j
@RestController
@RequestMapping("/model/provider")
public class ChatClient {@Autowiredprivate ModelProperties modelProperties;@Autowiredprivate ChatClientService qwenClientService;/*** AiService 流式对话接口* @param message* @return*/@PostMapping(value = "/stream", produces = TEXT_EVENT_STREAM_VALUE)public Flux<String> streamingAssistant(@RequestBody String message, @RequestParam("sessionId") int sessionId,@RequestParam("modelName") String modelName) {Assert.isTrue(modelProperties.getModelName().contains(modelName), "model is not exists");return qwenClientService.chatStream(sessionId,message,modelName);}@GetMapping("/modelName")public Set<String> getModelName() {return modelProperties.getModelName();}}
测试

curl --location 'http://localhost:8080/model/provider/stream?sessionId=1&modelName=qwen' \
--header 'Content-Type: text/plain' \
--data '你好'
