この記事では、マルチバインディングを使用する機能について説明します。これは、依存関係のプロビジョニングに関連する多くの問題の解決に役立ちます。
この記事を読むには、Dagger 2の基本的な知識が必要です。例では、Daggerバージョン2.11を使用しました。
Dagger 2を使用すると、オブジェクトが異なるモジュールにバインドされている場合でも、複数のオブジェクトをコレクションにバインドできます。 Dagger 2は、 Set
およびMap
マルチバインディングをサポートしています。
マルチバインディングを設定する
Set
に要素を追加するには、モジュールの@Provides
メソッドに@IntoSet
アノテーションを追加するだけです:
@Module public class ModuleA { @IntoSet @Provides public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ModuleB { @IntoSet @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } }
これらの2つのモジュールをコンポーネントに追加します。
@Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { //inject methods }
なぜなら セット内の要素のバインディングを含む2つのモジュールを1つのコンポーネントに結合しました。短剣はこれらの要素を1つのコレクションに結合します。
public class Values { @Inject public Values(Set<FileExporter> values) { // values: [XmlFileExporter, CSVFileExporter] } }
一度に複数の要素を追加することもできます。 @Provide
ためには、 @Provide
メソッドがSet
戻り値の型を持ち、 @Provide
メソッドの上に@ElementsIntoSet
アノテーションを配置する必要があります。
ModuleBを交換します。
@Module public class ModuleB { @ElementsIntoSet @Provides public Set<FileExporter> provideFileExporters(Context context) { return new HashSet<>(Arrays.asList(new CSVFileExporter(context), new JSONFileExporter(context))); } }
結果:
public class Values { @Inject public Values(Set<FileExporter> values) { // values: [XmlFileExporter, CSVExporter, JSONFileExporter] } }
コンポーネントを介して依存関係を提供できます。
@Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { Set<FileExporter> fileExporters(); } Set<FileExporter> fileExporters = DaggerAppComponent .builder() .context(this) .build() .fileExporters();
また、 @Provides
メソッドに対して@Qualifier
を使用してコレクションを提供し、それによってそれらを分離することもできます。
ModuleBを再度交換します。
@Module public class ModuleB { @ElementsIntoSet @Provides @Named("CSV_JSON") public Set<FileExporter> provideFileExporters(Context context) { return new HashSet<>(Arrays.asList(new CSVFileExporter(context), new JSONFileExporter(context))); } } // Qualifier public class Values { @Inject public Values(Set<FileExporter> values) { // values: [XmlFileExporter]. // , // c ModuleA. } } // Qualifier public class Values { @Inject public Values(@Named("CSV_JSON") Set<FileExporter> values) { // values: [CSVExporter, JSONFileExporter] } } // @Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { @Named("CSV_JSON") Set<FileExporter> fileExporters(); }
Dagger 2は、オブジェクトの初期化を最初の呼び出しまで延期する機能を提供します。この機能はコレクション用です。 Dagger 2の兵器庫には、遅延初期化を実現する2つの方法がありProvider<T> Lazy<T>
インターフェイスを使用する方法です。
遅延注入
T
依存関係の場合、 Lazy<T>
適用できます。このメソッドにより、 Lazy<T>.get()
の最初の呼び出しまで初期化を遅らせることができます。 T
シングルトンの場合、同じインスタンスが常に返されます。 T
スコープ外の場合、依存関係T
はLazy<T>.get
呼び出し時に作成され、 Lazy<T>.get
内のキャッシュに入れられ、このLazy<T>.get()
以降の各呼び出しはキャッシュされた値を返します。
例:
@Module public class AppModule { @Singleton @Provides public GroupRepository groupRepository(Context context) { return new GroupRepositoryImpl(context); } @Provides return new UserRepositoryImpl(context); public UserRepository userRepository(Context context) { } } public class MainActivity extends AppCompatActivity { @Inject Lazy<GroupRepository> groupRepositoryInstance1; @Inject Lazy<GroupRepository> groupRepositoryInstance2; @Inject Lazy<UserRepository> userRepositoryInstance1; @Inject Lazy<UserRepository> userRepositoryInstance2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent .builder() .context(this) .build() .inject(this); //GroupRepository @Singleton scope GroupRepository groupRepository1 = groupRepositoryInstance1.get(); GroupRepository groupRepository2 = groupRepositoryInstance1.get(); GroupRepository groupRepository3 = groupRepositoryInstance2.get(); //UserRepository unscope UserRepository userRepository1 = userRepositoryInstance1.get(); UserRepository userRepository2 = userRepositoryInstance1.get(); UserRepository userRepository3 = userRepositoryInstance2.get(); } }
インスタンスgroupRepository1, groupRepository2
およびgroupRepository3
は等しいため、 スコープシングルトンがあります。
インスタンスuserRepository1
とuserRepository2
は等しくなります。 userRepositoryInstance1.get()
の最初の呼び出しで、オブジェクトが作成され、 userRepositoryInstance1
内にキャッシュされますが、 userRepository3
は異なるインスタンスを持ちます。 彼には別のLazy
あり、 get()
が初めて呼び出されました。
プロバイダー注入
Provider<T>
では、オブジェクトの初期化を遅らせることもできますが、 Lazy<T>
とは異なり、スコープ外依存関係値はProvider<T>
キャッシュされず、新しいインスタンスを返すたびにキャッシュされません。 このアプローチは、たとえば、シングルトンミサゴを備えた特定のファクトリーがあり、このファクトリーが毎回新しいオブジェクトを提供する必要がある場合に必要になることがあります。例を考えてみましょう。
@Module public class AppModule { @Provides public Holder provideHolder() { return new Holder(); } @Provides @Singleton public HolderFactory provideHolderFactory(Provider<Holder> holder) { return new HolderFactoryImpl(holder); } } public class HolderFactoryImpl implements HolderFactory { private Provider<Holder> holder; public HolderFactoryImpl(Provider<Holder> holder) { this.holder = holder; } public Holder create() { return holder.get(); } } public class MainActivity extends AppCompatActivity { @Inject HolderFactory holderFactory; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent .builder() .context(this) .build() .inject(this); Holder holder1 = holderFactory.create(); Holder holder2 = holderFactory.create(); } }
ここでは、 holder1
とholder2
に異なるインスタンスがありますholder2
Lazy<T>
代わりにLazy<T>
を使用する場合、これらのオブジェクトにはキャッシュのために1つのインスタンスがあります。
遅延初期化はSet
も適用できます:
Lazy<Set<T>> Provider<Set<T>>
は、次のように使用できません: Set<Lazy<T>>
。
public class MainActivity extends AppCompatActivity { @Inject Lazy<Set<FileExporter>> fileExporters; //… // Set<FileExporter> exporters = fileExporters.get(); }
マルチバインディングをマップする
要素をMap
に追加するには、モジュールの@Provides
メソッドの上に@IntoMap
アノテーションとキーアノテーション( @MapKey
Heirs)を追加する必要があります。
@Module public class ModuleA { @IntoMap @Provides @StringKey("xml") public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ModuleB { @IntoMap @StringKey("csv") @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } } @Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { //inject methods }
結果:
public class Values { @Inject public Values(Map<String, FileExporter> values) { // values {xml=XmlFileExporter,csv=CSVExporter} } }
Set
と同様に、コンポーネントで2つのモジュールを指定したため、Daggerは値を1つのMap
結合しました。 @Qualifier
使用することもでき@Qualifier
。
Map
標準キータイプ:
- Intkey
- ロングキー
- StringKey
- クラスキー
Dagger-Androidアドオンモジュールの標準キータイプ:
- アクティビティキー
- BroadcastReceiverKey
- ContentProviderKey
- フラグメントキー
- サービスキー
ActivityKey
例の実装は次のようになります。
@MapKey @Target(METHOD) public @interface ActivityKey { Class<? extends Activity> value(); }
上記のように、またはenum
を使用して、独自のキータイプを作成できます。
public enum Exporters { XML, CSV } @MapKey @Target(METHOD) public @interface ExporterKey { Exporters value(); } @Module public class ModuleA { @IntoMap @Provides @ExporterKey(Exporters.XML) public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ModuleB { @IntoMap @ExporterKey(Exporters.CSV) @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } } public class Values { @Inject public Values(Map<Exporters, FileExporter> values) { // values {XML=XmlFileExporter,CSV=CSVExporter} } }
Set
同様に、遅延初期化を適用できます。
Lazy<Map<K,T>>, Provider<Map<K,T>>
。
Mapを使用すると、コレクション自体の初期化だけでなく、個々の要素の初期化を延期して、キー( Map<K,Provider<T>>
)を使用して毎回新しい値を受け取ることができます。
public class MainActivity extends AppCompatActivity { @Inject Map<Exporters, Provider<FileExporter>> exporterMap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent .builder() .context(this) .build(); FileExporter fileExporter1 = exporterMap.get(Exporters.CSV).get(); FileExporter fileExporter2 = exporterMap.get(Exporters.CSV).get(); } }
fileExporter1
とfileExporter2
には異なるインスタンスがあります。 また、 Exports.XML
要素は初期化さえされてExports.XML
私たちは彼に連絡しませんでした。
Map<K, Lazy<T>>
は使用できません 。
空のコレクションを検証するには、抽象メソッドに@Multibinds
アノテーションを追加する必要があります。
@Module public abstract class AppModule { @Multibinds abstract Map<Exporters, FileExporter> exporters(); }
これは、たとえば、このコレクションを既に使用したいが、実装されたモジュールがまだ利用可能でない(実装されていない)場合、およびモジュールを実装および追加するときに、値を共通のコレクションに結合する場合に必要になることがあります。
サブコンポーネントとマルチバインディング
親コンポーネントは、親コンポーネントのモジュールでのみ指定されたコレクションにアクセスでき、サブコンポーネントは親コンポーネントのすべてのコレクションを「継承」し、それらをサブコンポーネントコレクションと結合します。
@Module public class AppModule { @IntoMap @Provides @ExporterKey(Exporters.XML) public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ActivityModule { @IntoMap @ExporterKey(Exporters.CSV) @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } } @Singleton @Component(modules = {AppModule.class}) public interface AppComponent { ActivitySubComponent provideActivitySubComponent(); // {xml=XmlFileExporter} Map<Exporters, FileExporter> exporters(); @Component.Builder interface Builder { @BindsInstance Builder context(Context context); AppComponent build(); } } @ActivityScope @Subcomponent(modules = {ActivityModule.class}) public interface ActivitySubComponent { // {XML=XmlFileExporter,CSV=CSVExporter} Map<Exporters, FileExporter> exporters(); }
@Binds +マルチバインディング
Dagger 2では、抽象@Bindsメソッドを使用してオブジェクトをコレクションにバインドできます。
@Module public abstract class LocationTrackerModule { @Binds @IntoSet public abstract LocationTracker netLocationTracker(NetworkLocationTracker tracker); @Binds @IntoSet public abstract LocationTracker fileLocationTracker(FileLocationTracker tracker); }
プラグイン
プラグインで設計されたアプリケーションを構築するために、依存性注入フレームワークを使用してインターフェイスを実装から分離し、「プラグイン」をさまざまなアプリケーションで再利用できるようにします。
マルチバインディングを使用して、多くのプラグインの拡張ポイントとなるインターフェイスとプロバイダーメソッドを作成できます。
おわりに
私の意見では、Multibindingsは依存関係の提供を整理するための十分な機会を提供し、工場を美しく整理することができ、拡張アーキテクチャの実装にも適しています。