Kotlin-afhængighedsinjektion med Kodein

1. Oversigt

I denne artikel introducerer vi Kodein - en ren Kotlin-afhængighedsinjektionsramme (DI) - og sammenligner den med andre populære DI-rammer.

2. Afhængighed

Lad os først tilføje Kodein-afhængighed til vores pom.xml:

 com.github.salomonbrys.kodein kodein 4.1.0 

Bemærk, at den senest tilgængelige version er tilgængelig enten på Maven Central eller jCenter.

3. Konfiguration

Vi bruger nedenstående model til at illustrere Kodein-baseret konfiguration:

klasse Controller (privat val service: Service) klasse Service (privat val dao: Dao, privat val tag: String) interface Dao klasse JdbcDao: Dao klasse MongoDao: Dao

4. Bindingstyper

Kodein-rammen tilbyder forskellige bindingstyper. Lad os se nærmere på, hvordan de fungerer, og hvordan de bruges.

4.1. Singleton

Med Singleton bindende, en målbønne instantieres doven ved den første adgang og genbruges på alle yderligere anmodninger:

var oprettet = falsk; val kodein = Kodein {bind () med singleton {MongoDao ()}} assertThat (oprettet) .isFalse () val dao1: Dao = kodein.instance () assertThat (oprettet) .isFalse () val dao2: Dao = kodein.instance () assertThat (dao1) .isSameAs (dao2)

Bemærk: vi kan bruge Kodein.instance () til at hente målstyrede bønner baseret på en statisk variabel type.

4.2. Ivrig Singleton

Dette svarer til Singleton bindende. Den eneste forskel er det initialiseringsblokken kaldes ivrigt:

var oprettet = falsk; val kodein = Kodein {bind () med singleton {MongoDao ()}} assertThat (oprettet) .isTrue () val dao1: Dao = kodein.instance () val dao2: Dao = kodein.instance () assertThat (dao1) .isSameAs (dao2)

4.3. Fabrik

Med Fabrik binding, initialiseringsblokken modtager et argument, og et nyt objekt returneres fra det hver gang:

val kodein = Kodein {bind () med singleton {MongoDao ()} bind () med fabrik {tag: String -> Service (instans (), tag)}} val service1: Service = kodein.with ("myTag"). forekomst () val service2: Service = kodein.with ("myTag"). forekomst () assertThat (service1) .isNotSameAs (service2)

Bemærk: vi kan bruge Kodein.instance () til konfiguration af transitive afhængigheder.

4.4. Multiton

Multiton binding er meget lig Fabrik bindende. Den eneste forskel er det det samme objekt returneres for det samme argument i efterfølgende opkald:

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

4.5. Udbyder

Dette er en no-arg Fabrik bindende:

val kodein = Kodein {bind () med udbyder {MongoDao ()}} val dao1: Dao = kodein.instance () val dao2: Dao = kodein.instance () assertThat (dao1) .isNotSameAs (dao2)

4.6. Instans

Vi kan registrer en forudkonfigureret bønneinstans i beholderen:

val dao = MongoDao () val kodein = Kodein {bind () med instans (dao)} val fraContainer: Dao = kodein.instance () assertThat (dao) .isSameAs (fromContainer)

4.7. Mærkning

Det kan vi også registrer mere end en bønne af samme type under forskellige tags:

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

4.8. Konstant

Dette er syntaktisk sukker over mærket binding og antages skal bruges til konfigurationskonstanter - enkle typer uden arv:

val kodein = Kodein {konstant ("magi") med 42} val fraContainer: Int = kodein.instance ("magi") assertThat (fromContainer) .isEqualTo (42)

5. Adskillelse af bindinger

Kodein giver os mulighed for at konfigurere bønner i separate blokke og kombinere dem.

5.1. Moduler

Vi kan gruppere komponenter efter bestemte kriterier - for eksempel alle klasser relateret til data persistens - og kombiner blokke for at bygge en resulterende container:

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

Bemærk: Da moduler indeholder bindende regler, oprettes målbønner igen, når det samme modul bruges i flere Kodein-forekomster.

5.2. Sammensætning

Vi kan udvide en Kodein-forekomst fra en anden - det giver os mulighed for at genbruge bønner:

val persistenceContainer = Kodein {bind () med singleton {MongoDao ()}} val serviceContainer = Kodein {ext (persistenceContainer) bind () med singleton {Service (instans (), "myService")}} val fromPersistence: Dao = persistenceContainer. forekomst () val fromService: Dao = serviceContainer.instance () assertThat (fromPersistence) .isSameAs (fromService)

5.3. Tilsidesættelse

