Kotlin avhengighetsinjeksjon med Kodein

1. Oversikt

I denne artikkelen introduserer vi Kodein - et rent Kotlin-avhengighetsinjeksjonsrammeverk (DI) - og sammenligner det med andre populære DI-rammer.

2. Avhengighet

La oss først legge til Kodein-avhengigheten til vår pom.xml:

 com.github.salomonbrys.kodein kodein 4.1.0 

Vær oppmerksom på at den siste tilgjengelige versjonen er tilgjengelig enten på Maven Central eller jCenter.

3. Konfigurasjon

Vi bruker modellen nedenfor for å illustrere Kodein-basert konfigurasjon:

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. Bindende typer

Kodein-rammeverket tilbyr forskjellige bindingstyper. La oss se nærmere på hvordan de fungerer og hvordan vi bruker dem.

4.1. Singleton

Med Singleton bindende, en målbønne instantieres lat ved første tilgang og gjenbrukes på alle ytterligere forespørsler:

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

Merk: vi kan bruke Kodein.instance () for å hente målstyrte bønner basert på en statisk variabel type.

4.2. Ivrig Singleton

Dette ligner på Singleton bindende. Den eneste forskjellen er at initialiseringsblokken kalles ivrig:

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

4.3. Fabrikk

Med Fabrikk binding, mottar initialiseringsblokken et argument, og et nytt objekt returneres fra det hver gang:

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

Merk: vi kan bruke Kodein.instance () for å konfigurere transitive avhengigheter.

4.4. Multiton

Multiton binding er veldig lik Fabrikk bindende. Den eneste forskjellen er at det samme objektet returneres for det samme argumentet i påfølgende samtaler:

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

4.5. Forsørger

Dette er en no-arg Fabrikk bindende:

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

4.6. Forekomst

Vi kan registrer en forhåndskonfigurert bønneinstans i beholderen:

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

4.7. Merking

Vi kan også registrer mer enn en bønne av samme type under forskjellige tagger:

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 merket binding og antas som skal brukes til konfigurasjonskonstanter - enkle typer uten arv:

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

5. Bindinger Separasjon

Kodein lar oss konfigurere bønner i separate blokker og kombinere dem.

5.1. Moduler

Vi kan gruppere komponenter etter bestemte kriterier - for eksempel alle klasser relatert til datapresistens - og kombiner blokkene for å bygge en resulterende container:

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

Merk: ettersom moduler inneholder bindende regler, blir målbønner opprettet på nytt når den samme modulen brukes i flere Kodein-forekomster.

5.2. Sammensetning

Vi kan utvide en Kodein-forekomst fra en annen - dette gjør at vi kan bruke bønner på nytt:

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

5.3. Overstyring

Vi kan overstyre bindinger - dette kan være nyttig for testing:

class InMemoryDao: Dao val commonModule = Kodein.Module {bind () with singleton {MongoDao ()} bind () with singleton {Service (instance (), "myService")}} val testContainer = Kodein {import (commonModule) bind ( overstyrer = sann) med singleton {InMemoryDao ()}} val dao: Dao = testContainer.instance () assertThat (dao) .isInstanceOf (InMemoryDao :: class.java)

6. Flerbindinger

Vi kan konfigurere mer enn en bønne med samme vanlige (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

Applikasjonskoden vår var ikke klar over Kodein i alle eksemplene vi brukte før - den brukte vanlige konstruktørargumenter som ble gitt under containerens initialisering.

Imidlertid tillater rammeverket en alternativ måte å konfigurere avhengigheter gjennom delegerte egenskaper og Injektorer:

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 () med singleton {Service (forekomst (), "myService")}} val controller = Controller2 () controller.injectDependencies (kodein) assertThat (controller.service) .isNotNull

Med andre ord definerer en domeneklasse avhengigheter gjennom en injektor og henter dem fra en gitt container. En slik tilnærming er nyttig i spesifikke miljøer som Android.

8. Bruke Kodein With Android

I Android er Kodein-containeren konfigurert i en egendefinert applikasjon klasse, og senere, er det bundet til Kontekst forekomst. Alle komponenter (aktiviteter, fragmenter, tjenester, kringkastingsmottakere) antas å være utvidet fra verktøyklassene som KodeinActivity og KodeinFragment:

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

9. Analyse

I denne delen vil vi se hvordan Kodein sammenlignes med populære DI-rammer.

9.1. Vårramme

Spring Framework er mye mer funksjonsrikt enn Kodein. For eksempel har våren en veldig praktisk komponentskanningsanlegg. Når vi markerer klassene våre med spesielle merknader som @Komponent, @Service, og @Named, komponentskanningen henter automatisk disse klassene under initialisering av container.

Våren har også kraftige utvidelsespunkter for metaprogrammering, BeanPostProcessor og BeanFactoryPostProcessor, som kan være avgjørende når du tilpasser et konfigurert program til et bestemt miljø.

Endelig gir våren noe praktiske teknologier bygget på toppen av den, inkludert AOP, Transaksjoner, Test Framework og mange andre. Hvis vi vil bruke disse, er det verdt å holde oss til Spring IoC-beholderen.

9.2. Dolk 2

The Dagger 2 framework er ikke like funksjonsrikt som Spring Framework, men det er populært i Android-utvikling på grunn av hastigheten (den genererer Java-kode som utfører injeksjonen og bare utfører den i kjøretid) og størrelse.

La oss sammenligne bibliotekenes antall metaller og størrelser:

Kodein:Merk at kotlin-stdlib avhengighet utgjør mesteparten av disse tallene. Når vi ekskluderer det, får vi 1282 metoder og 244 KB DEX-størrelse.

Dolk 2:

Vi kan se at Dagger 2-rammeverket legger til langt færre metoder, og JAR-filen er mindre.

Når det gjelder bruken - den er veldig lik ved at brukerkoden konfigurerer avhengigheter (gjennom Injektor i Kodein og JSR-330-merknader i Dagger 2) og senere injiserer dem gjennom en enkelt metodeanrop.

Et sentralt trekk ved Dagger 2 er imidlertid at det validerer avhengighetsgrafen på kompileringstidspunktet, slik at applikasjonen ikke kan kompilere hvis det er en konfigurasjonsfeil.

10. Konklusjon

Vi vet nå hvordan du bruker Kodein for avhengighetsinjeksjon, hvilke konfigurasjonsalternativer det gir, og hvordan det sammenlignes med et par andre populære DI-rammer. Det er imidlertid opp til deg å bestemme om du vil bruke den i ekte prosjekter.

Som alltid kan kildekoden for eksemplene ovenfor bli funnet på GitHub.


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