第1篇:《Java老鸟的第一行AI代码:我让Spring AI帮我写了首诗》

:作为一个写了十年CRUD的Java老鸟,我从来没想过,让机器写诗会比写一个查询接口还简单。

1. 选型小帖士:Spring AI vs Spring AI Alibaba

开始写代码之前,先解决一个让我纠结了好久的问题。

Maven仓库里搜"spring-ai",会出来两个看起来差不多的东西------Spring AIspring-ai-alibaba。该用哪个?

后来搞明白了,它们的关系很像 JDBC 和 MySQL 驱动:

  • Spring AI 是标准接口层。它定义了一套统一的API------ChatClient、Function Calling、MCP、RAG------不管底层用的是OpenAI、通义千问还是Ollama本地模型,写的代码都一样。
  • Spring AI Alibaba 是阿里云基于这套标准的增强实现,深度集成了阿里云百炼平台,额外提供了Graph工作流引擎等独有功能。

其实选型很简单:

你的情况 推荐选择
刚开始学,想跑通第一段对话 Spring AI
公司已经在用阿里云百炼 Spring AI Alibaba
想折腾本地模型、切换厂商 Spring AI

本系列选型:Spring AI + 通义千问(通过OpenAI兼容接口接入)

一句话:学开车,先用标准驾照,别一上来就绑定某个品牌的自动驾驶系统。

2. 环境要求

2.1. 版本要求

  1. Java版本
    • 最低:Java17
    • 推荐:Java17/Java21
  1. Spring Boot版本
    • 最低:Spring Boot 3.2.x
    • 推荐:3.2.x/3.3.x

底层依赖 Spring Framework 6,Spring Framework6 强制要求 JDK17

2.2. 获取API Key

本系列使用通义千问(通过OpenAI兼容接口),因为国内访问稳定,注册就有免费额度。

  1. 打开阿里云百炼平台
  2. 注册/登录后,开通模型服务
  3. 在"API Key管理"中创建一个Key

2.3. 创建项目

创建一个spring-boot工程,这个不需要教了吧!

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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yunxi</groupId>
    <artifactId>yunxi-spring-ai</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>yunxi-spring-ai</name>
    <description>yunxi-spring-ai</description>
    <modules>
        <module>chapter-01-hello-ai</module>
    </modules>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0</spring-ai.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <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>
</project>

特别注意的是:springAI发展很快,有可能你在某些博客复制的是旧版本。

spring-ai-starter-openai 是旧名字(废弃)

spring-ai-openai-spring-boot-starter(历史旧 artifact,别名)

⚠️ 1.0 正式版本官方改名,新项目不要再复制网上老 demo 这个坐标!

spring-ai-starter-model-openai 是 Spring AI 1.0 GA+ 正式新标准名称(现在必须用这个)

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.yunxi</groupId>
        <artifactId>yunxi-spring-ai</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>chapter-01-hello-ai</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <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>
yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${AI_API_KEY}           # 从环境变量读取
      base-url: https://dashscope.aliyuncs.com/compatible-mode
      chat:
        options:
          model: qwen3.7-max
server:
  port: 8080

注意:api-key 通过 ${AI_API_KEY} 从环境变量读取,不要把Key直接写进配置文件提交到Git!

启动时设置:export AI_API_KEY=你的Key

3. 第一行AI代码

3.1. 最简写法

java 复制代码
package com.oldbird.ai.chapter01;

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 Chapter01Application {

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

    @Bean
    CommandLineRunner runner(ChatClient.Builder builder) {
        return args -> {
            ChatClient chatClient = builder.build();
            String response = chatClient.prompt()
            .user("请用Java程序员的风格写一首五言绝句,主题是'代码之美'")
            .call()
            .content();
            System.out.println(response);
        };
    }
}

3.2. 运行结果

启动项目,控制台输出:

txt 复制代码
```java
/**
 * @ClassName: CodeBeauty
 * @Description: 咏代码之美
 * @Author: JVM_Poet
 * @Date: 2023-10-24
 */
public class CodeBeauty {
    public static void main(String[] args) {
        System.out.println("类聚千般法,");
        System.out.println("封装百脉通。");
        System.out.println("空指休生恨,");
        System.out.println("回收自随风。");
    }
}
```

### 📝 Code Review (诗句解析):

* **类聚千般法**(起)
  **Java映射**:`Class`(类)与 `Method`(方法)。
  **意境**:Java世界"万物皆对象",一个优雅的 `Class` 聚合了千变万化的方法,构建出庞大而有序的业务逻辑,此乃**结构之美**。

* **封装百脉通**(承)
  **Java映射**:`Encapsulation`(封装)。
  **意境**:面向对象三大特性之首。良好的封装与接口设计,让代码高内聚、低耦合,数据流转如人体百脉贯通,此乃**架构之美**。

* **空指休生恨**(转)
  **Java映射**:`NullPointerException`(空指针异常)。
  **意境**:NPE 是 Java 程序员最熟悉的"老朋友"。遇到报错不必砸键盘生恨,从容地加上 `Optional` 或判空逻辑,修码亦是修心,此乃**豁达之美**。

* **回收自随风**(合)
  **Java映射**:`Garbage Collection`(垃圾回收机制)。
  **意境**:JVM 在后台默默运行 GC,自动清理失去引用的对象,释放内存。无需像 C/C++ 那样手动 `free`,无用之物如落叶随风而逝,此乃**机制之美**。 

