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で利用可能]です。