JavaにおけるCDI(コンテキストと依存性注入)の紹介
1概要
CDI(Contexts and Dependency Injection)は、Java EE 6以降に含まれる標準のhttps://en.wikipedia.org/wiki/Dependency__injection[Dependency injection]フレームワークです。
ドメイン固有のライフサイクルコンテキストを介してステートフルコンポーネントのライフサイクルを管理し、タイプセーフな方法でクライアントオブジェクトにコンポーネント(サービス)を注入することができます。
このチュートリアルでは、CDIの最も関連する機能を詳しく調べ、クライアントクラスに依存関係を注入するためのさまざまなアプローチを実装します。
2 DYDI(日曜大工依存インジェクション)
一言で言えば、フレームワークに頼らずにDIを実装することは可能です。
このアプローチは、DYDI(DIY依存性注入)として広く知られています。
DYDIでは、必要な依存関係を単純な古いファクトリ/ビルダーを介してクライアントクラスに渡すことによって、アプリケーションコードをオブジェクトの作成から分離しています。
基本的なDYDI実装は次のようになります。
public interface TextService {
String doSomethingWithText(String text);
String doSomethingElseWithText(String text);
}
public class SpecializedTextService implements TextService { ... }
public class TextClass {
private TextService textService;
//constructor
}
public class TextClassFactory {
public TextClass getTextClass() {
return new TextClass(new SpecializedTextService();
}
}
もちろん、DYDIは比較的単純なユースケースに適しています。
サンプルアプリケーションのサイズと複雑さが増し、相互接続されたオブジェクトのより大きなネットワークを実装すると、大量のオブジェクトグラフファクトリで汚染されることになります。
これはオブジェクトグラフを作成するためだけに多くの定型コードを必要とします。これは完全にスケーラブルなソリューションではありません。
DIをもっと良くできますか?もちろん、我々はできます。これがまさにCDIが登場する場所です。
3簡単な例
-
CDIはDIを非常に簡単なプロセスに変え、いくつかの単純なアノテーションでサービスクラスを装飾し、クライアントクラスで対応する注入ポイントを定義することになりました。
CDIが最も基本的なレベルでDIを実装する方法を示すために、簡単な画像ファイル編集アプリケーションを開発したいとしましょう。開く、編集、書き込み、画像ファイルの保存などができます。
3.1.
“ beans.xml”
ファイル
まず、__ “beans.xml”ファイルを “src/main/resources/META-INF/”フォルダーに配置する必要があります。
このファイルに特定のDIディレクティブがまったく含まれていない場合でも、CDIを起動して実行するためには
が必要です。
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans__1__0.xsd">
</beans>
3.2. サービスクラス
次に、GIF、JPG、およびPNGファイルで上記のファイル操作を実行するサービスクラスを作成しましょう。
public interface ImageFileEditor {
String openFile(String fileName);
String editFile(String fileName);
String writeFile(String fileName);
String saveFile(String fileName);
}
public class GifFileEditor implements ImageFileEditor {
@Override
public String openFile(String fileName) {
return "Opening GIF file " + fileName;
}
@Override
public String editFile(String fileName) {
return "Editing GIF file " + fileName;
}
@Override
public String writeFile(String fileName) {
return "Writing GIF file " + fileName;
}
@Override
public String saveFile(String fileName) {
return "Saving GIF file " + fileName;
}
}
public class JpgFileEditor implements ImageFileEditor {
//JPG-specific implementations for openFile()/editFile()/writeFile()/saveFile()
...
}
public class PngFileEditor implements ImageFileEditor {
//PNG-specific implementations for openFile()/editFile()/writeFile()/saveFile()
...
}
3.3. クライアントクラス
最後に、コンストラクタで
ImageFileEditor
実装を取るクライアントクラスを実装し、
@ Inject
アノテーションを使用してインジェクションポイントを定義しましょう。
public class ImageFileProcessor {
private ImageFileEditor imageFileEditor;
@Inject
public ImageFileProcessor(ImageFileEditor imageFileEditor) {
this.imageFileEditor = imageFileEditor;
}
}
簡単に言えば、**
@ Inject
アノテーションはCDIの実際の主力です。これにより、クライアントクラスで注入ポイントを定義できます。
この場合、
@ Inject
はCDIに対して、
ImageFileEditor
実装をコンストラクターに挿入するように指示します。
さらに、フィールド内の
@ Inject
アノテーション(フィールドインジェクション)とセッター(セッターインジェクション)を使用してサービスをインジェクトすることもできます。これらのオプションについては後で説明します。
3.4. Weld
を使用した
ImageFileProcessor
オブジェクトグラフの作成
もちろん、CDIが正しい
ImageFileEditor
実装を
ImageFileProcessor
クラスコンストラクターに挿入するようにする必要があります。
そうするには、まず、クラスのインスタンスを取得する必要があります。
-
CDIを使用するためにどのJava EEアプリケーションサーバーにも依存しないので、これはJava SEでのCDI参照実装であるhttp://weld.cdi-spec.org/[Weld]で行います。
public static void main(String[]args) {
Weld weld = new Weld();
WeldContainer container = weld.initialize();
ImageFileProcessor imageFileProcessor = container.select(ImageFileProcessor.class).get();
System.out.println(imageFileProcessor.openFile("file1.png"));
container.shutdown();
}
ここでは、
WeldContainer
オブジェクトを作成してから
ImageFileProcessor
オブジェクトを取得し、最後にその
openFile()
メソッドを呼び出します。
予想通り、アプリケーションを実行すると、CDIは__DeploymentExceptionをスローして大声で不平を言います。
Unsatisfied dependencies for type ImageFileEditor with qualifiers @Default at injection point...
-
CDIは
ImageFileProcessor
コンストラクタにどの
ImageFileEditor
実装をインジェクトするのかわからないため、この例外が発生しています。**
CDIの用語では、これはあいまいな挿入例外として知られています
。
3.5.
@ Default
と
@ Alternative
の注釈
このあいまいさを解決するのは簡単です。 ** CDIは、デフォルトで、
@ Default
アノテーションでインターフェースのすべての実装にアノテーションを付けます。
したがって、どの実装をクライアントクラスにインジェクトするかを明示的に指示する必要があります。
@Alternative
public class GifFileEditor implements ImageFileEditor { ... }
@Alternative
public class JpgFileEditor implements ImageFileEditor { ... }
public class PngFileEditor implements ImageFileEditor { ... }
この場合は、
GifFileEditor
および
JpgFileEditor
に
@ Alternative
アノテーションを付けたので、CDIは
PngFileEditor
(デフォルトでは
@ Default
アノテーションが付けられている)がインジェクトする実装であることがわかりました。
アプリケーションを再実行すると、今回は期待どおりに実行されます。
Opening PNG file file1.png
さらに、
PngFileEditor
に
@ Default
アノテーションを付けて他の実装を代替手段として使用しても、上記と同じ結果になります。
これは、一言で言えば、** サービスクラス内の
@ Alternative
アノテーションを単純に切り替えることによって、実装のランタイムインジェクションを非常に簡単に交換できる方法を示しています。
4フィールドインジェクション
CDIは箱から出してフィールドとセッターの両方の注入をサポートします。
フィールドインジェクションを実行する方法は次のとおりです(
@ Default
アノテーションと
@ Alternative
アノテーションを使用してサービスを修飾するためのルールは同じです
)。
@Inject
private final ImageFileEditor imageFileEditor;
5セッター注入
同様に、セッターインジェクションを行う方法は次のとおりです。
@Inject
public void setImageFileEditor(ImageFileEditor imageFileEditor) { ... }
6. __ @名前付き注釈
これまで、クライアントクラスでインジェクションポイントを定義し、
@ Inject
、
@ Default
、および
@ Alternative
アノテーションを使用してサービスをインジェクトする方法を学びました。
それにもかかわらず、CDIでは
@ Named
アノテーションを使用してサービスインジェクションを実行することもできます。
このメソッドは意味のある名前を実装に結び付けることによって、サービスを注入するためのより意味のある方法を提供します。
@Named("GifFileEditor")
public class GifFileEditor implements ImageFileEditor { ... }
@Named("JpgFileEditor")
public class JpgFileEditor implements ImageFileEditor { ... }
@Named("PngFileEditor")
public class PngFileEditor implements ImageFileEditor { ... }
それでは、
ImageFileProcessor
クラスのインジェクションポイントを名前付き実装と一致するようにリファクタリングする必要があります。
@Inject
public ImageFileProcessor(@Named("PngFileEditor") ImageFileEditor imageFileEditor) { ... }
名前付き実装を使用してフィールドとセッターのインジェクションを実行することもできます。これは、
@ Default
アノテーションと
@ Alternative
アノテーションを使用するのと非常によく似ています。
@Inject
private final @Named("PngFileEditor") ImageFileEditor imageFileEditor;
@Inject
public void setImageFileEditor(@Named("PngFileEditor") ImageFileEditor imageFileEditor) { ... }
7.
@ Produces
アノテーション
場合によっては、サービスは、追加の依存関係を処理するために注入される前に、いくつかの構成を完全に初期化する必要があります。
CDIは、
@ Produces
アノテーションを通じて、これらの状況に対するサポートを提供します。
-
@ Produces
を使用すると、完全に初期化されたサービスの作成を担当するファクトリクラスを実装できます。
@ Produces
アノテーションがどのように機能するのかを理解するために、
ImageFileProcessor
クラスをリファクタリングしましょう。そうすれば、コンストラクタ内で追加の
TimeLogger
サービスを利用することができます。
このサービスは、特定のイメージファイル操作が実行された時刻を記録するために使用されます。
@Inject
public ImageFileProcessor(ImageFileEditor imageFileEditor, TimeLogger timeLogger) { ... }
public String openFile(String fileName) {
return imageFileEditor.openFile(fileName) + " at: " + timeLogger.getTime();
}
//additional image file methods
この場合、
TimeLogger
クラスは2つの追加サービス
SimpleDateFormat
と
Calendar
を受け取ります。
public class TimeLogger {
private SimpleDateFormat dateFormat;
private Calendar calendar;
//constructors
public String getTime() {
return dateFormat.format(calendar.getTime());
}
}
完全に初期化された
TimeLogger
オブジェクトを取得するためにどこを探すべきかをCDIにどのように伝えますか?
TimeLogger
ファクトリクラスを作成し、そのファクトリメソッドに
@ Produces
アノテーションを付けるだけです。
public class TimeLoggerFactory {
@Produces
public TimeLogger getTimeLogger() {
return new TimeLogger(new SimpleDateFormat("HH:mm"), Calendar.getInstance());
}
}
ImageFileProcessor
インスタンスを取得するたびに、CDIは
TimeLoggerFactory
クラスをスキャンしてから(
@ Produces
アノテーションでアノテーションされたとおりに)
getTimeLogger()
メソッドを呼び出し、最後に
Time Logger
サービスをインジェクトします。
リファクタリングされたサンプルアプリケーションを
Weld
で実行すると、次のように出力されます。
Opening PNG file file1.png at: 17:46
8カスタム修飾子
CDIは、依存関係を修飾し、あいまいな注入ポイントを解決するためのカスタム修飾子の使用をサポートします。
カスタム修飾子は非常に強力な機能です。
セマンティック名をサービスにバインドするだけでなく、注入メタデータもバインドします。
https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/RetentionPolicyなどのメタデータ。
html[RetentionPolicy]および有効な注釈ターゲット(https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/ElementType.html[ElementType])。
アプリケーションでカスタム修飾子を使用する方法を見てみましょう。
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface GifFileEditorQualifier {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface JpgFileEditorQualifier {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface PngFileEditorQualifier {}
それでは、カスタム修飾子を
ImageFileEditor
実装にバインドしましょう。
@GifFileEditorQualifier
public class GifFileEditor implements ImageFileEditor { ... }
@JpgFileEditorQualifier
public class JpgFileEditor implements ImageFileEditor { ... }
@PngFileEditorQualifier
public class PngFileEditor implements ImageFileEditor { ... }
最後に、
ImageFileProcessor
クラスのインジェクションポイントをリファクタリングします
_:
_
@Inject
public ImageFileProcessor(@PngFileEditorQualifier ImageFileEditor imageFileEditor, TimeLogger timeLogger) { ... }
アプリケーションをもう一度実行すると、上記と同じ出力が生成されるはずです。
カスタム修飾子は、名前と注釈メタデータを実装にバインドするための適切な意味論的アプローチを提供します。
さらに、** カスタム修飾子を使用すると、より制限の厳しいタイプセーフなインジェクションポイントを定義できます(@Defaultおよび@Alternativeアノテーションの機能よりも優れています)。
サブタイプのみが型階層で修飾されている場合、CDIは基本タイプではなくサブタイプのみを挿入します。
9結論
疑いの余地なく、
CDIは依存性注入を非常に簡単
にします。追加のアノテーションのコストは、組織化された依存性注入を得るための非常に小さな努力です。
DYDIがCDIよりもまだその地位を占めている場合があります。単純なオブジェクトグラフしか含まれていないかなり単純なアプリケーションを開発するときのように。
いつものように、この記事に示されているすべてのコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/cdi/src/main/java/com/baeldung/dependencyinjection[GitHubで利用可能]です。