Java开发者AI实践:Spring AI与DJL框架实战指南
1. 为什么Java开发者不用转Python也能做AI这两年AI的火烧得有多旺不用我多说。离谱的是圈子里好像默认了一件事搞AI就得用Python不学Python就是时代的边角料。做Java的同学尤其焦虑技术群里天天有人问“Java还有前途吗”“要不要转Python搞AI”。我的看法很直接完全不用焦虑Java生态里早就有了能落地的AI框架而且不是玩具级别是能在生产环境里跑出稳定成绩的那种。这篇文章想聊的就是两个Java AI框架Spring AI 和 DJLDeep Java Library。前者负责“怎么优雅地接大模型”后者负责“怎么在Java里跑模型推理”。两个框架配合起来从对话机器人到知识库问答从意图识别到图片分类基本都能在纯Java的体系里搞定完全不需要跑到Python那边去重学一套技术栈。先说清楚这篇文章适合谁。如果你是刚接触AI的Java后端开发或者正在准备Java面试想给自己加点新话题又或者只是单纯不想跟风转Python都很适合读下去。我不会讲太多学院派的理论更多是实际项目里能用上的东西包括配置、代码、踩坑记录照着写就能跑起来。顺便说句题外话很多招聘JD里现在都写“熟悉AI应用开发优先”如果你能在简历上写“熟悉Spring AI、DJL有Java AI应用实践经验”在Java赛道里其实是很有辨识度的。毕竟会Python的人多会“用Java做AI”的人还是少数这反而成了差异化优势。2. Spring AI实战让Java应用轻松获得大模型能力2.1 Spring AI到底是个什么东西Spring AI是Spring官方推出的AI应用开发框架目标很明确让Java开发者用Spring的习惯去对接大模型。它做的事情有点像把Python生态里LangChain那套能力用Spring的方式重写一遍但又比LangChain更贴合Java后端的需求。我对Spring AI的评价是它解决了Java接大模型时最痛的那几件事。第一统一的API抽象。OpenAI、Ollama、通义千问、智谱、DeepSeek这些模型服务商API风格各不相同Spring AI把它们统一成ChatClient、EmbeddingModel、ImageModel等接口业务代码不用绑定具体厂商。第二和Spring Boot无缝集成。自动配置、依赖注入、配置文件管理这些Spring开发者熟悉的东西全部保留学习成本极低。第三内置了RAG、Function Calling、Agent等进阶能力不需要自己从零搭。我见过很多人一听说“Java AI框架”就觉得是缝合怪其实不是。Spring AI的设计思路很清晰核心就是“把模型访问抽象出来把AI能力组件化”。你写业务代码的人根本不需要关心HTTP请求怎么发、JSON怎么解析、上下文怎么管理Spring AI全帮你处理了。2.2 环境准备和依赖配置要跑Spring AI基础环境很简单JDK 17以上、Maven 3.6以上再加一个Spring Boot 3.x项目就行。如果你本机能装一个Ollama体验会更好因为Ollama可以在本地跑大模型不依赖外网也不烧太多API费用调试起来特别方便。Maven依赖长这样parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency /dependencies注意Spring AI的版本迭代很快不同小版本的API可能会有微调。我上面写的1.0.0是比较稳的一个版本如果你搜索到更新的版本改用新版本也问题不大核心用法是一致的。配置文件application.yml这样写spring: application: name: ai-demo ai: ollama: base-url: http://localhost:11434 chat: model: qwen2.5:7b这里的model名称要和你本Ollama拉取的模型一致。先确保Ollama服务已启动然后拉一个模型ollama pull qwen2.5:7b如果不想用本地模型想接在线API替换成对应的starter和配置就行。比如接通义千问用spring-ai-dashscope-spring-boot-starter配置里写api-key和模型名即可。Spring AI最省心的地方就在这里换模型服务商只是换依赖和配置业务代码基本不用动。2.3 三步完成一个对话接口写一个最简单的对话接口只要三步注入ChatClient、拼Prompt、拿结果。RestController RequestMapping(/ai) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public MapString, String chat(RequestParam String message) { String reply chatClient.prompt() .user(message) .call() .content(); return Map.of(reply, reply); } }这段代码核心就一句话chatClient.prompt()创建Promptuser()填写用户消息call()调用模型content()取出模型返回的文本。启动项目后访问http://localhost:8080/ai/chat?message你好就能得到模型的回复。很多初学者第一次跑通这个接口会特别兴奋觉得AI接入也没那么难嘛。确实Spring AI把最复杂的通信过程全部封装掉了你只需要关心业务逻辑。但这里我要提醒一句不要止步于“跑通”生产环境里你要考虑Prompt管理、上下文记忆、Token消耗、模型返回的稳定性这些接下来都会讲到。2.4 ChatClient的高级玩法系统Prompt和上下文记忆真实项目里用户提问往往需要定制语气、限定范围、附带业务规则。直接用user()发消息太裸了得用system()设置系统提示词。String reply chatClient.prompt() .system(你是一个专业的Java开发助教回答要简洁、准确优先给出代码示例。) .user(请解释一下Java的volatile关键字) .call() .content();你会发现加了系统Prompt之后模型回答的风格立刻不一样了。这个技巧在写客服机器人、知识助手时特别常用你想让AI扮演什么角色就用system()把角色设定喂给它。还有一个高频需求是上下文记忆。默认情况下每次调用模型都是无状态的模型记不住你上一轮说了什么。要让对话连贯需要把历史消息传进去。Spring AI提供了Message接口用SimpleMessage或者更具体的UserMessage、SystemMessage、AssistantMessage来构造历史记录。一个简单做法是ListMessage history List.of( new SystemMessage(你是一个乐于助人的助手。), new UserMessage(我的名字是张三), new AssistantMessage(你好张三很高兴认识你。), new UserMessage(我叫什么名字) ); String reply chatClient.prompt() .messages(history) .call() .content();这里模型看到完整对话历史自然能回答出“你叫张三”。实际开发中历史记录通常存在Redis或数据库里按会话维度管理即可。注意一点模型的上下文窗口是有限的历史消息不能无限堆积。超过窗口长度模型会报错或者直接把最早的消息丢掉。实际项目里建议只取最近N轮对话作为上下文比如最近10轮这对大多数业务场景已经够用了。2.5 用RAG让AI“学习”你自己的私有知识大模型训练数据有截止日期也没有你的业务文档。想让AI回答关于自己项目、产品、内部文档的问题就得用RAG检索增强生成技术。RAG的思路简单说就是用户提问时先从知识库里检索出相关内容把检索结果和问题一起拼进Prompt让模型基于这些内容作答。这样模型不用“背下”你的知识只需要做阅读理解准确率会高很多。Spring AI对RAG的支持非常完整。标准流程是准备文档 - 切分 - 向量化 - 存向量库 - 查询时检索 - 喂给模型。在Spring AI里前几步有现成的API向量存储可以用内置的简单向量数据库也可以接Redis、PGVector等。先引入向量存储相关依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-pgvector-store-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-tika-document-reader/artifactId version${spring-ai.version}/version /dependency然后写一个初始化加载接口把知识文档读进来切成片段向量化后存进向量库Service public class KnowledgeService { private final SimpleVectorStore vectorStore; private final EmbeddingModel embeddingModel; public KnowledgeService(SimpleVectorStore vectorStore, EmbeddingModel embeddingModel) { this.vectorStore vectorStore; this.embeddingModel embeddingModel; } public void loadKnowledge(String filePath) { var reader new PagePdfDocumentReader(filePath); var documents reader.get(); var splitter new TokenTextSplitter(); var chunks splitter.apply(documents); vectorStore.add(chunks); } }查询的时候用SearchRequest去向量库检索相似内容然后合成Promptpublic String query(String question) { var results vectorStore.similaritySearch(SearchRequest.builder() .query(question) .topK(5) .build()); var context results.stream() .map(doc - doc.getContent()) .collect(Collectors.joining(\n)); String reply chatClient.prompt() .system(你只能根据给定的资料回答问题如果资料中没有相关内容请如实说明不知道。) .user(资料\n context \n\n问题 question) .call() .content(); return reply; }RAG做得好不好最关键的几个点切分粒度、向量化模型、topK取值。片段太小上下文信息不完整片段太大可能导致检索噪音增多。创建知识库时建议先跑几个测试问题反复调整切分参数直到回答质量稳定。2.6 Function Calling让AI能调用你的Java方法RAG解决的是“知识获取”问题Function Calling解决的则是“能力扩展”问题。比如用户说“帮我查一下订单物流信息”模型本身不接入你的订单系统但可以通过Function Calling把用户意图转成一次函数调用由你的代码去查数据库再把结果交还给模型组织语言。Spring AI里实现Function Calling很方便。先定义一个Java方法public record OrderQueryRequest(String orderId) {} public String queryOrder(OrderQueryRequest request) { // 这里查数据库或其他服务 return 订单 request.orderId() 当前状态为已发货正在配送中; }然后把方法注册给ChatClientBean public ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultFunctions(queryOrder) .build(); }同时需要给模型描述一下这个函数的能力让模型懂得在合适的时候调用。Spring AI提供注解方式简化描述public class OrderService { Tool(description 根据订单号查询订单物流状态) public String queryOrder(ToolParam(description 订单号) String orderId) { return 订单 orderId 当前状态为已发货正在配送中; } }Function Calling是我觉得Spring AI最值得深挖的功能。一旦用上AI就不只是聊天工具了而是变成了一个“会说人话的接口调度器”。用户说“帮我取消订单12345”模型自动调用取消订单方法然后回复“您的订单12345已成功取消”。这种体验才是真正的AI应用落地。3. DJL实战在Java里跑模型推理的正确姿势3.1 DJL是什么为什么还需要它Spring AI解决的是“对接大模型”的问题但大模型API只能处理文本输入输出。现实业务里我们经常还需要在本地跑一些模型比如图片分类、目标检测、人脸识别、文本向量化。这些场景如果每次都调API延迟高、成本高、还依赖网络。DJL就是解决这个问题的。DJL全称Deep Java Library是AWS开源的Java深度学习框架底层支持PyTorch、TensorFlow、ONNX Runtime等引擎。它设计的核心目标是Java开发者不需要写Python代码也能完成模型的训练、加载和推理。对于大多数应用场景我们只需要用到它的“加载模型 推理”能力这就是纯Java环境下部署AI的利器。我做一个简单类比Spring AI像“前端接待员”负责和大模型聊天DJL像“本地技能工作室”负责在自家服务器上跑专用模型。两个配合才能覆盖完整的AI应用场景。3.2 引入DJL依赖和环境准备DJL本身是Java库引入方式和普通Maven依赖一样。它采用“核心 引擎”的分离结构你需要按实际使用的底层引擎引入额外依赖。properties djl.version0.28.0/djl.version /properties dependencies !-- DJL核心API -- dependency groupIdai.djl/groupId artifactIdapi/artifactId version${djl.version}/version /dependency !-- 使用PyTorch引擎 -- dependency groupIdai.djl.pytorch/groupId artifactIdpytorch-engine/artifactId version${djl.version}/version /dependency !-- 使用ONNX Runtime引擎推荐 -- dependency groupIdai.djl.onnxruntime/groupId artifactIdonnxruntime-engine/artifactId version${djl.version}/version /dependency /dependencies如果你是本地开发建议优先用ONNX Runtime因为它跨平台比较省心不需要额外安装Python环境也没有庞大的PyTorch二进制依赖。把模型转换成onnx格式后部署时依赖很少集成到Docker镜像里也很轻量。DJL对Java版本要求也不高Java 11以上即可和Spring Boot应用放在一起完全没冲突。3.3 用DJL加载图片分类模型的完整步骤我拿一个最常见的场景举例图片分类比如判断一张图是猫还是狗。用DJL做推理的核心要素是四个模型ZooModel、输入Input、预测器Predictor、输出Output。逻辑是加载模型 - 创建预测器 - 把输入数据转成模型需要的格式 - 执行推理 - 解析输出。下面是完整代码public class ImageClassificationDemo { public static void main(String[] args) throws Exception { // 1. 构造模型加载条件 CriteriaImage, Classifications criteria Criteria.builder() .optApplication(Application.CV.IMAGE_CLASSIFICATION) .setTypes(Image.class, Classifications.class) .optFilter(backbone, resnet50) .optProgress(new ProgressBar()) .build(); // 2. 加载模型 try (ZooModelImage, Classifications model criteria.loadModel()) { // 3. 创建预测器 try (PredictorImage, Classifications predictor model.newPredictor()) { // 4. 读取并预处理图片 Image img ImageFactory.getInstance() .fromUrl(https://example.com/dog.jpg); // 5. 推理 Classifications classifications predictor.predict(img); // 6. 输出结果 classifications.topK(3).forEach((className, prob) - { System.out.printf(%s: %.2f%%%n, className, prob * 100); }); } } } }代码不复杂但有几个点新手特别容易踩坑。第一optApplication指定了任务类型“图像分类”DJL会根据这个类型自动匹配内置模型仓库里的可用模型。如果你没有手动指定模型文件它会尝试自动下载。在中国大陆网络环境下模型下载可能很慢强烈建议预先下载好模型文件放到本地然后用optModelUrls指定本地路径。第二图片输入必须是模型训练时的预处理器能接受的格式。不同模型对图片尺寸、归一化方式都有要求。DJL内置的ImageFactory会自动处理缩放和归一化但如果是自定义模型你需要自己实现Translator来做精确的预处理。Translator是DJL里比较核心的概念后续我会专门讲。第三是资源释放问题。模型对象、Predictor都可能占用内存或显存用完一定要关。上面的代码用了try-with-resources这是官方推荐写法别偷懒不写关闭的代码。3.4 用DJL做文本向量化的实战案例除了图像DJL另一个常用场景是文本向量化也就是把一段文字转成一个固定维度的浮点数组。这个能力在做语义搜索、文本相似度计算、推荐系统时非常实用。这里我直接用HuggingFace上的sentence-transformers模型它可以把文本映射成384维的向量语义相近的句子向量余弦相似度也高。先需要一个Translator把文本转成模型接收的格式public class TextEmbeddingTranslator implements TranslatorString, float[] { Override public NDList processInput(TranslatorContext ctx, String input) { // 这里用内置BERT分词器处理文本 var tokenizer BertTokenizer.builder() .setVocabulary(cacheVocabulary()) .build(); ListString tokens tokenizer.tokenize(input); // 构造模型需要的输入张量input_ids、attention_mask NDManager manager ctx.getNDManager(); var inputIds manager.create(new long[]{...}); var attentionMask manager.create(new long[]{...}); return new NDList(inputIds, attentionMask); } Override public float[] processOutput(TranslatorContext ctx, NDList list) { // 取池化层的输出转成float数组 NDArray embedding list.get(1); return embedding.toFloatArray(); } }这块代码是示意性的因为完整的分词和处理逻辑比较长。实践中我推荐直接使用DJL内置的HuggingFace模型支持比如CriteriaString, float[] criteria Criteria.builder() .setTypes(String.class, float[].class) .optModelUrls(file:///models/all-MiniLM-L6-v2) .optEngine(OnnxRuntime) .optTranslator(new TextEmbeddingTranslator()) .build();用文本向量化有个很实用的组合把文档库里的所有文本都向量化存到向量数据库用户查询时同样转成向量然后做余弦相似度检索。这就是之前讲的RAG的底层原理。你在Spring AI里配好RAG后其实内部就是在做这件事。自己用DJL实现一遍能更深刻理解RAG的每个环节。3.5 DJL常见性能问题与优化思路DJL跑推理性能问题主要集中在三方面模型加载耗时、推理耗时、内存占用。模型加载耗时在每次启动时都会出现本地加载onnx模型通常几百毫秒到几秒不等复杂模型会更久。生产环境建议把模型对象做成单例启动时预加载一次而不是每次请求都loadModel。推理耗时和CPU/GPU有关系。DJL支持CUDA如果机器有NVIDIA显卡配置好环境后推理速度能提升好几倍。没有GPU时尽量选择小模型比如用轻量的MobileNet代替ResNet50用量化模型减少计算量。量化是这个领域很重要的手段把FP32模型转成INT8体积缩小4倍速度提升明显精度损失通常在可接受范围。内存占用方面要注意模型加载时会占用一份内存推理过程中也会创建临时NDArray。如果并发量高建议复用Predictor并控制并发线程数。DJL内部线程安全由实现决定一般建议每个线程维护自己的Predictor或者用线程池控制并发访问。4. 双框架组合实战做一个“AI客服 本地模型路由”的完整DEMO4.1 这个Demo要解决什么问题前面两章分别讲了Spring AI和DJL这一章把它们组合起来做一个相对完整的智能客服Demo。需求背景现在很多公司有自己的商品库和订单系统用户提问五花八门有的是查物流有的是问商品库存还有的是闲聊调戏AI。如果所有问题都一股脑交给大模型大而全地回答一方面Token消耗太高另一方面回答容易不可控。我的设计思路是用DJL在本地跑一个轻量的意图分类模型先把用户输入分成“商品咨询”“订单查询”“闲聊”三类。分类结果用于路由订单查询走Function Calling查订单系统商品咨询走RAG检索商品库再回答闲聊则直接走大模型普通对话。这样每个分支都用最小代价完成精度和成本都能兼顾。这类“本地小模型先行分流大模型负责精细化生成”的架构在真实AI应用里非常常见。它解决了大模型服务贵、延迟高、不能稳定按规则走流程的问题。4.2 项目整体结构Demo使用Spring Boot 3 Spring AI DJLONNX Runtime H2内存数据库方便跑通。目录结构按职责划分src/main/java/com/example/aisupport/ ├── AISupportApplication.java ├── controller/ │ └── CustomerServiceController.java ├── service/ │ ├── ChatService.java │ ├── IntentRecognitionService.java │ └── OrderService.java ├── repository/ │ └── ProductRepository.java └── config/ └── AppConfig.javaMaven依赖把Spring AI和DJL的依赖都加上另外加一个H2数据库依赖用来存商品数据。这些依赖加在一起Maven会帮你下载不少库耐心等就行。4.3 用DJL实现意图识别模块意图识别我直接用DJL加载一个文本分类的ONNX模型。理论上你可以自己训练一个但这里重点演示如何集成所以用HuggingFace上现成的中文文本分类模型比较合适。如果暂时没有现成模型文件也可以用规则兜底先尝试加载模型如果模型不存在就根据关键词简单判断意图比如包含“订单”“物流”“快递”就归为订单查询包含“库存”“价格”“多少钱”就归为商品咨询没有命中就默认闲聊。这样开发阶段不至于被模型文件卡住。核心代码如下Service public class IntentRecognitionService { private ZooModelString, Classifications model; private PredictorString, Classifications predictor; public IntentRecognitionService() { // 模型加载逻辑使用本地onnx模型文件 CriteriaString, Classifications criteria Criteria.builder() .setTypes(String.class, Classifications.class) .optEngine(OnnxRuntime) .optModelUrls(file:///models/intent_model.onnx) .optTranslator(new IntentTranslator()) .build(); model criteria.loadModel(); predictor model.newPredictor(); } public String recognize(String message) { try { Classifications result predictor.predict(message); String intent result.topK(1).get(0).getClassName(); return mapToBusinessIntent(intent); } catch (Exception e) { return fallbackRule(message); } } }注意模型加载如果失败一定不要直接让整个系统启动失败这里用try-catch降级到规则匹配是生产环境里很实用的容错设计。4.4 Spring AI实现对话与RAG检索意图识别确定了分支之后对话服务负责具体执行响应。Service public class ChatService { private final ChatClient chatClient; private final IntentRecognitionService intentService; private final ProductRepository productRepository; public String handle(String userMessage) { String intent intentService.recognize(userMessage); return switch (intent) { case ORDER - handleOrderQuery(userMessage); case PRODUCT - handleProductQuery(userMessage); default - chatClient.prompt() .system(你是XX公司的智能客服语气亲切回答尽量简短。) .user(userMessage) .call() .content(); }; } private String handleOrderQuery(String userMessage) { // 提取订单号可以用正则或者大模型辅助这里简化处理 String orderId extractOrderId(userMessage); String orderInfo orderService.queryOrder(orderId); return orderInfo; } private String handleProductQuery(String userMessage) { // 从商品库中检索相关商品 ListProduct products productRepository.searchByKeyword(userMessage); String context products.stream() .map(p - p.getName() 价格 p.getPrice() 元库存 p.getStock() 件) .collect(Collectors.joining()); return chatClient.prompt() .system(根据提供的商品信息回答用户问题信息不足时如实说明。) .user(商品信息 context \n用户问题 userMessage) .call() .content(); } }这个Demo的价值在于它展示了一种真实可落地的架构思路小模型做路由决策大模型做语义生成领域数据用RAG做补充。它不追求炫技但每一步都是实际项目里踩过坑后沉淀下来的模式。4.5 部署运行和验证启动Spring Boot项目后用curl测试接口curl -X POST http://localhost:8080/api/customer/chat \ -H Content-Type: application/json \ -d {message:我要查一下订单20260101123456的物流}如果一切正常系统会先识别意图为订单查询然后查订单系统返回物流状态。如果用户问“你们有无线耳机吗”则触发商品咨询分支通过RAG检索商品库生成回答。如果用户说“你吃饭了吗”就直接走大模型闲聊。整个流程链路清晰运行稳定而且没有一丝Python代码参与。这就是Java技术栈做AI的魅力。5. 常见问题与避坑实录5.1 Spring AI常见报错汇总Spring AI版本更新快报错信息五花八门这里整理几个我实际遇到的典型问题。问题一连接Ollama超时表现调用ChatClient时报ConnectTimeoutException。原因大多是Ollama服务没启动或者base-url配置错误。排查步骤先确认本机能通过浏览器访问http://localhost:11434再确认配置文件里的地址正确。如果Ollama和Spring Boot不在同一台机器一定要写对IP和端口。问题二模型名不存在表现调用时报“model not found”或HTTP 404。原因是Ollama仓库里没有你配置的那个模型。可以用ollama list命令查看本机已安装的模型确保model名称完全一致。模型名一般要带版本标签比如qwen2.5:7b不能只写qwen2.5。问题三返回结果时好时坏大模型本身是概率输出同样的问题在不同时间可能返回不同答案。这个问题不是Bug而是大模型的固有特性。解决办法不是去修代码而是优化Prompt提高输出的确定性。比如要求模型“只回答JSON格式”“不要输出解释”或者把temperature参数调低。Temperature参数在Spring AI里可以这样配置spring: ai: ollama: chat: options: temperature: 0.2temperature越低输出越保守、越稳定越高输出越多样性。生产环境里做结构化输出建议把temperature调低。5.2 DJL模型加载失败的排查思路DJL报错最多的场景是加载模型时。常见错误信息包括“Model not found”“Invalid model file”“No engine found”。如果提示No engine found说明ONNX Runtime或PyTorch引擎的依赖没引全检查Maven依赖里是否包含对应的engine模块。如果提示Invalid model file模型文件本身可能损坏或者不是合法的onnx/pytorch格式。如果是模型下载失败建议手动去模型仓库下载放到本地路径后用optModelUrls指定避免运行时联网下载的麻烦。还有一个隐蔽的坑模型文件的绝对路径里如果包含中文或空格在某些操作系统上会导致加载失败。稳妥做法是把模型放在项目resouces目录下或者放在纯英文路径下。5.3 依赖冲突问题Spring AI和DJL各自都带了不少依赖放在一个项目里偶尔会冲突最常见的是Jackson版本冲突和Netty版本冲突。解决办法不复杂用Maven的依赖树插件找到冲突版本然后在pom.xml里通过dependencyManagement统一版本。我建议先把Spring Boot的BOM放最前面再引入Spring AI的BOM最后引入DJL让Maven的依赖仲裁按我们预期的顺序执行。如果还是冲突可以看具体报错。我遇到过一次Netty版本冲突报错信息一直指向某个类方法找不到。最终查下来是一个库传递依赖了老版本Netty。解决办法是在pom里显式排除传递依赖或者统一指定Netty版本。5.4 并发场景下的性能坑生产环境最常见的性能坑是没有复用模型和Predictor。DJL的模型加载非常耗时如果每个请求都加载一次系统能直接被打挂。我在代码里也强调过模型对象作为单例启动时加载一次Predictor尽量复用或按线程池复用。Spring AI这边ChatClient是线程安全的可以放心共用。但要注意底层HTTP连接池的配置。高并发场景下给Spring Boot的HTTP客户端设置合理的连接超时、读取超时和最大连接数否则大量请求会堆积等待。建议在生产配置里给RestClient设置超时spring: ai: chat: client: timeout: 60s这个超时时间要综合考虑模型响应速度。大模型生成长文本时几十秒都很正常超时设太短会导致频繁报错。5.5 说说我对Java做AI这件事的最终感受这两个框架我实际用下来的体会是它们不是Python生态的替代品而是Java技术栈在AI时代的自然延伸。Python在训练和实验阶段的优势确实无可替代但Java在工程化、稳定性、并发处理上依然是老本行。真正做企业级应用时Java做AI服务层的优势会非常明显。我见过太多人因为AI焦虑去学Python结果学了个寂寞。如果你本身是Java开发者先在Spring AI和DJL上深入下去用Java把手里的业务做成AI应用这条路反而更容易出成果。等哪天你真的需要训练一个全新模型那时候再学Python也不迟。最后分享一个小技巧把Spring AI和DJL结合起来用的时候建议先把链路打通再考虑优化。先用最简单的规则路由跑通全流程然后逐步替换成模型推理最后再做性能调优。这样每一步改动都能验证排查问题时也不会手忙脚乱。技术选型有时候没有标准答案适合自己团队、自己业务的那套方案就是最好的方案。