コデインによるコトリン依存性注射

Kodeinを使用したKotlin依存性注入

1. 概要

この記事では、Kodein(純粋なKotlin依存性注入(DI)フレームワーク)を紹介し、他の一般的なDIフレームワークと比較します。

2. 依存

まず、Kodeinの依存関係をpom.xmlに追加しましょう。


    com.github.salomonbrys.kodein
    kodein
    4.1.0

利用可能な最新バージョンは、Maven CentralまたはjCenterのいずれかで利用可能であることに注意してください。

3. 設定

Kodeinベースの構成を説明するために、以下のモデルを使用します。

class Controller(private val service : Service)

class Service(private val dao: Dao, private val tag: String)

interface Dao

class JdbcDao : Dao

class MongoDao : Dao

4. 製本タイプ

Kodeinフレームワークは、さまざまなバインディングタイプを提供します。 それらがどのように機能し、どのように使用するかを詳しく見てみましょう。

4.1. シングルトン

Singletonバインディング、a target bean is instantiated lazily on the first accessを使用して、以降のすべての要求で再利用します。

var created = false;
val kodein = Kodein {
    bind() with singleton { MongoDao() }
}

assertThat(created).isFalse()

val dao1: Dao = kodein.instance()

assertThat(created).isFalse()

val dao2: Dao = kodein.instance()

assertThat(dao1).isSameAs(dao2)

注:静的変数タイプに基づいてターゲット管理対象Beanを取得するためにKodein.instance()を使用できます。

4.2. 熱心なシングルトン

これは、Singletonバインディングに似ています。 唯一の違いは、the initialization block is called eagerly

var created = false;
val kodein = Kodein {
    bind() with singleton { MongoDao() }
}

assertThat(created).isTrue()
val dao1: Dao = kodein.instance()
val dao2: Dao = kodein.instance()

assertThat(dao1).isSameAs(dao2)

4.3. 工場

Factoryバインディングを使用すると、初期化ブロックは引数を受け取り、a new object is returned from it every timeは次のようになります。

val kodein = Kodein {
    bind() with singleton { MongoDao() }
    bind() with factory { tag: String -> Service(instance(), tag) }
}
val service1: Service = kodein.with("myTag").instance()
val service2: Service = kodein.with("myTag").instance()

assertThat(service1).isNotSameAs(service2)

注:推移的な依存関係を構成するためにKodein.instance()を使用できます。

4.4. マルチトン

Multitonバインディングは、Factoryバインディングと非常によく似ています。 唯一の違いは、the same object is returned for the same argument in subsequent calls

val kodein = Kodein {
    bind() with singleton { MongoDao() }
    bind() with multiton { tag: String -> Service(instance(), tag) }
}
val service1: Service = kodein.with("myTag").instance()
val service2: Service = kodein.with("myTag").instance()

assertThat(service1).isSameAs(service2)

4.5. プロバイダ

これは引数なしのFactoryバインディングです。

val kodein = Kodein {
    bind() with provider { MongoDao() }
}
val dao1: Dao = kodein.instance()
val dao2: Dao = kodein.instance()

assertThat(dao1).isNotSameAs(dao2)

4.6. インスタンス

コンテナ内でregister a pre-configured bean instanceを実行できます。

val dao = MongoDao()
val kodein = Kodein {
    bind() with instance(dao)
}
val fromContainer: Dao = kodein.instance()

assertThat(dao).isSameAs(fromContainer)

4.7. タグ付け

異なるタグの下でregister more than one bean of the same typeを使用することもできます。

val kodein = Kodein {
    bind("dao1") with singleton { MongoDao() }
    bind("dao2") with singleton { MongoDao() }
}
val dao1: Dao = kodein.instance("dao1")
val dao2: Dao = kodein.instance("dao2")

assertThat(dao1).isNotSameAs(dao2)

4.8. 定数

これはタグ付きバインディングに対する構文糖衣構文であり、to be used for configuration constantsと見なされます—継承のない単純なタイプ:

val kodein = Kodein {
    constant("magic") with 42
}
val fromContainer: Int = kodein.instance("magic")

assertThat(fromContainer).isEqualTo(42)

5. バインディングの分離

Kodeinを使用すると、Beanを個別のブロックで構成して結合できます。

5.1. モジュール

group components by particular criteria —たとえば、データの永続性に関連するすべてのクラス—and combine the blocks to build a resulting container

val jdbcModule = Kodein.Module {
    bind() with singleton { JdbcDao() }
}
val kodein = Kodein {
    import(jdbcModule)
    bind() with singleton { Controller(instance()) }
    bind() with singleton { Service(instance(), "myService") }
}

val dao: Dao = kodein.instance()
assertThat(dao).isInstanceOf(JdbcDao::class.java)

注:モジュールにはバインディングルールが含まれているため、同じモジュールが複数のKodeinインスタンスで使用されると、ターゲットBeanが再作成されます。

5.2. 組成

1つのKodeinインスタンスを別のインスタンスから拡張できます。これにより、Beanを再利用できます。

