インスタンスの生成と利用
あるクラス内で別のクラスの機能を利用する場合、利用したいクラスのインスタンスを生成する必要があります。Spring Frameworkを使用する場合、このインスタンスの取得方法には主に2通りあります。
①Spring DIコンテナを利用する場合(通常の使用法)
②Javaの標準的なnew演算子を利用する場合
参考:
Spring FrameworkのDIコンテナってなに?メリットは?
①Spring DIコンテナを利用する場合(通常の使用法)
クラスに@Serviceや@Componentなどのアノテーションが付いていて、Spring Beanとして扱われる場合:
-
引数がすべてDIコンテナ内に存在する(Beanである)
→開発者が引数を指定する必要はない。Springがコンテナ内で引数の型に合うBeanを自動で探し出し、コンストラクタに渡してインスタンス化します。 -
引数にDIコンテナ内に存在しないものがある
→Springは通常、インスタンス化に失敗します(適切なBeanが見つからないため)。カスタムファクトリやプロバイダを使用して、非Beanを供給する特別な設定をしない限り、自動的なインスタンス化はできません。
DI注入の2つの方法
1.フィールドインジェクション
UserService クラスは、JpaUserRepository のインスタンスを自分でnewしません。代わりに、フィールドとして宣言し、@Autowired アノテーションを付けます。
(Spring Frameworkのバージョン4.3以降では、DIコンテナが管理するクラス(Bean)のコンストラクタが一つだけの場合に、@Autowiredアノテーションを省略できます。)
// 利用側:UserService.java
@Service
public class UserService {
// Springに「UserRepositoryのインスタンスを入れて!」とお願いする
@Autowired
private UserRepository userRepository;
// ↑ ここで new JpaUserRepository() は書かない
public User getUserData(String email) {
// 注入された(渡された)インスタンスのメソッドを呼び出す
return userRepository.findByEmail(email);
}
}
なぜこれが使えるのか?
UserService の実行前に、Spring DIコンテナが以下の処理を自動で行います。
-
@Service が付いた
UserServiceを読み込む。 -
@Autowired が付いた
userRepositoryフィールドを見つける。 -
UserRepositoryインターフェースを実装したクラス(JpaUserRepository)のインスタンスを自動で生成する。 -
その生成したインスタンスを
userRepositoryフィールドに渡す(注入する)。
開発者は「どのような機能(インターフェース)を使いたいか」だけを宣言すればよく、「どうやって作るか(具象クラス)」を気にしなくて済みます。
2.コンストラクタインジェクション
コンストラクタインジェクションでは、依存関係にあるクラス(この例では UserRepository)をクラスのコンストラクタの引数として受け取ります。
// 利用側:UserService.java
@Service
public class UserService {
// 依存関係にあるフィールドをfinal宣言する(不変にする)
private final UserRepository userRepository;
// コンストラクタインジェクション
// コンストラクタの引数としてUserRepositoryを受け取る
// ※ Spring Boot 2.3以降、コンストラクタが一つしかない場合は@Autowiredを省略可能
public UserService(UserRepository userRepository) {
// 受け取ったインスタンスをフィールドに設定する
this.userRepository = userRepository;
}
public User getUserData(String email) {
// 注入された(渡された)インスタンスのメソッドを呼び出す
return userRepository.findByEmail(email);
}
}
なぜこれが推奨されるのか?
コンストラクタインジェクションには、フィールドインジェクション(@Autowired をフィールドに直接つける方法)にはない、いくつかの大きなメリットがあります。
-
依存関係の明確化 (Visibility)
コンストラクタを見れば、そのクラス(UserService)が動作するために最低限必要な依存関係がすべて一目でわかります。これはクラスの設計を明確にする上で非常に重要です。 -
不変性の保証 (Immutability)
依存オブジェクト(userRepository)をfinalキーワードで宣言できます。これにより、一度コンストラクタで初期化された後、そのインスタンスが途中で変更されることがなくなり、コードの安全性が向上します。 -
テストの容易性 (Testability)
DIコンテナを使わない純粋なJavaの単体テストを行う際に、コンストラクタを使って簡単に依存オブジェクト(テスト用のモックなど)を渡せるため、テストコードがシンプルになります。
// コンストラクタインジェクションなら、テスト時に簡単に依存オブジェクトを渡せる
UserRepository mockRepo = mock(UserRepository.class); // モックを作成
UserService userService = new UserService(mockRepo); // テスト対象のインスタンスを生成
② new 演算子による手動での生成
これはJavaの標準的な方法です。利用側が直接 new を使ってインスタンスを生成します。
具体例
ユーザーが送信したフォームのデータ(一時的なデータ)を格納するオブジェクト(DTOやPOJO)を生成する場合などです。
// データ格納用:UserRequestDto.java
public class UserRequestDto {
private String name;
private String email;
// ... getter, setter
}
// 利用側(例:Controllerなど)
@RestController
public class UserController {
public String registerUser(String name, String email) {
// 開発者がコード内で直接インスタンスを生成する
UserRequestDto userDto = new UserRequestDto();
userDto.setName(name);
userDto.setEmail(email);
// ... このDTOをサービス層などに渡す
return "登録処理を開始しました";
}
}
使い分け
-
サービス、リポジトリ、コントローラーなどのアプリケーションの主要なロジックを担うコンポーネント(
Spring Bean)は、① DIコンテナで管理すべきです。 -
データ転送オブジェクト(
DTO)、一時的な設定オブジェクトなど、特定の処理のためだけに生成され、特別なライフサイクル管理が不要なオブジェクトは、②new演算子で手動で生成することが一般的です。
まとめ
このように、DIコンテナは内部的(アプリケーション起動時)にインスタンス化することで、、複雑な依存関係を開発者から隠蔽し、アプリケーションの柔軟性(疎結合)、保守性、テストの容易性を劇的に向上させる、現代のJavaエンタープライズ開発における必須の基盤技術です。