パッケージ jakarta.inject

このパッケージは、コンストラクター、ファクトリ、サービスロケーター(JNDI など)などの従来のアプローチと比較して、再利用性、テスト容易性、保守容易性を最大化するような方法でオブジェクトを取得する手段を指定します。 依存性注入と呼ばれるこのプロセスは、ほとんどの重要なアプリケーションにとって有益です。

多くの型は、他の型に依存しています。例: Stopwatch は TimeSource に依存する場合があります。型が依存する型は、その依存関係と呼ばれます。実行時に使用する依存関係のインスタンスを見つけるプロセスは、依存関係の解決と呼ばれます。そのようなインスタンスが見つからない場合、依存関係は満足されていないと見なされ、アプリケーションは壊れています。

依存関係の注入がない場合、オブジェクトはいくつかの方法で依存関係を解決できます。コンストラクターを呼び出し、オブジェクトをその依存関係の実装とライフサイクルに直接ハードワイヤリングできます。

   class Stopwatch {
     final TimeSource timeSource;
     Stopwatch () {
       timeSource = new AtomicClock(...);
     }
     void start() { ... }
     long stop() { ... }
   }

さらに柔軟性が必要な場合、オブジェクトはファクトリまたはサービスロケータを呼び出すことができます。

   class Stopwatch {
     final TimeSource timeSource;
     Stopwatch () {
       timeSource = DefaultTimeSource.getInstance();
     }
     void start() { ... }
     long stop() { ... }
   }

依存関係の解決に対するこれらの従来のアプローチを決定する際、プログラマーはトレードオフを行う必要があります。コンストラクターはより簡潔ですが制限があります。ファクトリはクライアントと実装をある程度切り離しますが、定型コードが必要です。サービスロケーターはさらに分離しますが、コンパイル時の型安全性を低下させます。3 つのアプローチはすべて、単体テストを禁止します。例: プログラマーがファクトリを使用する場合、ファクトリに依存するコードに対する各テストは、ファクトリをモックアウトし、それ自体をクリーンアップすることを忘れないでください。

   void testStopwatch() {
     TimeSource original = DefaultTimeSource.getInstance();
     DefaultTimeSource.setInstance(new MockTimeSource());
     try {
       // Now, we can actually test Stopwatch.
       Stopwatch sw = new Stopwatch();
       ...
     } finally {
       DefaultTimeSource.setInstance(original);
     }
   }

実際には、ファクトリをモックするこの機能をサポートすると、ボイラープレートコードがさらに多くなります。複数の依存関係がすぐに手に負えなくなった後にモックアウトしてクリーンアップするテスト。さらに悪いことに、プログラマーは将来どの程度の柔軟性が必要になるかを正確に予測する必要があります。プログラマーが最初にコンストラクターを使用することを選択したが、後でより高い柔軟性が必要であると判断した場合、プログラマーはコンストラクターへのすべての呼び出しを置き換える必要があります。プログラマーが注意を怠ってファクトリを前もって作成すると、不要な定型コードが大量に発生し、ノイズ、複雑さ、エラーが発生しやすくなります。

依存性注入は、これらすべての課題に対処します。プログラマーがコンストラクターまたはファクトリを呼び出す代わりに、 依存関係インジェクターと呼ばれるツールが依存関係をオブジェクトに渡します。

   class Stopwatch {
     final TimeSource timeSource;
     @Inject Stopwatch(TimeSource timeSource) {
       this.timeSource = timeSource;
     }
     void start() { ... }
     long stop() { ... }
   }

インジェクターは、オブジェクトグラフ全体を構築するまで、依存関係を他の依存関係にさらに渡します。例: プログラマーがインジェクターに StopwatchWidget インスタンスの作成を依頼したとします。

   /** GUI for a Stopwatch */
   class StopwatchWidget {
     @Inject StopwatchWidget(Stopwatch sw) { ... }
     ...
   }

インジェクターは:

  1. TimeSource を見つける
  2. TimeSource で Stopwatch を構築する
  3. Stopwatch で StopwatchWidget を構築する

これにより、プログラマのコードはクリーンで柔軟性があり、依存関係に関連するインフラストラクチャが比較的なくなります。

単体テストでは、プログラマーはオブジェクトを直接(インジェクターなしで)作成し、モックの依存関係を渡すことができます。プログラマーは、各テストでファクトリやサービスロケーターをセットアップしたり分解したりする必要がなくなりました。これにより、単体テストが大幅に簡略化されます。

   void testStopwatch() {
     Stopwatch sw = new Stopwatch(new MockTimeSource());
     ...
   }

単体テストの複雑さの全体的な減少は、単体テストの数と依存関係の数の積に比例します。

このパッケージは、移植可能なクラスを可能にする依存性注入アノテーションを提供しますが、外部依存関係の構成はインジェクター実装に任されています。プログラマーは、コンストラクター、メソッド、フィールドにアノテーションを付けて、それらの注入可能性を宣伝します(コンストラクターの注入は上記の例で示されています)。依存関係インジェクターは、これらのアノテーションをインスペクションしてクラスの依存関係を識別し、実行時に依存関係を注入します。さらに、インジェクターはビルド時にすべての依存関係が満たされていることを確認できます。対照的に、サービスロケータは、実行時まで満たされていない依存関係を検出できません。

インジェクターの実装には多くの形式があります。インジェクタは、XML、アノテーション、DSL(ドメイン固有の言語)、プレーン Java コードを使用してそれ自体を構成できます。インジェクターはリフレクションまたはコード生成に依存する可能性があります。コンパイル時のコード生成を使用するインジェクターは、独自の実行時表現を持たない場合もあります。他のインジェクターは、コンパイル時でも実行時でも、コードをまったく生成できない場合があります。一部の定義では「コンテナー」はインジェクタになる可能性がありますが、このパッケージ仕様はインジェクタ実装の制限を最小限に抑えることを目的としています。

関連事項:
@Inject