Christoph Grimmer-Dietrich

Dependency Injection mit Kotlin und Spring

Knowledge Base | Development

Das Spring Framework ermöglicht drei Arten von Dependency Injection:

  • Field Injection
  • Setter Injection
  • Constructor Injection

Als Guter Entwickler™ verwendet ihr natürlich Constructor Injection und keine Field Injection. Doch manchmal gibt es Situationen, in denen das nicht passt. In dem Fall steht mit Setter Injection eine gute Alternative zur Verfügung - dafür ein Beispiel mit Kotlin zu finden ist aber gar nicht so einfach.

Field Injection

Field Injection ist erstmal sehr einfach und wird deswegen - leider - häufig in der Lehre verwendet. Eine member-Variable wird direkt mit @Autowired annotiert und fertig.

class C {
    @Autowired
    lateinit var d: D
}

Das verführt leicht dazu eine zu große Anzahl von Abhängigkeiten in einer Klasse zu haben (Stichwort Class Complexity und Separation of Concerns). Außerdem lässt sich Field Injection nur schlecht Unit-testen; es erfordert entweder offene Klassen und Vererbung oder funktioniert nur mit reflection.

Constructor Injection

Constructor Injection funktioniert mit neueren Versionen von Spring (Version 4.3 und aufwärts) zusammen mit Kotlin sehr elegant. Wenn eine @Component-Klasse (z.B: @Service, @Controller …) nur einen Konstruktor hat wird dieser automatisch für die injection verwendet. Bei Kotlin ist der default constructor wiederum Teil der Klassensignatur:

@Component
class C(val d: D) {
    ...
}

Constructor Injection ist sehr strikt, verbietet zyklische Abhängigkeiten und lässt sich sehr gut Unit-testen: einfach die Mocks an den Konstruktor übergeben - fertig. Allerdings gibt es manchmal Situationen, in denen genau dieser strenge Rahmen Probleme bereitet - zum Beispiel bei optionalen Abhängigkeiten oder wenn sich zwei Services gegenseitig brauchen. Letzteres gilt es zu vermeiden, aber das geht nicht immer.

Setter Injection

In der Mitte zwischen diesen beiden Varianten findet sich die Setter Injection. Anders als Field Injection lassen sich Mocks in Unittest leicht übergeben. Anders als bei Constructor Injectionen ist es möglich, zyklische Abhängigkeiten abzubilden, da die Instanzen der Klassen nach der Erstellung wechselseitig injiziert werden. Trotz oder gerade wegen dieser Flexibilität verwende ich Setter Injection nur in Ausnahmefällen. Viel zu leicht mischt man unterschiedliche Dinge in einer Klasse (Separation of Concerns) oder erzeugt unnötige zyklische Abhängigkeiten, wodurch schnell das Lokalitätsprinzip verletzt wird. Wenn sich eure Architektur nicht mit Constructor Injection umsetzen lässt steht vielleicht eher ein Refactoring an.

Zurück zur Setter Injection mit Kotlin. Normalerweise schreibe ich bei Kotlin keine setter für die member einer Klasse, da diese automatisch erzeugt werden und ich beim Programmieren die assignment syntax (instance.member = value) verwende. Allerdings kann man die setter (und getter) explizit definieren:

var d: D? = null
    set(value) {
        d = value
    }

Sieht gut aus - funktionier aber nicht. Eine Gute IDE™ warnt davor, dass es sich hierbei um einen zyklischen Aufruf handelt. d ist nämlich kein field sondern eine property; beim Zuweisen im setter wird der setter aufgerufen… Hallo stack overflow! Statt dessen muss der Wert dem backing field der property zugewiesen werden, das der Compiler ebenfalls erzeugt.

var d: D? = null
    set(value) {
        field = value
    }

Das ist so natürlich Humbug und wird von unserer Guten IDE™ als redundante Implementierung markiert. Ich stelle zum Abschluss die beiden Varianten vor, mit der sich eine optionale sowie eine verpflichtende Setter Dependency Injection abbilden lassen.

@Service
class C {
    var d: D? = null
        @Autowired(required = false)
        set(value) {
           field = value
        }
}
@Service
class C {
    lateinit var d: D
        @Autowired
        set(value) {
           field = value
        }
}

Happy Coding!

5 Dec 2018 #Dependency-Injection #Kotlin #Spring #Setter