val persistenceContainer = Kodein {
    bind() with singleton { MongoDao() }
}
val serviceContainer = Kodein {
    extend(persistenceContainer)
    bind() with singleton { Service(instance(), "myService") }
}
val fromPersistence: Dao = persistenceContainer.instance()
val fromService: Dao = serviceContainer.instance()

assertThat(fromPersistence).isSameAs(fromService)

5.3. オーバーライド

バインディングをオーバーライドできます。これはテストに役立ちます。

class InMemoryDao : Dao

val commonModule = Kodein.Module {
    bind() with singleton { MongoDao() }
    bind() with singleton { Service(instance(), "myService") }
}
val testContainer = Kodein {
    import(commonModule)
    bind(overrides = true) with singleton { InMemoryDao() }
}
val dao: Dao = testContainer.instance()

assertThat(dao).isInstanceOf(InMemoryDao::class.java)

6. マルチバインディング

コンテナでmore than one bean with the same common (super-)typeを構成できます。

val kodein = Kodein {
    bind() from setBinding()
    bind().inSet() with singleton { MongoDao() }
    bind().inSet() with singleton { JdbcDao() }
}
val daos: Set = kodein.instance()

assertThat(daos.map {it.javaClass as Class<*>})
  .containsOnly(MongoDao::class.java, JdbcDao::class.java)

7. インジェクター

私たちのアプリケーションコードは、以前に使用したすべての例でKodeinを認識していませんでした。これは、コンテナーの初期化中に提供された通常のコンストラクター引数を使用していました。

ただし、フレームワークではan alternative way to configure dependencies through delegated properties and Injectorsが許可されています。

class Controller2 {
    private val injector = KodeinInjector()
    val service: Service by injector.instance()
    fun injectDependencies(kodein: Kodein) = injector.inject(kodein)
}
val kodein = Kodein {
    bind() with singleton { MongoDao() }
    bind() with singleton { Service(instance(), "myService") }
}
val controller = Controller2()
controller.injectDependencies(kodein)

assertThat(controller.service).isNotNull

つまり、ドメインクラスはインジェクターを介して依存関係を定義し、特定のコンテナーからそれらを取得します。 Such an approach is useful in specific environments like Android

8. AndroidでKodeinを使用する

In Android, the Kodein container is configured in a custom Application class, and later on, it is bound to the Context instance.すべてのコンポーネント(アクティビティ、フラグメント、サービス、ブロードキャストレシーバー)は、KodeinActivityKodeinFragmentなどのユーティリティクラスから拡張されていると見なされます。

class MyActivity : Activity(), KodeinInjected {
    override val injector = KodeinInjector()

    val random: Random by instance()

    override fun onCreate(savedInstanceState: Bundle?) {
        inject(appKodein())
    }
}

9. 分析

このセクションでは、Kodeinが一般的なDIフレームワークとどのように比較されるかを見ていきます。

9.1. 春のフレームワーク

Spring FrameworkはKodeinよりも機能が豊富です。 たとえば、Springには非常にconvenient component-scanning facilityがあります。 @Component@Service@Named,などの特定のアノテーションでクラスをマークすると、コンポーネントスキャンはコンテナの初期化中にこれらのクラスを自動的に取得します。

Springにはpowerful meta-programming extension pointsBeanPostProcessor、およびBeanFactoryPostProcessorもあります。これらは、構成されたアプリケーションを特定の環境に適合させるときに重要になる場合があります。

最後に、Springは、AOP、トランザクション、テストフレームワーク、その他多くのconvenient technologies built on top of itを提供します。 これらを使用する場合は、SpringIoCコンテナを使用する価値があります。

9.2. ダガー2

Dagger 2フレームワークは、その速度(インジェクションを実行し、実行時に実行するだけのJavaコードを生成する)とサイズのためにnot as feature-rich as Spring Framework, but it’s popular in Android developmentです。

ライブラリのメソッド数とサイズを比較してみましょう。

Kodeinimagekotlin-stdlibの依存関係がこれらの数値の大部分を占めることに注意してください。 除外すると、1282のメソッドと244 KBのDEXサイズが得られます。

 

imageDagger 2フレームワークが追加するメソッドがはるかに少なく、JARファイルが小さいことがわかります。

使用法に関しては、ユーザーコードが依存関係を構成し(KodeinのInjectorとDagger 2のJSR-330アノテーションを介して)、後で単一のメソッド呼び出しを介してそれらを挿入するという点で非常に似ています。

ただし、Dagger 2の重要な機能は、validates the dependency graph at compile timeであるため、構成エラーが発生した場合にアプリケーションをコンパイルできません。

10. 結論

これで、依存関係の注入にKodeinを使用する方法、提供される構成オプション、および他のいくつかの一般的なDIフレームワークとの比較方法がわかりました。 ただし、実際のプロジェクトで使用するかどうかはあなた次第です。

いつものように、上記のサンプルのソースコードはover on GitHubにあります。