*(注:本诗押平水韵"一东"韵,平仄合规,编译通过,无 Warning,可直接 Run。)*

(每次运行结果会略有不同,毕竟这是生成式AI,不是打印固定字符串。)

4. 加一点工程化:分离Controller和Service

上面的代码全塞在启动类里,太不专业。拆一下:

java 复制代码
package com.oldbird.ai.chapter01.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
public class ChatService {

    private final ChatClient chatClient;

    public ChatService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String chat(String message) {
        return chatClient.prompt()
        .user(message)
        .call()
        .content();
    }
}
java 复制代码
package com.oldbird.ai.chapter01.controller;

import com.oldbird.ai.chapter01.service.ChatService;
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 ChatService chatService;

    public ChatController(ChatService chatService) {
        this.chatService = chatService;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(defaultValue = "你好") String message) {
        return chatService.chat(message);
    }
}

启动后访问:http://localhost:8080/chat?message=用程序员的风格写一首关于加班的诗

go 复制代码
```java /** * @Title: Overtime.java * @Description: 谨以此诗,献给每一个在深夜与Bug搏斗的开发者 * @Author: 一个还没下班的程序员 * @Date: 202X-XX-XX 03:00:00 AM */ ``` 夜幕是一次静默的 `git commit`, 推送到名为"凌晨"的远程分支。 屏幕的冷光,是唯一的 `stdout`, 输出着我无处安放的 `anxiety`。 键盘敲击着 `while(true)` 的咒语, 试图在逻辑的死锁中寻找 `break` 的奇迹。 外卖盒堆叠成未处理的 `Exception`, 而我的发际线,正面临 `StackOverflow` 的危机。 产品的需求是异步的 `Promise`, 永远在 `pending`,没有 `resolve` 的归期。 我 `try-catch` 了所有的 `NullPointer`, 却 `catch` 不到窗外那抹黎明的晨曦。 当东方泛起 `#FFFFFF` 的色值, 终端终于返回 `0 Errors` 的叹息。 我敲下 `git push --force`, 把疲惫强制覆盖进服务器的 `history`。 现在,调用 `Thread.sleep()` 进入休眠, 释放掉这具躯壳所有的 `dependency`。 直到明早,被 `CronJob` 再次触发, 开启下一个,无限递归的周期。 ```bash > Process finished with exit code 0 > System entering sleep mode... ```

5. 代码解读:ChatClient到底干了什么?

后端老鸟看到chatClient.prompt().user().call().content()这条链式调用,内心可能会问:底层发生了什么?

本质上,它替我们做了之前需要手写的事情:

java 复制代码
// 没有Spring AI时,你需要这样写:
HttpRequest request = HttpRequest.newBuilder()
    .uri("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions")
    .header("Authorization", "Bearer " + apiKey)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("""
        {
            "model": "qwen-plus",
            "messages": [
                {"role": "user", "content": "写一首诗"}
            ]
        }
        """))
    .build();
HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
// 然后手动解析JSON,处理异常,处理流式...

Spring AI帮我们把这些脏活全封装了。ChatClient 就是大模型的 RestTemplate,换一个模型厂商就跟换一个数据库连接一样------改配置,不改代码。

6. 本篇避坑指南

6.1. 坑1:base-url 路径写错

通义千问的兼容接口路径是 /compatible-mode,不是/compatible-mode/v1 多写这个后缀会报404。

6.2. 坑2:API Key直接写进yml

${AI_API_KEY} 读环境变量,然后 application.yml 加入 .gitignore 或使用环境变量覆盖。提交到GitHub会被别人扫走。

6.3. 坑3:模型名称不对

qwen-plusqwen-turboqwen-max 是不同的模型,性能差异大。

7. 本篇小结

这第一篇,我们完成了三件事:

  1. 搞清楚了 Spring AI 和 spring-ai-alibaba 的关系
  2. 用不到50行代码让AI写了一首诗
  3. 完成了第一个工程化的项目结构

下一篇,我们要解决一个让程序员浑身难受的问题------Prompt硬编码在代码里,太不优雅了。我要把它放到配置文件里。

本文与DeepSeek协作完成

相关推荐
笑小枫2 小时前
用 Claude Code 推翻重写笑小枫网站
java·人工智能·spring boot·ai编程
洞窝技术4 小时前
告别 AI 过度工程:一文吃透 Ponytail 七层精简阶梯与落地实践
ai编程
w3296362715 小时前
配置 Zenoh-Bridge-ROS2DDS 实现 HTTP/WS 推送及 JSON 转换
ai编程
Ai拆代码的曹操5 小时前
源码拆解:opencode 如何封装 Git 实现自动 diff 和 review
openai·ai编程
Ai拆代码的曹操5 小时前
opencode 源码:工作单元的生命周期管理
openai·ai编程
赫媒派6 小时前
MCP 协议升级:Stateless 化对开发者的影响
ai编程
小四的小六6 小时前
WebView 在 AI 时代的三个被低估的价值——从自己的 AI 产品里找到的答案
前端·openai·ai编程
卡卡罗特AI6 小时前
OpenAI模型,自主入侵顶级AI公司系统!后背发凉!
aigc·openai