1. 概要

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

2. 依存

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

<dependency>
    <groupId>com.github.salomonbrys.kodein</groupId>
    <artifactId>kodein</artifactId>
    <version>4.1.0</version>
</dependency>

利用可能な最新バージョンは、 MavenCentralまたは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. シングルトン

シングルトンバインディングを使用すると、ターゲットBeanは最初のアクセスで遅延インスタンス化され、それ以降のすべてのリクエストで再利用されます。

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

assertThat(created).isFalse()

val dao1: Dao = kodein.instance()

assertThat(created).isFalse()

val dao2: Dao = kodein.instance()

assertThat(dao1).isSameAs(dao2)

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

4.2. 熱心なシングルトン

これは、シングルトンバインディングに似ています。 唯一の違いは、初期化ブロックが熱心にと呼ばれることです。

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

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

assertThat(dao1).isSameAs(dao2)

4.3. 工場

Factory バインディングを使用すると、初期化ブロックは引数を受け取り、新しいオブジェクトが毎回返されます。

val kodein = Kodein {
    bind<Dao>() with singleton { MongoDao() }
    bind<Service>() 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バインディングと非常によく似ています。 唯一の違いは、後続の呼び出しで同じ引数に対して同じオブジェクトが返されることです。

val kodein = Kodein {
    bind<Dao>() with singleton { MongoDao() }
    bind<Service>() 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<Dao>() with provider { MongoDao() }
}
val dao1: Dao = kodein.instance()
val dao2: Dao = kodein.instance()

assertThat(dao1).isNotSameAs(dao2)

4.6. 実例

事前設定されたBeanインスタンスをコンテナに登録できます。

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

assertThat(dao).isSameAs(fromContainer)

4.7. タグ付け

同じタイプの複数のBeanを異なるタグで登録することもできます。

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

assertThat(dao1).isNotSameAs(dao2)

4.8. 絶え間ない

これはタグ付きバインディングに対する構文糖衣構文であり、構成定数に使用されると想定されています—継承のない単純な型:

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

assertThat(fromContainer).isEqualTo(42)

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

Kodeinを使用すると、Beanを別々のブロックに構成し、それらを組み合わせることができます。

5.1. モジュール

特定の基準(たとえば、データの永続性に関連するすべてのクラス)によってコンポーネントをグループ化し、ブロックを組み合わせて、結果のコンテナーを構築できます。

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

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

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

5.2. 構成

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

val persistenceContainer = Kodein {
    bind<Dao>() with singleton { MongoDao() }
}
val serviceContainer = Kodein {
    extend(persistenceContainer)
    bind<Service>() 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<Dao>() with singleton { MongoDao() }
    bind<Service>() with singleton { Service(instance(), "myService") }
}
val testContainer = Kodein {
    import(commonModule)
    bind<Dao>(overrides = true) with singleton { InMemoryDao() }
}
val dao: Dao = testContainer.instance()

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

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

コンテナ内で同じ共通(スーパー)タイプを使用して複数のBeanを構成できます。

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

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

7. インジェクター

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

ただし、フレームワークでは、委任されたプロパティとインジェクターを介して依存関係を構成する別の方法が可能です。

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

assertThat(controller.service).isNotNull

つまり、ドメインクラスはインジェクターを介して依存関係を定義し、特定のコンテナーから依存関係を取得します。 このようなアプローチは、Androidなどの特定の環境で役立ちます。

8. AndroidでKodeinを使用する

Androidでは、KodeinコンテナーはカスタムApplicationクラスで構成され、後でContextインスタンスにバインドされます。すべてのコンポーネント(アクティビティ、フラグメント、サービス、ブロードキャストレシーバー)は、 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

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

Springには、強力なメタプログラミング拡張ポイント BeanPostProcessor 、および BeanFactoryPostProcessor もあります。これは、構成済みアプリケーションを特定の環境に適合させる場合に重要になる可能性があります。

最後に、Springは、AOP、トランザクション、テストフレームワーク、その他多くのその上に構築された便利なテクノロジーを提供します。 これらを使用したい場合は、SpringIoCコンテナーを使用する価値があります。

9.2. ダガー2

Dagger2フレームワークはSpringFrameworkほど機能が豊富ではありませんが、その速度(インジェクションを実行し、実行時に実行するだけのJavaコードを生成する)とサイズにより、Android開発で人気があります。

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

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

 

短剣2:

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

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

ただし、Dagger 2の重要な機能は、コンパイル時に依存関係グラフを検証するため、構成エラーが発生した場合にアプリケーションをコンパイルできないことです。

10. 結論

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

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