Kotlin-afhankelijkheidsinjectie met Kodein

1. Overzicht

In dit artikel zullen we Kodein introduceren - een puur Kotlin dependency injection (DI) -raamwerk - en het vergelijken met andere populaire DI-frameworks.

2. Afhankelijkheid

Laten we eerst de Kodein-afhankelijkheid toevoegen aan onze pom.xml:

 com.github.salomonbrys.kodein kodein 4.1.0 

Houd er rekening mee dat de nieuwste beschikbare versie beschikbaar is op Maven Central of jCenter.

3. Configuratie

We gebruiken het onderstaande model om de op Kodein gebaseerde configuratie te illustreren:

class Controller (private val service: Service) class Service (private val dao: Dao, private val tag: String) interface Dao class Jdbcdred: Dao class Mongoão: Dao

4. Bindingstypes

Het Kodein-framework biedt verschillende soorten binding. Laten we eens nader bekijken hoe ze werken en hoe u ze kunt gebruiken.

4.1. Singleton

Met Singleton verbindend, een doelboon wordt lui geïnstantieerd bij de eerste toegang en hergebruikt op alle verdere verzoeken:

var gemaakt = false; val kodein = Kodein {bind () met singleton {Mongoão ()}} assertThat (gemaakt) .isFalse () val dao1: Dao = kodein.instance () assertThat (gemaakt) .isFalse () val dao2: Dao = kodein.instance () assertThat (dao1) .isSameAs (dao2)

Let op: we kunnen gebruiken Kodein.instance () voor het ophalen van door een doel beheerde bonen op basis van een statisch variabeletype.

4.2. Gretig Singleton

Dit is vergelijkbaar met de Singleton verbindend. Het enige verschil is dat het initialisatieblok wordt gretig opgeroepen:

var gemaakt = false; val kodein = Kodein {bind () met singleton {Mongoão ()}} assertThat (gemaakt) .isTrue () val dao1: Dao = kodein.instance () val dao2: Dao = kodein.instance () assertThat (dao1) .isSameAs (dao2)

4.3. Fabriek

Met Fabriek binding ontvangt het initialisatieblok een argument, en er wordt elke keer een nieuw object uit geretourneerd:

val kodein = Kodein {bind () met singleton {Mongoão ()} bind () met fabriek {tag: String -> Service (instance (), tag)}} val service1: Service = kodein.with ("myTag"). instance () val service2: Service = kodein.with ("myTag"). instance () assertThat (service1) .isNotSameAs (service2)

Let op: we kunnen gebruiken Kodein.instance () voor het configureren van transitieve afhankelijkheden.

4.4. Multiton

Multiton binding lijkt veel op Fabriek verbindend. Het enige verschil is dat hetzelfde object wordt geretourneerd voor hetzelfde argument in volgende aanroepen:

val kodein = Kodein {bind () met singleton {Mongoão ()} bind () met multiton {tag: String -> Service (instantie (), tag)}} val service1: Service = kodein.with ("myTag"). instance () val service2: Service = kodein.with ("myTag"). instance () assertThat (service1) .isSameAs (service2)

4.5. Aanbieder

Dit is een no-arg Fabriek verbindend:

val kodein = Kodein {bind () met provider {Mongoão ()}} val dao1: Dao = kodein.instance () val dao2: Dao = kodein.instance () assertThat (dao1) .isNotSameAs (dao2)

4.6. Voorbeeld

Wij kunnen registreer een vooraf geconfigureerde bean-instantie in de container:

val dao = Mongoão () val kodein = Kodein {bind () met instantie (dao)} val fromContainer: Dao = kodein.instance () assertThat (dao) .isSameAs (fromContainer)

4.7. Taggen

We kunnen ook registreer meer dan één boon van hetzelfde type onder verschillende tags:

val kodein = Kodein {bind ("dao1") met singleton {Mongoão ()} bind ("dao2") met singleton {Mongoão ()}} val dao1: Dao = kodein.instance ("dao1") val dao2: Dao = kodein.instance ("dao2") assertThat (dao1) .isNotSameAs (dao2)

4.8. Constante

Dit is syntactische suiker over gelabelde binding en wordt aangenomen te gebruiken voor configuratieconstanten - eenvoudige typen zonder overerving:

val kodein = Kodein {constante ("magic") met 42} val fromContainer: Int = kodein.instance ("magic") assertThat (fromContainer) .isEqualTo (42)

5. Scheiding van bindingen

Met Kodein kunnen we bonen in afzonderlijke blokken configureren en combineren.

5.1. Modules

Wij kunnen groepeer componenten op basis van bepaalde criteria - bijvoorbeeld alle klassen die verband houden met gegevenspersistentie - en combineer de blokken om een ​​resulterende container te bouwen:

val jdbcModule = Kodein.Module {bind () met singleton {Jdbc Dao ()}} val kodein = Kodein {import (jdbcModule) bind () met singleton {Controller (instance ())} bind () met singleton {Service (instance ( ), "myService")}} val dao: Dao = kodein.instance () assertThat (dao) .isInstanceOf (Jdbc Dao :: class.java)

Opmerking: aangezien modules bindende regels bevatten, worden targetbeans opnieuw gemaakt wanneer dezelfde module in meerdere Kodein-instanties wordt gebruikt.

5.2. Samenstelling

We kunnen de ene Kodein-instantie van de andere uitbreiden - dit stelt ons in staat bonen opnieuw te gebruiken:

