Java调用Claude API完整代码(Spring Boot + WebClient + 流式输出)

一句话总结:本文提供Spring Boot调用Claude API的完整代码,覆盖同步调用、SSE流式输出、多轮对话,可直接复制运行。

一、Claude vs OpenAI:为什么选Claude?

Claude特别适合:长文档分析、复杂代码生成、需要严格遵循格式的场景。

二、依赖引入

xml 复制代码
<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot WebFlux(用于WebClient) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <!-- 可选:Spring AI Anthropic(如果官方支持) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
        <version>1.0.0</version>
    </dependency>

    <!-- Lombok(简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

三、配置文件

yaml 复制代码
# application.yml
anthropic:
  api-key: ${ANTHROPIC_API_KEY}
  base-url: https://api.anthropic.com
  version: 2023-06-01  # API版本
  model: claude-3-5-sonnet-20241022  # 最新模型
  max-tokens: 4096
  temperature: 0.7

# 超时配置
spring:
  webflux:
    client:
      connect-timeout: 30000
      read-timeout: 60000

四、核心代码

4.1 配置类:ClaudeClientConfig

kotlin 复制代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Configuration
public class ClaudeClientConfig {

    @Value("${anthropic.api-key}")
    private String apiKey;

    @Value("${anthropic.base-url:https://api.anthropic.com}")
    private String baseUrl;

    @Value("${anthropic.version:2023-06-01}")
    private String apiVersion;

    @Bean
    public WebClient claudeWebClient() {
        return WebClient.builder()
            .baseUrl(baseUrl)
            .defaultHeader("x-api-key", apiKey)
            .defaultHeader("anthropic-version", apiVersion)
            .defaultHeader("Content-Type", "application/json")
            .build();
    }
}

4.2 同步调用:ClaudeChatService

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import lombok.Data;
import java.util.List;

@Service
public class ClaudeChatService {

    @Autowired
    private WebClient claudeWebClient;

    @Value("${anthropic.model:claude-3-5-sonnet-20241022}")
    private String model;

    @Value("${anthropic.max-tokens:4096}")
    private int maxTokens;

    /**
     * 同步单轮对话
     */
    public String chat(String message) {
        ClaudeRequest request = new ClaudeRequest();
        request.setModel(model);
        request.setMaxTokens(maxTokens);
        request.setMessages(List.of(
            new Message("user", message)
        ));

        ClaudeResponse response = claudeWebClient.post()
            .uri("/v1/messages")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(ClaudeResponse.class)
            .block();  // 同步阻塞

        return extractContent(response);
    }

    /**
     * 同步多轮对话
     */
    public String chatWithHistory(List<Message> history, String newMessage) {
        history.add(new Message("user", newMessage));

        ClaudeRequest request = new ClaudeRequest();
        request.setModel(model);
        request.setMaxTokens(maxTokens);
        request.setMessages(history);

        ClaudeResponse response = claudeWebClient.post()
            .uri("/v1/messages")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(ClaudeResponse.class)
            .block();

        // 把AI回复加入历史
        history.add(new Message("assistant", extractContent(response)));

        return extractContent(response);
    }

    private String extractContent(ClaudeResponse response) {
        if (response == null || response.getContent() == null || response.getContent().isEmpty()) {
            return "";
        }
        return response.getContent().get(0).getText();
    }

    // DTO类
    @Data
    public static class ClaudeRequest {
        private String model;
        private int maxTokens;
        private List<Message> messages;
    }

    @Data
    public static class Message {
        private String role;  // "user" or "assistant"
        private String content;

        public Message(String role, String content) {
            this.role = role;
            this.content = content;
        }
    }

    @Data
    public static class ClaudeResponse {
        private String id;
        private String model;
        private List<ContentBlock> content;
        private Usage usage;
    }

    @Data
    public static class ContentBlock {
        private String type;  // "text"
        private String text;
    }

    @Data
    public static class Usage {
        private int inputTokens;
        private int outputTokens;
    }
}

4.3 流式调用:ClaudeStreamService(SSE)

arduino 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.util.List;

@Service
public class ClaudeStreamService {

    @Autowired
    private WebClient claudeWebClient;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 流式对话(SSE)
     * 返回Flux<String>,每个元素是一个token片段
     */
    public Flux<String> chatStream(String message) {
        ClaudeRequest request = new ClaudeRequest();
        request.setModel("claude-3-5-sonnet-20241022");
        request.setMaxTokens(4096);
        request.setMessages(List.of(new Message("user", message)));
        request.setStream(true);  // 关键:开启流式

        return claudeWebClient.post()
            .uri("/v1/messages")
            .bodyValue(request)
            .retrieve()
            .bodyToFlux(String.class)  // 按行读取SSE数据
            .filter(line -> line.startsWith("data:"))  // 只处理data:行
            .map(line -> line.substring(5).trim())  // 去掉"data: "前缀
            .filter(data -> !data.equals("[DONE]"))  // 过滤结束标记
            .map(this::extractDeltaText)
            .filter(text -> text != null && !text.isEmpty());
    }

    /**
     * 从SSE事件中提取文本增量
     */
    private String extractDeltaText(String jsonData) {
        try {
            StreamEvent event = objectMapper.readValue(jsonData, StreamEvent.class);
            if ("content_block_delta".equals(event.getType()) && event.getDelta() != null) {
                return event.getDelta().getText();
            }
            return "";
        } catch (Exception e) {
            return "";
        }
    }

    // 流式事件DTO
    @Data
    public static class StreamEvent {
        private String type;  // "content_block_delta", "message_start", etc.
        private String index;
        private Delta delta;
    }

    @Data
    public static class Delta {
        private String type;  // "text_delta"
        private String text;  // 实际的文本增量
    }

    // 复用ClaudeChatService的DTO
    @Data
    public static class ClaudeRequest {
        private String model;
        private int maxTokens;
        private List<ClaudeChatService.Message> messages;
        private boolean stream;
    }

    @Data
    public static class Message {
        private String role;
        private String content;

        public Message(String role, String content) {
            this.role = role;
            this.content = content;
        }
    }
}

4.4 Controller层:REST API暴露

typescript 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/api/claude")
public class ClaudeController {

    @Autowired
    private ClaudeChatService chatService;

    @Autowired
    private ClaudeStreamService streamService;

    /**
     * 同步对话
     */
    @PostMapping("/chat")
    public Mono<String> chat(@RequestBody ChatRequest request) {
        return Mono.just(chatService.chat(request.getMessage()));
    }

    /**
     * 流式对话(SSE)
     */
    @PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(@RequestBody ChatRequest request) {
        return streamService.chatStream(request.getMessage());
    }

    /**
     * 多轮对话(带历史)
     */
    private final List<ClaudeChatService.Message> conversationHistory = new ArrayList<>();

    @PostMapping("/chat/history")
    public Mono<String> chatWithHistory(@RequestBody ChatRequest request) {
        String response = chatService.chatWithHistory(conversationHistory, request.getMessage());
        return Mono.just(response);
    }

    @PostMapping("/chat/history/clear")
    public Mono<String> clearHistory() {
        conversationHistory.clear();
        return Mono.just("历史记录已清空");
    }

    @Data
    public static class ChatRequest {
        private String message;
    }
}

五、测试验证

5.1 同步调用测试

bash 复制代码
curl -X POST http://localhost:8080/api/claude/chat   -H "Content-Type: application/json"   -d '{"message": "用Java写一个单例模式,要求线程安全"}'

5.2 流式调用测试(SSE)

bash 复制代码
curl -X POST http://localhost:8080/api/claude/chat/stream   -H "Content-Type: application/json"   -d '{"message": "讲一个程序员笑话"}'

# 输出:
# data: 有
# data: 一
# data: 个
# data: 程序员...

5.3 前端EventSource消费

ini 复制代码
const eventSource = new EventSource('/api/claude/chat/stream', {
  method: 'POST',
  body: JSON.stringify({message: '你好'})
});

eventSource.onmessage = (event) => {
  console.log('收到:', event.data);
  appendToUI(event.data);  // 追加到页面
};

eventSource.onerror = () => {
  console.log('连接结束');
  eventSource.close();
};

六、与OpenAI对比

typescript 复制代码
@Service
public class AIChatService {

    @Autowired
    private ClaudeChatService claudeService;

    // 可切换的AI Provider
    public String chat(String provider, String message) {
        switch (provider) {
            case "claude":
                return claudeService.chat(message);
            case "openai":
                // return openAIService.chat(message);
            default:
                return claudeService.chat(message);
        }
    }
}

七、常见问题

7.1 403 Forbidden

Claude API需要申请访问权限,新账户可能无法直接调用。

解决:在Anthropic官网申请API访问,或使用第三方代理。

7.2 流式输出乱码

SSE事件格式与OpenAI不同,注意解析content_block_delta事件。

7.3 上下文长度超限

Claude 3.5支持200K tokens,但超过后会被截断。

解决:实现滑动窗口历史管理,只保留最近N轮对话。

八、完整项目结构

bash 复制代码
claude-java-demo/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/claude/
│   │   │       ├── ClaudeJavaDemoApplication.java
│   │   │       ├── config/
│   │   │       │   └── ClaudeClientConfig.java
│   │   │       ├── service/
│   │   │       │   ├── ClaudeChatService.java
│   │   │       │   └── ClaudeStreamService.java
│   │   │       └── controller/
│   │   │           └── ClaudeController.java
│   │   └── resources/
│   │       └── application.yml
│   └── test/
│       └── java/
│           └── ClaudeChatServiceTest.java

👤 关于作者

JavaAgent架构师 --- 十年Java分布式架构老兵,专注AI Agent企业级落地。

主导过数字员工、SOP智能引擎等项目,开发过RPC框架、消息中间件、ORM框架。

📚 正在输出三个专栏

  • 前端AI工程化》--- SSE/流式渲染/Function Calling/企业级架构
  • 《浩哥学JavaAI:35岁架构师转型日记》--- 探索AI副业变现、让AI提升能力

让Java开发者不转Python,让前端工程师掌握AI核心竞争力。

📮 关注专栏 |🔔 点赞收藏 |💬 评论区见

相关推荐
萤丰信息1 小时前
破局提质,共生发展——智慧园区建设的痛点破解与未来方向
大数据·人工智能·智慧城市
北京自在科技1 小时前
苹果官宣 WWDC 2026:AI 重构 Siri,全新系统即将登场
人工智能·重构·wwdc
运营小白1 小时前
Google 2026年3月核心更新:电商SEO的自动化分水岭
人工智能·seo工具·效率提升·seonib·搜索流量·google搜索
子兮曰1 小时前
GEO 生成式引擎优化完全指南:让你的内容成为 AI 的默认答案
前端·后端·seo
kangsf19891 小时前
AI(人工智能) 领域常见的专业名词
人工智能
weilaieqi11 小时前
从济南利客行,看固驰城市旗舰店如何真正落地
人工智能
志栋智能1 小时前
从单点检查到全景监控:巡检超自动化的维度拓展
运维·服务器·网络·人工智能·自动化
EasyDSS1 小时前
私有化视频会议系统/智能会议管理系统EasyDSS以数据能力重构视频会议协同新生态
大数据·人工智能·重构
木雷坞1 小时前
小团队 CI runner 排队:从镜像拉取到缓存策略的排查记录
后端