保姆级|Spring AI 2.0 第一个 Demo:Spring Boot 4 接入大模型(含国内可用方案 + 完整 pom)

保姆级|Spring AI 2.0 第一个 Demo:Spring Boot 4 接入大模型(含国内可用方案 + 完整 pom)

本文将解决 :5 分钟内,用 Spring AI 2.0 的 ChatClient 把第一个大模型回答打印到控制台,并给出 OpenAI / DeepSeek / 通义 / Ollama 四种国内可用接入方案。

你将学到:

  • 为什么 Java 接入大模型首选 Spring AI,而不是手搓 HTTP
  • Spring AI 2.0 的依赖坐标与版本管理(spring-ai-bom)
  • 最小 application.yml 配置(base-url / api-key)
  • 两行代码完成第一次 ChatClient.call()
  • 三种国内可用方案:OpenAI 兼容协议接 DeepSeek/通义、本地 Ollama 零成本
  • 5 个新手必踩的坑(base-url、代理、超时、Key、版本冲突)

一、为什么 Java 接入大模型要选 Spring AI

很多 Java 开发者第一次接大模型,会用 RestTemplate / WebClient 手搓 OpenAI 的 HTTP 接口:自己拼 JSON、自己解析响应、自己处理流式 SSE。能跑,但很快会撞墙:换模型要重写、流式输出要自己管背压、工具调用要自己实现多轮循环、RAG 要自己接向量库。

Spring AI 的价值,就是把这套东西变成 Spring 生态里的原生抽象 :像用 JdbcTemplate 一样用 ChatClient,像换数据库一样换模型。它由 Spring 官方团队维护,2.0 已经是生产可用的 GA 版本。

一句话定位:Spring AI 是 Spring 官方对"Java 调用大模型"这件事的标准化封装,不是又一个通用 Agent 框架,也不和 LangChain4j 抢同一个生态位------它深度融入 Spring Boot 的自动装配、可观测、依赖管理。


二、版本标注(重要,先看清楚)

本文基于以下版本,2.0 相对 1.x 有 breaking 变化,不要照抄 1.x 的老教程:

组件 版本 说明
Spring AI 2.0.1 BOM 统一锁版本
Spring Boot 4.1.x 2.0 要求 Boot 4.0/4.1,不是 Boot 3
JDK 21+ 2.0 最低要求 JDK 21,不是 JDK 17

⚠️ 如果你还在用 Boot 3.x + JDK 17 + Spring AI 1.x,请看本系列第 4 篇《1.x→2.0 迁移指南》。


三、环境准备

  • JDK 21(java -version 确认)
  • Maven 3.9+
  • 一个模型服务的 API Key(OpenAI 或 DeepSeek 任选,下文给出国内可用方案)

不需要 GPU、不需要装向量库、不需要任何额外中间件------入门就是这么轻。


四、依赖与完整 pom.xml

Spring AI 的版本管理靠 BOM(Bill of Materials) :引入 spring-ai-bom 后,下面所有 spring-ai-starter-* 依赖都不用再写版本号,避免版本冲突。

创建 Maven 项目,pom.xml 如下(可直接复制):

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- Spring Boot 4 父工程,统一依赖与插件版本 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-ai-demo</name>
    <description>Spring AI 2.0 第一个 Demo</description>

    <properties>
        <java.version>21</java.version>
        <spring-ai.version>2.0.1</spring-ai.version>
    </properties>

    <!-- 关键:spring-ai-bom 统一锁 Spring AI 所有模块版本 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Web 依赖:让 AI 通过 HTTP 接口对外提供服务 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- OpenAI 模型 starter(版本由 BOM 管理) -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

两个关键点:

  1. spring-ai-bom 放在 dependencyManagement 里,spring-ai-starter-model-openai 不写版本号。
  2. starter 命名是 spring-ai-starter-model-openai------不是 老教程里的 spring-ai-openai-spring-boot-starter(那是 1.0 之前的旧名,2.0 已废弃,写了会报依赖找不到)。

五、最小 application.yml 配置

src/main/resources/application.yml:

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}          # 从环境变量读取,别把 Key 硬编码进代码
      base-url: https://api.openai.com    # 默认就是它,接国产模型时改成对应地址
      chat:
        options:
          model: gpt-4o-mini              # 模型名;换成你的模型即可
          temperature: 0.7

说明:2.0 对配置属性做了扁平化调整,chat.options.* 这层在部分 2.0.x 版本里已改名。本文代码尽量把 model/temperature 放到 Java 侧的 ChatOptions.builder() 里 ,从根上规避属性名漂移,配置里只留最稳的 api-key 和 base-url。这也是工程上的推荐做法------参数跟代码走,配置跟环境走。


六、第一次调用:两行代码跑通

最直接的跑通方式:写一个 CommandLineRunner,应用启动时打印一次 AI 回复,不用开浏览器、不用写 Controller。