val persistenceContainer = Kodein {bind () met singleton {Mongoão ()}} val serviceContainer = Kodein {verleng (persistenceContainer) bind () met singleton {Service (instance (), "myService")}} val fromPersistence: Dao = persistenceContainer. instance () val fromService: Dao = serviceContainer.instance () assertThat (fromPersistence) .isSameAs (fromService)

5.3. Overheersend

We kunnen bindingen negeren - dit kan handig zijn om te testen:

class InMemoryão: Dao val commonModule = Kodein.Module {bind () met singleton {Mongoão ()} bind () met singleton {Service (instance (), "myService")}} val testContainer = Kodein {import (commonModule) bind ( overrides = true) met singleton {InMemory Dao ()}} val dao: Dao = testContainer.instance () assertThat (dao) .isInstanceOf (InMemory Dao :: class.java)

6. Multi-bindingen

We kunnen configureren meer dan één boon van hetzelfde gangbare (super-) type in de container:

val kodein = Kodein {bind () van setBinding () bind (). inSet () met singleton {Mongoão ()} bind (). inSet () met singleton {Jdbcbao ()}} val daos: Set = kodein.instance ( ) assertThat (daos.map {it.javaClass as Class}) .containsOnly (Mongobao :: class.java, Jdbcbao :: class.java)

7. Injector

Onze applicatiecode kende Kodein niet in alle voorbeelden die we eerder gebruikten - het gebruikte gewone constructorargumenten die werden opgegeven tijdens de initialisatie van de container.

Het raamwerk laat echter toe een alternatieve manier om afhankelijkheden te configureren via gedelegeerde eigenschappen en Injectoren:

class Controller2 {private val injector = KodeinInjector () val service: Service door injector.instance () fun injectDependencies (kodein: Kodein) = injector.inject (kodein)} val kodein = Kodein {bind () met singleton {Mongoão ()} bind () met singleton {Service (instance (), "myService")}} val controller = Controller2 () controller.injectDependencies (kodein) assertThat (controller.service) .isNotNull

Met andere woorden, een domeinklasse definieert afhankelijkheden via een injector en haalt ze op uit een bepaalde container. Zo'n aanpak is handig in specifieke omgevingen zoals Android.

8. Kodein gebruiken met Android

In Android is de Kodein-container geconfigureerd in een aangepast Toepassing class, en later is het gebonden aan de Context voorbeeld. Van alle componenten (activiteiten, fragmenten, services, uitzendontvangers) wordt aangenomen dat ze worden uitgebreid vanuit de hulpprogramma-klassen zoals KodeinActivity en KodeinFragment:

class MyActivity: Activity (), KodeinInjected {override val injector = KodeinInjector () val random: Random by instance () override fun onCreate (savedInstanceState: Bundle?) {inject (appKodein ())}}

9. Analyse

In deze sectie zullen we zien hoe Kodein zich verhoudt tot populaire DI-frameworks.

9.1. Spring Framework

Het Spring Framework is veel rijker aan functies dan Kodein. De lente heeft bijvoorbeeld een zeer handige mogelijkheid om componenten te scannen. Wanneer we onze klassen markeren met bepaalde annotaties zoals @Component, @Onderhoud, en @Genaamd, de component-scan pikt die klassen automatisch op tijdens de initialisatie van de container.

De lente heeft ook krachtige uitbreidingspunten voor metaprogrammering, BeanPostProcessor en BeanFactoryPostProcessor, wat cruciaal kan zijn bij het aanpassen van een geconfigureerde applicatie aan een bepaalde omgeving.

Ten slotte biedt de lente wat handige technologieën die erop zijn gebouwd, inclusief AOP, Transactions, Test Framework en vele anderen. Als we deze willen gebruiken, is het de moeite waard om bij de Spring IoC-container te blijven.

9.2. Dolk 2

Het Dagger 2-raamwerk is niet zo rijk aan functies als Spring Framework, maar het is populair in Android-ontwikkeling vanwege zijn snelheid (het genereert Java-code die de injectie uitvoert en het gewoon in runtime uitvoert) en grootte.

Laten we het aantal en de grootte van de methode van de bibliotheken vergelijken:

Kodein:Merk op dat de kotlin-stdlib afhankelijkheid is verantwoordelijk voor het grootste deel van deze aantallen. Als we het uitsluiten, krijgen we 1282 methoden en 244 KB DEX-grootte.

Dolk 2:

We kunnen zien dat het Dagger 2-framework veel minder methoden toevoegt en dat het JAR-bestand kleiner is.

Wat betreft het gebruik - het lijkt erg op het feit dat de gebruikerscode afhankelijkheden configureert (via Injector in Kodein en JSR-330 annotaties in Dagger 2) en injecteert ze later via een enkele methodeaanroep.

Een belangrijk kenmerk van Dagger 2 is echter dat het valideert de afhankelijkheidsgrafiek tijdens het compileren, dus het staat de toepassing niet toe om te compileren als er een configuratiefout is.

10. Conclusie

We weten nu hoe we Kodein moeten gebruiken voor het injecteren van afhankelijkheden, welke configuratie-opties het biedt en hoe het zich verhoudt tot een aantal andere populaire DI-frameworks. Het is echter aan jou om te beslissen of je het in echte projecten wilt gebruiken.

Zoals altijd is de broncode voor de bovenstaande voorbeelden te vinden op GitHub.


$config[zx-auto] not found$config[zx-overlay] not found