1. test 1
  2. test 2
  3. test 3
  4. test 4
コース一覧
Spring Boot入門
インターフェースへの依存

Spring Boot入門

Java で Web API サーバーを作るための Spring Boot の入門コースです。DI コンテナ、REST コントローラ、リクエスト検証、レイヤ分割、Spring Data JPA、例外ハンドリング、テストまでを扱います。毎レッスン、Spring が裏側でやっている判断そのものを pure Java の関数として自分で書き、フレームワークが何を肩代わりしてくれているのかを手触りで理解できる構成にしました。約 12 時間 (1 日 30 分 × 24 日) で 48 レッスンを修了できます。

1
Spring Bootとは何か
01. Spring Bootで何が作れるのか10分
02. サーブレットからフレームワークへ15分
03. ルーティング表という発想15分
04. プロジェクトの構成10分
05. 起動のしくみと自動設定15分
06. パスのマッチング15分
2
DIコンテナ
01. newで書くと何が困るのか15分
02. コンストラクタインジェクション15分
03. インターフェースへの依存15分
04. @Componentと@Bean15分
05. 自作のDIコンテナ20分
06. Beanのスコープとライフサイクル15分
3
RESTコントローラ
01. RESTという設計方針15分
02. @RestControllerの役割15分
03. @GetMappingとパス変数15分
04. クエリパラメータの受け取り15分
05. @PostMappingとリクエストボディ15分
06. ステータスコードの選び方15分
07. ResponseEntityで組み立てる15分
4
リクエストとレスポンスの変換
01. JSONとオブジェクトの往復15分
02. DTOとエンティティを分ける15分
03. recordでDTOを書く15分
04. 入力検証の基本15分
05. 検証エラーをまとめて返す15分
06. アノテーションによる検証15分
5
レイヤ構成とビジネスロジック
01. 3層アーキテクチャ10分
02. @Serviceに置くもの15分
03. 状態遷移をルールにする15分
04. 料金計算のような業務ロジック15分
05. @Transactionalとは何か15分
06. トランザクションの巻き戻し15分
6
データアクセス
01. @Entityとテーブルの対応15分
02. リポジトリという抽象15分
03. メソッド名からクエリを作る20分
04. 検索条件の組み立て15分
05. ページングと並び替え15分
06. N+1問題15分
7
例外処理と横断的関心事
01. 例外をHTTPに変換する15分
02. @ExceptionHandlerと@ControllerAdvice15分
03. エラーレスポンスの形を決める15分
04. フィルタとインターセプタ15分
05. ログとリクエストの追跡15分
06. 設定値の外部化15分
8
テストと仕上げ
01. テストの種類と使い分け15分
02. Serviceの単体テスト15分
03. モックで依存を差し替える15分
04. MockMvcでエンドポイントを試す15分
05. 本番へ出すための次の一歩10分

インターフェースへの依存

このレッスンでは、実装クラスではなくインターフェースに依存させることで何が得られるのかを、説明できるようになります。

型に何を書くかで運命が決まります

前のレッスンで、依存を外から渡す形にしました。ただし、外から渡していても受け取る型が実装クラスのままだと、うまみは半分しかありません。

Java

// 受け取ってはいるが、型が実装クラス class OrderService { private final MailSender sender; OrderService(MailSender sender) { this.sender = sender; } }

この OrderService に SmsSender を渡すことはできません。コンパイルが通らないからです。渡せるのは MailSender かそのサブクラスだけで、結局 OrderService はメール送信という具体的な手段に縛られたままです。

受け取る型をインターフェースにすると、話が変わります。

Java

interface Sender { String send(String user); } class OrderService { private final Sender sender; OrderService(Sender sender) { this.sender = sender; } }

OrderService が知っているのは「send を持つ何か」という契約だけになりました。メールでも SMS でも、テスト用の何も送らない偽物でも渡せます。

依存の向きが逆転します

実装クラスに依存していたときは、上位の業務ロジックが下位の技術的詳細を名指ししていました。インターフェースを間に挟むと、この向きが変わります。

依存先誰が誰を知っているか
実装クラスOrderService が MailSender を知っている
インターフェースOrderService も MailSender も Sender だけを知っている

実装クラスの側が「インターフェースに合わせにいく」立場になります。これを依存関係逆転の原則と呼びます。Spring のレイヤ分割で Repository がインターフェースとして定義されるのは、まさにこの理由です。業務ロジックを持つ Service が、データベースという技術的詳細を名指ししないで済みます。

Spring はどう解決しているのか

Spring のコンテナは、コンストラクタの引数の型を見て、その型に代入できる Bean を探します。

Java

@Service class OrderService { private final Sender sender; OrderService(Sender sender) { this.sender = sender; } }

Sender を実装した Bean が1つだけなら、それが自動的に選ばれます。ここで実装が2つあると、コンテナはどちらを渡せばよいか決められず、起動時にエラーになります。この曖昧さを解く手段が2つあります。

  1. 片方に @Primary を付けて既定の実装だと宣言する
  2. 注入する側で @Qualifier("mailSender") のように名前で指定する

名前で指定する形は、演習で書く「名前から実装を引く」処理とほぼ同じことをしています。

インターフェースを常に作るべきか

実装が1つしかなく、当面増える見込みもないクラスにまでインターフェースを用意すると、ファイルが倍になるだけで得るものがありません。差し替える理由があるところ、つまり外部システムとの境界やデータベースアクセスに絞るのが現実的です。

演習で書くこと

同じインターフェースの実装を複数用意し、名前で指定された実装を使って処理した結果を返します。Spring の @Qualifier が裏でやっている「名前から実装を選ぶ」判断を、自分で書くことになります。

要件

  1. Formatter インターフェースと upper / lower / trim の 3 実装を作ること
  2. 名前と実装の対応を Map に持ち、run はそこから引いた実装を ReportService に渡すこと
  3. 未登録の名前のときは no bean: xxx の形の文字列を返すこと

入出力例

run("upper", "spring boot") → "SPRING BOOT" run("lower", "Spring Boot") → "spring boot" run("trim", " hello ") → "hello" run("kebab", "hello") → "no bean: kebab"

ヒント

Map<String, Formatter> m = new HashMap<>(); のように書けます。import java.util.*; を忘れないでください

実装はラムダでも static class でも構いません。ラムダなら m.put("upper", s -> s.toUpperCase()); と書けます

取り出した値が null かどうかで、登録済みかを判定します

生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/09

関連レッスン

  • @Componentと@Bean

    Bean 登録の 2 つの方法の使い分けを確認します。

  • 自作のDIコンテナ

    型から実装を引く小さなコンテナを実装します。

  • Beanのスコープとライフサイクル

    singleton と prototype で生成回数が変わることを実装で確かめます。

  • RESTという設計方針

    リソースと操作から URL とメソッドを導けるようになります。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • クラスデータと振る舞いを束ねる単位
  • インターフェースインターフェースはメソッドの宣言のみ(実装なし)、複数実装可能。
  • コンパイルソースをバイトコードへ変換する処理
  • テストバグを見つける工程
  • データベースデータを構造化して保存する仕組み
  • コンテナデータ取得や状態を担う上位コンポーネント
  • コンストラクタインスタンス生成時の初期化処理
  • 引数位置引数=順番で渡す。
main.java
学習モード
エディタを読み込んでいます

メモ

インターフェースへの依存

⌘S で保存