java 复制代码
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class SpringAiDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAiDemoApplication.class, args);
    }

    @Bean
    CommandLineRunner runner(ChatClient.Builder builder) {
        return args -> {
            ChatClient chatClient = builder.build();
            String reply = chatClient.prompt()
                    .user("用一句话介绍 Spring AI")
                    .call()
                    .content();
            System.out.println("AI 回复:" + reply);
        };
    }
}

启动应用(mvn spring-boot:run),控制台会打印出 AI 回复。到此你已经跑通了 Spring AI 2.0。

核心就一个链式调用:builder.build() 拿到 ChatClient,.prompt().user("...").call().content() 发起请求并取回字符串。


七、进阶:暴露成 HTTP 接口

实际项目里 AI 能力要通过接口对外提供,加一个 Controller:

java 复制代码
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    // 直接注入 ChatClient.Builder,由框架装配
    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(defaultValue = "你好,介绍一下你自己") String q) {
        return chatClient.prompt()
                .user(q)
                .call()
                .content();
    }
}

启动后访问:http://localhost:8080/chat?q=写一首关于Java的诗,即可得到回复。


八、国内可用方案(三种)

OpenAI 的 Key 很多人没有,这里给出三种国内可用的替代方案。

方案 A:DeepSeek(OpenAI 兼容协议,改 base-url 即可)

DeepSeek 提供了 OpenAI 兼容接口,不用换 starter,只改配置:

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${DEEPSEEK_API_KEY}
      base-url: https://api.deepseek.com     # 也可写 https://api.deepseek.com/v1
      chat:
        options:
          model: deepseek-chat               # DeepSeek 的对话模型

代码完全不用改------这就是 Spring AI 抽象的价值(第 3 篇会展开讲)。

方案 B:通义千问(阿里云,走兼容协议)

阿里云百炼的 DashScope 也提供 OpenAI 兼容端点:

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${DASHSCOPE_API_KEY}
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      chat:
        options:
          model: qwen-plus

方案 C:本地 Ollama(零成本、零 Key、数据不出本地)

Ollama 让你在本地跑开源模型,完全免费、无需 Key、数据私有。先装 Ollama 并拉一个模型:

bash 复制代码
# 安装后拉取一个模型(以 Qwen2.5 为例)
ollama pull qwen2.5

然后换 starter 和配置:

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
yaml 复制代码
spring:
  ai:
    ollama:
      base-url: http://localhost:11434    # Ollama 默认本地端口
      chat:
        options:
          model: qwen2.5                  # 必须是已 ollama pull 的模型名

三种方案对比:

方案 starter base-url 成本 需要 Key 适用
OpenAI spring-ai-starter-model-openai https://api.openai.com 按量付费 是 效果最强
DeepSeek spring-ai-starter-model-openai https://api.deepseek.com 便宜 是 性价比高
通义千问 spring-ai-starter-model-openai https://dashscope.aliyuncs.com/compatible-mode/v1 按量 是 国内合规
Ollama spring-ai-starter-model-ollama http://localhost:11434 免费 否 本地/私有化

九、原理图解:一次调用的完整链路

OpenAI/DeepSeek/Ollama ChatModel ChatClient ChatController 浏览器/控制台 OpenAI/DeepSeek/Ollama ChatModel ChatClient ChatController 浏览器/控制台 #mermaid-svg-Kn4A7222KfLh6EII{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Kn4A7222KfLh6EII .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Kn4A7222KfLh6EII .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Kn4A7222KfLh6EII .error-icon{fill:#552222;}#mermaid-svg-Kn4A7222KfLh6EII .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Kn4A7222KfLh6EII .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Kn4A7222KfLh6EII .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Kn4A7222KfLh6EII .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Kn4A7222KfLh6EII .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Kn4A7222KfLh6EII .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Kn4A7222KfLh6EII .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Kn4A7222KfLh6EII .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Kn4A7222KfLh6EII .marker.cross{stroke:#333333;}#mermaid-svg-Kn4A7222KfLh6EII svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Kn4A7222KfLh6EII p{margin:0;}#mermaid-svg-Kn4A7222KfLh6EII .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Kn4A7222KfLh6EII text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Kn4A7222KfLh6EII .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Kn4A7222KfLh6EII .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Kn4A7222KfLh6EII .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Kn4A7222KfLh6EII .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Kn4A7222KfLh6EII #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Kn4A7222KfLh6EII .sequenceNumber{fill:white;}#mermaid-svg-Kn4A7222KfLh6EII #sequencenumber{fill:#333;}#mermaid-svg-Kn4A7222KfLh6EII #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Kn4A7222KfLh6EII .messageText{fill:#333;stroke:none;}#mermaid-svg-Kn4A7222KfLh6EII .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Kn4A7222KfLh6EII .labelText,#mermaid-svg-Kn4A7222KfLh6EII .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Kn4A7222KfLh6EII .loopText,#mermaid-svg-Kn4A7222KfLh6EII .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Kn4A7222KfLh6EII .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Kn4A7222KfLh6EII .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Kn4A7222KfLh6EII .noteText,#mermaid-svg-Kn4A7222KfLh6EII .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Kn4A7222KfLh6EII .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Kn4A7222KfLh6EII .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Kn4A7222KfLh6EII .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Kn4A7222KfLh6EII .actorPopupMenu{position:absolute;}#mermaid-svg-Kn4A7222KfLh6EII .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Kn4A7222KfLh6EII .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Kn4A7222KfLh6EII .actor-man circle,#mermaid-svg-Kn4A7222KfLh6EII line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Kn4A7222KfLh6EII :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GET /chat?q=你好prompt().user(q).call()组装 Prompt(含角色/参数)HTTP 请求(携带 api-key)模型生成的回复ChatResponsecontent() 取字符串返回回复

