はじめに
前回の記事
では、Spring AI の ChatClient を使って Gemini と会話する最小構成(GETリクエスト・クエリパラメータ渡し)を作りました。
今回はその発展として、以下の2点を変更した構成を作ってみます。
- リクエストを GET から POST に変更し、質問をJSONボディで受け取る
- Controllerが
ChatClientを直接扱うのをやめ、Service層(インターフェース+実装) に切り出す
コード量はそこまで増えませんが、実際のアプリケーションに組み込むことを意識した形に一歩近づけます。
前提環境
前回と同様です。
- Eclipse Version: 2025-12 (4.38.0)(Spring Tools がインストール済み)
- JDK 21
- Spring Boot 4.1.1 / Spring AI 2.0.1
- ビルドツール:Gradle
1. 前回からの変更点
前回はコントローラーが直接 ChatClient を持っていましたが、今回は次のような役割分担にします。
| クラス | 役割 |
|---|---|
Question / Answer
|
質問・回答を表す record
|
SpringAiService |
「質問を受け取って回答を返す」ことを表すインターフェース |
SpringAiSample02Service |
SpringAiService の実装。Gemini とのやり取りを担当 |
AskController |
HTTPリクエストの受け口。SpringAiService に処理を委譲するだけ |
こうしておくと、たとえば「Geminiではなく別のAIプロバイダーを試したい」「テストのときはモックに差し替えたい」といった場合に、AskController を一切変更せずに済みます。今回はこの構成にしておくことで、将来の拡張の土台を作っておく狙いもあります。
2. 実装
プロジェクトの作り方(Eclipseでの新規プロジェクト作成、application.properties の書き方)は前回と同じなので割愛します。今回は application.properties も前回と同じ内容です。
spring.application.name=SpringAISample02
spring.ai.google.genai.api-key={AIza...から始まるAPIキー}
spring.ai.google.genai.chat.model=gemini-3.1-flash-lite
2.1 Question / Answer
質問と回答を表す、シンプルな record です。
package com.example.springaisample;
public record Question(String question) {
}
package com.example.springaisample;
public record Answer(String answer) {
}
@RequestBody / produces="application/json" によって、これらは自動的にJSONへ変換・JSONから変換されます。
2.2 SpringAiService(インターフェース)
「質問を受け取り、回答を返す」という処理内容だけを定義したインターフェースです。
package com.example.springaisample;
public interface SpringAiService {
Answer askQuestion(Question question);
}
2.3 SpringAiSample02Service(実装)
Gemini とのやり取りを行う実装クラスです。中身は前回のコントローラーにあった処理とほぼ同じで、置き場所がServiceクラスに変わっただけです。
package com.example.springaisample;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class SpringAiSample02Service implements SpringAiService {
private final ChatClient chatClient;
public SpringAiSample02Service(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@Override
public Answer askQuestion(Question question) {
var answerText = chatClient.prompt()
.user(question.question())
.call()
.content();
return new Answer(answerText);
}
}
@Service を付けることで、Spring のDIコンテナに SpringAiService の実装として登録されます。コンストラクタで受け取る ChatClient.Builder は、前回同様 Spring AI が自動構成してくれるものです。
2.4 AskController
前回の @GetMapping の代わりに @PostMapping を使い、クエリパラメータの代わりに @RequestBody でJSONボディを受け取ります。
package com.example.springaisample;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AskController {
private final SpringAiService springAiService;
public AskController(SpringAiService springAiService) {
this.springAiService = springAiService;
}
@PostMapping(path = "/ask", produces = "application/json")
public Answer ask(@RequestBody Question question) {
return springAiService.askQuestion(question);
}
}
コントローラーは SpringAiService(インターフェース)にだけ依存しており、Gemini を呼んでいることすら知りません。これがService層に切り出したメリットです。
2.5 SpringAiSample02Application(自動生成)
Spring Initializr / Eclipseのプロジェクト作成時に自動生成されるエントリーポイントです。特に変更はありません。
package com.example.springaisample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringAiSample02Application {
public static void main(String[] args) {
SpringApplication.run(SpringAiSample02Application.class, args);
}
}
ソースコードは下記を参照してください。
3. 実行
Eclipseから「Spring Boot アプリケーション」として実行します。
起動できたら、ターミナルから curl でPOSTリクエストを送ってみます。
curl localhost:8080/ask \
-H"Content-type: application/json" \
-d'{"question":"なぜ、犬はおなかを見せるのでしょうか?"}'
以下のように、JSON形式で回答が返ってきます。
まとめ
今回は、前回作成した最小構成を以下の2点で書き換えました。
- GET+クエリパラメータ → POST+JSONボディ
- コントローラー直書き → インターフェース+実装によるService層への切り出し
ChatClient を扱う部分がServiceクラスに閉じ込められたことで、コントローラー側はGeminiを使っていることすら意識しない、見通しの良い構成になりました。