Vi kan tilsidesætte bindinger - dette kan være nyttigt til test:

klasse InMemoryDao: Dao val commonModule = Kodein.Module {bind () med singleton {MongoDao ()} bind () med singleton {Service (instans (), "myService")}} val testContainer = Kodein {import (commonModule) bind ( tilsidesætter = sand) med singleton {InMemoryDao ()}} val dao: Dao = testContainer.instance () assertThat (dao) .isInstanceOf (InMemoryDao :: class.java)

6. Multi-bindinger

Vi kan konfigurere mere end en bønne med samme almindelige (super-) type i beholderen:

val kodein = Kodein {bind () fra setBinding () bind (). inSet () med singleton {MongoDao ()} bind (). inSet () med singleton {JdbcDao ()}} val daos: Set = kodein.instance ( ) assertThat (daos.map {it.javaClass as Class}) .containsOnly (MongoDao :: class.java, JdbcDao :: class.java)

7. Injektor

Vores applikationskode var uvidende om Kodein i alle de eksempler, vi brugte før - den brugte regelmæssige konstruktørargumenter, der blev leveret under containerens initialisering.

Imidlertid tillader rammen en alternativ måde at konfigurere afhængigheder gennem delegerede egenskaber og Injektorer:

klasse Controller2 {privat valinjektor = KodeinInjector () val service: Service ved injector.instance () sjov injektionsafhængighed (kodein: Kodein) = injector.inject (kodein)} val kodein = Kodein {bind () med singleton {MongoDao ()} bind () med singleton {Service (forekomst (), "myService")}} val controller = Controller2 () controller.injectDependencies (kodein) assertThat (controller.service) .isNotNull

Med andre ord definerer en domæneklasse afhængigheder gennem en injektor og henter dem fra en given beholder. En sådan tilgang er nyttig i specifikke miljøer som Android.

8. Brug af Kodein med Android

I Android er Kodein-containeren konfigureret i en brugerdefineret Ansøgning klasse, og senere er det bundet til Sammenhæng eksempel. Alle komponenter (aktiviteter, fragmenter, tjenester, udsendelsesmodtagere) antages at være udvidet fra hjælpeklasser som KodeinAktivitet og KodeinFragment:

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

9. Analyse

I dette afsnit vil vi se, hvordan Kodein sammenligner med populære DI-rammer.

9.1. Spring Framework

Spring Framework er meget mere funktionsrig end Kodein. For eksempel har foråret en meget praktisk komponentscanningsfacilitet. Når vi markerer vores klasser med bestemte kommentarer som f.eks @Komponent, @Serviceog @Som hedder, komponentscanningen henter disse klasser automatisk under initialisering af container.

Foråret har også kraftfulde metaprogrammeringsudvidelsespunkter, BeanPostProcessor og BeanFactoryPostProcessor, hvilket kan være afgørende, når en konfigureret applikation tilpasses til et bestemt miljø.

Endelig giver Spring nogle praktiske teknologier bygget oven på det, inklusive AOP, Transaktioner, Test Framework og mange andre. Hvis vi vil bruge disse, er det værd at holde fast i Spring IoC-containeren.

9.2. Dolk 2

Dagger 2-rammen er ikke så funktionsrige som Spring Framework, men det er populært i Android-udvikling på grund af dens hastighed (den genererer Java-kode, der udfører injektionen og bare udfører den i kørselstid) og størrelse.

Lad os sammenligne bibliotekernes metodetællinger og størrelser:

Kodein:Bemærk, at kotlin-stdlib afhængighed tegner sig for størstedelen af ​​disse tal. Når vi udelukker det, får vi 1282 metoder og 244 KB DEX-størrelse.

Dolk 2:

Vi kan se, at Dagger 2-rammen tilføjer langt færre metoder, og dens JAR-fil er mindre.

Med hensyn til brugen - det er meget ens, idet brugerkoden konfigurerer afhængigheder (gennem Injektor i Kodein- og JSR-330-kommentarerne i Dagger 2) og senere indsprøjtes dem gennem et enkelt metodekald.

Et nøglefunktion i Dagger 2 er dog, at det validerer afhængighedsgrafen på kompileringstidspunktet, så det tillader ikke, at applikationen kompileres, hvis der er en konfigurationsfejl.

10. Konklusion

Vi ved nu, hvordan man bruger Kodein til afhængighedsinjektion, hvilke konfigurationsmuligheder det giver, og hvordan det sammenlignes med et par andre populære DI-rammer. Det er dog op til dig at beslutte, om du vil bruge det til ægte projekter.

Som altid kan kildekoden til eksemplerne ovenfor findes på GitHub.


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