要点:你的业务代码只和 ChatClient 打交道 ,ChatClient 背后是 ChatModel,ChatModel 再去调真正的模型服务。换模型(DeepSeek/通义/Ollama)只是换 ChatModel 的实现,业务代码零改动。


十、踩坑 FAQ(新手必看,都是真实报错)

坑 1:启动报 NoSuchMethodError / 类找不到

现象 :一启动就报 java.lang.NoSuchMethodError 或 ClassNotFoundException。

原因 :忘了引入 spring-ai-bom ,导致各 starter 版本互相冲突;或把 starter 写成了旧名 spring-ai-openai-spring-boot-starter。

解决 :确认 pom.xml 里有 dependencyManagement 引入 spring-ai-bom,starter 用 spring-ai-starter-model-openai。

坑 2:报 apiKey must not be null

现象 :启动或调用时抛异常,提示 api key 为空。

原因 :api-key 没配,或环境变量 OPENAI_API_KEY 没设。

解决 :确认 application.yml 里 api-key: ${OPENAI_API_KEY},并在启动前 export OPENAI_API_KEY=sk-xxx。

坑 3:请求返回 404

现象 :能启动,但调用模型报 404。

原因 :base-url 写错了路径------常见是少了 /v1 或多写了一段。

解决 :核对 base-url。DeepSeek 用 https://api.deepseek.com(或其 /v1);通义兼容端点必须带 /compatible-mode/v1。

坑 4:Connection refused 或 Read timed out

现象 :连接被拒或读超时。

原因 :公司/校园网络有代理拦截;或 Ollama 没启动。

解决 :走代理时给 JVM 加代理参数 -Dhttp.proxyHost=... -Dhttp.proxyPort=...;用 Ollama 前先 ollama serve 确保服务在 11434 端口。

坑 5:Boot 3 上跑 Spring AI 2.0 起不来

现象 :把 spring-ai 版本改成 2.0,但 Boot 还是 3.x,启动报兼容错。

原因 :Spring AI 2.0 只支持 Boot 4.0/4.1,和 Boot 3 不兼容。

解决 :要么 Boot 升到 4.1,要么 Spring AI 降到 1.1.x。两者版本必须匹配(详见第 4 篇迁移指南)。


十一、小结与合集导航

这一篇你完成了 Spring AI 2.0 的"从 0 到 1":BOM 版本管理、最小配置、ChatClient 首次调用、三种国内可用方案、五个新手坑。

下一步建议按顺序读:

  • 下一篇 :《ChatClient Fluent API 全拆解》------把 .prompt()/call()/stream() 和消息角色、参数调优彻底讲透
  • 收藏备用:本合集「Spring AI 深入浅出」总目录(文末置顶评论有链接)
  • 源码仓库:本篇完整可运行代码见文末置顶评论的 GitHub 仓库

十二、互动钩子

投票:你准备用哪个模型接入?

  • OpenAI
  • DeepSeek
  • 通义千问
  • 本地 Ollama
  • 其他

评论区晒出你的第一个跑通截图,我会逐条答疑;源码与合集导航已置顶评论,欢迎自取。

相关推荐
neocheng_5221 小时前
AI证书含金量的5个判断维度:考核、学习、应用与认可情况
人工智能
MindUp1 小时前
AI 能帮我做数据分析吗?从使用场景看三类辅助方式
人工智能·数据挖掘·数据分析
Angeldream1231 小时前
第 9 章 医学影像项目实战
大数据·图像处理·人工智能·python·医学影像处理
金海星科技1 小时前
人工智能赋能船舶交通管理中心的值守转型探析
人工智能·语音识别
前沿在线1 小时前
广交会上的人机乒乓:一颗小球,量出具身智能的落地深浅 | 前沿在线
人工智能·ai·前沿在线
2601_965448871 小时前
AI视频制作:做历史视频,用 AE 做人物时间线太难,AI怎么直接生成人物年表动画?
人工智能·音视频
xierui1231231 小时前
视频Agent如何制作英文版:脚本、配音、字幕与画面文字的协同流程
人工智能·音视频
狂师1 小时前
夯爆了!分享几个我在用的,基于业务场景接口串联测试Skill !
人工智能·后端·测试
wasee0011 小时前
三四线城市适合投资XR大空间吗?投入、客流与回本周期测算
大数据·人工智能