Einstieg in Spring Boot, Teil 9 Dependency-Injektion in Spring Boot

Autor / Redakteur: Dr. Dirk Koller / Stephan Augsten

Dependency Injection gehört zu den fundamentalen Konzepten von Spring. Set-Methoden erlaubten dies schon in der allerersten Framework-Version. Als Alternativen kamen später Konstruktor- und Field-Injektion hinzu. Aber welchen Typ nutzt man wann?

Firmen zum Thema

Alle reden vom Impfen, wir auch: Das als Dependency Injection bekannte Einimpfen von Objekten ist eines der grundlegenden Konzepten von Spring.
Alle reden vom Impfen, wir auch: Das als Dependency Injection bekannte Einimpfen von Objekten ist eines der grundlegenden Konzepten von Spring.
(Bild: julientromeur / Pixabay )

Objekte in Java (und in anderen objektorientierten Programmiersprachen) kommunizieren mit anderen Objekten, um die ihnen zugedachte Aufgabe zu erledigen. Programme sind somit Geflechte aus interagierenden Objekten. Der einfachste Weg, um an Instanzen von Helferklassen zu kommen, ist sie mit dem new-Operator selber zu erzeugen:

public class VeryImportantService {   private DatabasePersister databasePersister;   public VeryImportantService() {
      this.databasePersister = new DatabasePersister();
   }
}

Das ist einfach und schnell, hat aber verschiedene Nachteile. Durch die Zeile this.databasePersister = new DatabasePersister(); wird eine feste Abhängigkeit zu exakt dem Typ DatabasePersister geschaffen. DatabasePersister lässt sich nicht mehr ohne Weiteres gegen einen anderen Typ (FilesystemPersister, CloudPersister oder ähnliches) austauschen.

Anders ist das bei der Verwendung von Dependency Injektion (DI). In diesem Fall instanziiert das Objekt nicht selbst die benötigte Klasse, sondern bekommt die Abhängigkeit von einem DI-Container beim Start der Anwendung injiziert. Der Container, in unserem Fall Spring, ist also nun in der Verantwortung, die erforderlichen Objekte zu erzeugen.

„Spring Boot“-Tutorial
Bildergalerie mit 18 Bildern

Hierfür durchsucht Spring die zur Verfügung stehenden Beans im Container nach passenden Typen. Wegen dieser Übergabe der Verantwortlichkeit an das Framework spricht man hier auch von Inversion of Control (IoC). Der Vorteil: Die feste Abhängigkeit fällt weg.

Wird die Abhängigkeit gar in Form eines Interface, hier beispielsweise Persister, an Stelle einer konkreten Klasse angegeben, können konfigurationsabhängig sogar verschiedene Typen injiziert werden. Dadurch lassen nicht nur verschiedene Implementierungen eines Typs, sondern auch Testklassen (Mocks und Stubs) injizieren.

Dependency Injektion ist eines der wichtigsten Pattern überhaupt und kann in Spring – wie eingangs erwähnt – auf drei verschiedenen Wegen realisiert werden.

Constructor-Injection

Bei der Konstruktor-Injektion wird das erforderliche Objekt als Argument im Konstruktor aufgeführt und dabei automatisch vom Container injiziert:

@Component
public class VeryImportantService {
   // Abhängigkeit zu Persister
   private Persister persister;
   // Konstruktor, über den der Container ein Persister-Objekt injiziert
   @Autowired
   public VeryImportantService(Persister persister) {
      this.persister = persister;
   }
   // Logik, die den injizierten Persister benutzt
   // ...
}

Seit Spring 4.3 kann die Annotation @Autowired auch weggelassen werden, wenn es nur einen Konstruktor gibt. Bei mehreren Konstruktoren wird mit @Autowired definiert, in welchen injiziert wird.

Setter-Injection

Die Setter-Injektion funktioniert ähnlich, der Spring Container ruft die Setter-Methoden nach der Instanziierung des Beans auf:

class VeryImportantService {   // Abhängigkeit zu Persister
   private Persister persister;
   @Autowired
   // Setter, über den der Container ein Persister-Objekt injiziert
   public void setPersister(Persister persister) {
      this.persister = persister;
   }
   // Logik, die den injizierten Persister benutzt
   // ...
}

Mithilfe der @Required-Annotation kann man bei Bedarf sicherstellen, dass die Eigenschaft auch wirklich vorhanden und nicht etwa null ist.

Field-Injection

Die dritte Möglichkeit ist die Field-Injektion. Sie sieht sehr sauber aus und erfordert am wenigsten Code. Es wird lediglich die Membervariable mit @Autowired annotiert. Die Injektion erfolgt nach der Instanziierung der Klasse:

class VeryImportantService {   @Autowired
   private Persister persister;
   // Logik, die den injizierten Persister benutzt
   // ...
}

Die Qual der Wahl

Die verschiedenen Injektionsmöglichkeiten haben alle ihre Vor- und Nachteile und diese werden im Netz rege diskutiert, beispielsweise hier im Spring-Blog. In der Praxis haben sich die folgenden drei Regeln bewährt:

  • 1. Für alle wirklich erforderlichen Abhängigkeiten nutzt man am besten Construktor Injection. Das Objekt wird gar nicht erst erzeugt, wenn die Abhängigkeiten nicht vorhanden sind und ein weiterer Check kann entfallen.
  • 2. Bei optionalen Eigenschaften verwendet man die Setter-Injection, insbesondere wenn für den Fall, dass kein Objekt vorhanden ist, sinnvolle Default-Values gesetzt werden können. Ansonsten muss man notgedrungen überall prüfen, dass das Objekt nicht null ist, bevor man es anspricht.
  • 3. Die Field-Injection verwendet man am besten gar nicht. Das Problem dieser Variante ist, dass sich eine neue Membervariable sehr leicht zufügen lässt. Zu leicht. So entstehen sehr schnell Klassen mit sehr vielen Abhängigkeiten, die aus ihrer eigentlichen Verantwortlichkeit heraus wachsen.

Bei der Konstruktor-Injection fallen Konstruktoren mit mehr als fünf Argumenten schnell ins Auge, beziehungsweise ziehen als Bad Smell (muss die Klasse womöglich aufgeteilt werden?) in die Nase. Neuere Frameworks wie Angular bieten die Field-Variante deshalb gar nicht mehr an. Allerdings findet sie wegen der Übersichtlichkeit relativ oft in Büchern oder Blog-Artikeln Verwendung.

Collection-Injection

Erwähnenswert ist schließlich noch die Möglichkeit, sogenannte Collections, also Listen, Sets oder Maps zu injizieren. Dank typisierter Collections lässt sich exakt beschreiben, welchen Typs das zu injizierende Objekt sein soll. Im folgenden Beispiel wird im Container nach einer Map gesucht, die Strings als Schlüssel und Floats als Werte enthält.

public class VeryImportantService {   private Map<String, Float> accounts;   public void setAccounts(Map<String, Float> accounts) {
      this.accounts = accounts;
   }
}

Verwandt aber doch nicht das gleiche ist das Injiziieren individuell konfigurierter Beans als Collections. In der folgenden Konfiguration werden drei verschiedene Beans konfiguriert, die alle das Persister-Interface implementieren:

@Configuration
public class PersisterConfig {
   @Bean
   public Persister databasePersister() {
      return new DatabasePersister();
   }
   @Bean
   public Persister filesystemPersistery() {
      return new FilesystemPersister();
   }
   @Bean
   public Persister cloudPersister() {
      return new CloudPersister();
   }
}
„Spring Boot“-Tutorial
Bildergalerie mit 18 Bildern

Alle drei Beans auf einmal lassen sich nun beispielsweise in eine Liste vom Typ Persister injizieren:

@Service
public class PersisterService {
   @Autowired
   List<Persister> persisters;   // ...
}

Damit endet das große Impf-Special. Im nächsten Teil der Serie werfen wir wieder mal einen Blick in die Werkzeugkiste und schauen uns das Projekt Lombok an. Bis dann!

(ID:47242676)

Über den Autor