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

1概要

この記事では、https://github.com/SalomonBrys/Kodein[Kodein] - 純粋なKotlin依存性注入(DI)フレームワーク - を紹介し、それを他の一般的なDIフレームワークと比較します。

2依存

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

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

入手可能な最新バージョンはhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22com.github.salomonbrys.kodein%22%20AND%20a%3A%のいずれかで入手可能です22kodein%22[Maven Central]またはhttps://bintray.com/bintray/jcenter/com.github.salomonbrys.kodein%3Akodein[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 バインディングでは、 最初のアクセス時にターゲット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. 熱心なシングルトン

これは Singleton バインディングに似ています。唯一の違いは、初期化ブロックが積極的に呼び出されることです。

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. マルチトン

マルチバインディングは、ファクトリバインディングとよく似ています。唯一の違いは、後続の呼び出しで同じ引数に対して同じオブジェクトが返されることです。

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を認識していませんでした。コンテナの初期化中に提供された通常のコンストラクタ引数を使用していました。

ただし、このフレームワークでは、https://kotlinlang.org/docs/reference/delegated-properties.html[delegated properties]および Injectors ** を介して依存関係を設定する別の方法が可能です。

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コンテナはカスタムhttps://developer.android.com/reference/android/app/Application.html[ Application ]クラスに設定されており、後でhttps://開発者にバインドされます。 .android.com/reference/android/content/Context.html[ Context ]instance。** すべてのコンポーネント(アクティビティ、フラグメント、サービス、ブロードキャストレシーバー)は、 KodeinActivity KodeinFragment などのユーティリティクラスから拡張されていると見なされます。

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には 強力なメタプログラミング拡張ポイント もあります。https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html およびhttps://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanFactoryPostProcessor.html[ BeanFactoryPostProcessor__]は、構成済みのアプリケーションを適応させるときに重要になる可能性があります特定の環境に

最後に、SpringはAOP、Transactions、Test Framework、その他多くのものを含む その上に構築された 便利な技術を提供します。これらを使用したい場合は、Spring IoCコンテナにこだわる価値があります。

9.2. 短剣2

Dagger 2フレームワークは、Spring Frameworkほど機能が豊富ではありませんが、そのスピード(インジェクションを実行して実行時に実行するJavaコードを生成する)とサイズのために、Android開発で人気があります。

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

Kodein :link:/uploads/em%3E%20dependency%20accounts%20for%20the%20bulk%20of %20これらの%20数%20%20we%20除外%20it、%20we%20get%201282%20メソッド%20および%20244%20KB%20DEX%20サイズ%3C/p%3E%0A%3Cp%3E%C2%A0% 3C/p%3E%0A%3Cp%3E%3Ca%20href =[短剣2]:

link:/uploads/a%3E%3Cstrong%3EWe%20can%20see%20that%20the%20Dagger%202%20framework%20adds%20far%20fewer%20methods%20and%20its%20JAR%20file%20J%20file%20is%20smaller.%3C/strong%3E%3C/p%3E%0A%3Cp%3次の%20the%20usage%20 - %20it’s%20very%20in%20%20the%20user%20code%20構成%20依存関係%20(%20%3Cemから) %3EInjector%3C/em%3E%20in%20Kodein%20および%20%3Ca%20href =[Dagger 2]以降の[JSR-330]注釈は、単一のメソッド呼び出しを介してそれらを挿入します。

しかし、Dagger 2の重要な特徴は、 コンパイル時に依存関係グラフを検証する ということです。そのため、設定エラーがあるとアプリケーションはコンパイルできません。

10結論

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

いつものように、上記のサンプルのソースコードはhttps://github.com/eugenp/tutorials/tree/master/kotlin-libraries[over GitHub]にあります。