Skip to content
Playground

インターフェイスと DI

サービスとリポジトリ で組み立てた 3 層は、BookRepository をインターフェイスにし、DI コンテナーが実装を注入する形にできます。

BookService は、データアクセスの実装クラスである BookRepository に直接依存しています。インターフェイス では、GradeJudge をインターフェイスに変えて実装クラスを分けると、利用側を変えずに実装を差し替えられると述べました。BookRepository にも同じ形を適用します。

1-1. インターフェイスと実装の分離

Section titled “1-1. インターフェイスと実装の分離”

メソッドの宣言だけを BookRepository インターフェイスとして残し、JDBC の実装は JdbcBookRepository に分けます。

BookRepository.java
package com.example.bookstore;
import java.util.List;
public interface BookRepository {
List<Book> findAll();
void save(Book book);
boolean existsByTitle(String title);
}
JdbcBookRepository.java
@Repository
public class JdbcBookRepository implements BookRepository {
private final DataSource dataSource;
public JdbcBookRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
// findAll・save・existsByTitle の本体は、インターフェイスにする前の BookRepository と同じ
}

@Repository は実装クラスの JdbcBookRepository に移ります。implements BookRepository と各メソッドへの @Override を加えるだけで、メソッドの本体は変わりません。BookService のフィールドの型は BookRepository(インターフェイス)のままで、BookService のコードは修正不要です。

BookService が受け取るのは BookRepository 型ですが、Spring Boot が注入するのは実装の JdbcBookRepository です。インターフェイス型のフィールドに実装のインスタンスを代入できるのは、多態性 によるものです。

new を書かずに実装が注入されるのは、Spring Boot の DI コンテナーが組み立てを担うためです。

Spring Boot が生成・管理するオブジェクトを Bean と呼びます。@Controller@Service@Repository の付いたクラスは、起動時に Bean として登録されます。Bean を保持し、依存を解決して注入する仕組みを DI コンテナーApplicationContext)と呼びます。

起動時、コンポーネントスキャンが Bean を見つけてコンテナーに登録します。コンテナーは各 Bean のコンストラクターの引数をたどって依存関係を調べ、必要な Bean から順に生成して注入します。Bean は既定で 1 つだけ生成され、アプリケーション全体で共有されます。

BookService のコードには、BookRepository(インターフェイス)の型だけが現れ、実装クラスの名前は現れません。そのため、コンテナーに登録する実装を入れ替えれば、BookService を変えずに取得方法を変えられます。たとえばテストでは、データベースにアクセスせずメモリ上のリストで動く実装を渡せば、BookService の検証ルールだけを、データベースを起動せずに素早く確かめられます。

どの実装を注入するかを外から決められることが、依存性の注入の利点です。これは インターフェイス で扱った実装の差し替えを、DI コンテナーが行う仕組みです。