Eine Sammlung von nützlichen Code-Schnipseln
How-To: Dependency Injection mit Spring
Stichworte
dependency injection
,
ioc
,
singleton
,
spring
Einleitung
Dependency Injection (DI) dient zur Konfiguration von Software-Komponenten (Erzeugung und Initialisierung von Objekten), wobei deren Abhängigkeit untereinander so gering wie möglich gehalten werden soll. Das Prinzip hinter DI heißt Inversion of Control (IoC). Hiernach werden abhängige Objekte nicht von der Komponente selbst erzeugt oder beschafft, sondern diese werden von Außen in sie injiziert. Die Injektion wird von einem DI-Framework - etwa SpringFramework - durchgeführt.
Im folgenden Beispiel benötigen mehrere Komponenten jeweils eine Referenz auf ein Config-Objekt, das für die Anwendung wichtige Präferenzen speichern soll. Das Config-Objekt wird durch DI in die abhängigen Komponenten (ReadController, WriteController) injiziert.
Das Config-Objekt enthält einfach eine Map der Präferenzen nach dem Schema Schlüssel->Wert. Die von Spring verwalteten Objekte (also im Beispiel Config, ReadController, WriteController) werden als Beans bezeichnet.
Das zu injizierende Bean
Zunächst wird eine Schnittstelle für das Config-Objekt definiert. Dies ist zwar nicht unbedingt notwendig für DI. Aber meistens ist es besser und zukunftsicherer gegen Schnittstellen statt gegen konkrete Klassen zu programmieren, wenn man die Implementierung später austauschen möchte, was durch DI ja erleichtert werden soll.
Die zu konfigurierenden Beans
Das Config-Objekt bzw. die Implementierung ConfigImpl wird von den Klassen ReadController und WriteController benötigt. Die Klassen definieren hierzu je eine Member-Variable vom Typ Config. Getter oder Setter sind nicht notwendig. Die Variable wird mit der Annotation @Autowired markiert. Hierdurch wird Spring angewiesen, die Variable automatisch zu "verkabeln". Spring sucht eine Bean in seinem Kontext (s.u.), die dem Typ der zu injizierenden Variable (Config) entspricht. Dieses Bean wird der Member-Variablen zugewiesen.
Gibt es im Kontext mehr eine Bean des passenden Typs, muss zusätzlich die ID des gewünschten Objekts angegeben werden. Dies geschieht mit der Annotation @Qualifier("idDerBean").
Konfigurationsdatei für Spring
Schließlich muss Spring selbst konfiguriert werden. In einer XML-Datei werden die Namen (IDs) und Klassen der Beans angegeben.
Das Hauptprogramm
Jetzt kann Spring gestartet und die konfigurierten Beans können benutzt werden:
Singleton?
Standardmäßig werden Beans von Spring im Singleton-Scope erzeugt:
Dies darf aber nicht mit dem Singleton-Entwurfsmuster, wonach nur ein einziges Objekt dieser Klasse möglich ist, verwechselt werden. Spring legt in dieser Einstellung nur ein Objekt pro Kontext und pro Bean-ID an. Mehrere Aufrufe bzw. Referenzierungen des Beans liefern immer das selbe Objekt zurück. Es ist aber durchaus möglich, andere Beans dieses Typs mit unterschiedlichen IDs anzulegen.
Das Gegenteil des Singleton-Scopes ist der Prototyp. Prototype-Beans werden bei jedem Aufruf und jeder Referenzierung von Spring neu angelegt. Ändert man den Scope des Config-Beans im obigen Beispiel auf Prototype, so erhalten WriterController und ReaderController jeweils ein eigenes ConfigImpl-Objekt.
Ressourcen
Dependency Injection (DI) dient zur Konfiguration von Software-Komponenten (Erzeugung und Initialisierung von Objekten), wobei deren Abhängigkeit untereinander so gering wie möglich gehalten werden soll. Das Prinzip hinter DI heißt Inversion of Control (IoC). Hiernach werden abhängige Objekte nicht von der Komponente selbst erzeugt oder beschafft, sondern diese werden von Außen in sie injiziert. Die Injektion wird von einem DI-Framework - etwa SpringFramework - durchgeführt.
Im folgenden Beispiel benötigen mehrere Komponenten jeweils eine Referenz auf ein Config-Objekt, das für die Anwendung wichtige Präferenzen speichern soll. Das Config-Objekt wird durch DI in die abhängigen Komponenten (ReadController, WriteController) injiziert.
Das Config-Objekt enthält einfach eine Map der Präferenzen nach dem Schema Schlüssel->Wert. Die von Spring verwalteten Objekte (also im Beispiel Config, ReadController, WriteController) werden als Beans bezeichnet.
Das zu injizierende Bean
Zunächst wird eine Schnittstelle für das Config-Objekt definiert. Dies ist zwar nicht unbedingt notwendig für DI. Aber meistens ist es besser und zukunftsicherer gegen Schnittstellen statt gegen konkrete Klassen zu programmieren, wenn man die Implementierung später austauschen möchte, was durch DI ja erleichtert werden soll.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package depinjdemo; /* * Schnittstelle für Konfiguration-Objekt. * * In der Anwendung soll ein und dasselbe Config-Objekt an vielen * verschiedenen Stellen (d.h. Klassen) verwendet werden. */ public interface Config { public String getValue(String key); public void setValue(String key, String value); } //-------------------------------------------------------------- package depinjdemo; import java.util.HashMap; import java.util.Map; /* * Standard-Implementierung der Config-Schnittstelle. */ public class ConfigImpl implements Config { private Map<String, String> prefs = new HashMap<String, String>(); public String getValue(String key) { return this.prefs.get(key); } public void setValue(String key, String value) { this.prefs.put(key, value); } }
Die zu konfigurierenden Beans
Das Config-Objekt bzw. die Implementierung ConfigImpl wird von den Klassen ReadController und WriteController benötigt. Die Klassen definieren hierzu je eine Member-Variable vom Typ Config. Getter oder Setter sind nicht notwendig. Die Variable wird mit der Annotation @Autowired markiert. Hierdurch wird Spring angewiesen, die Variable automatisch zu "verkabeln". Spring sucht eine Bean in seinem Kontext (s.u.), die dem Typ der zu injizierenden Variable (Config) entspricht. Dieses Bean wird der Member-Variablen zugewiesen.
Gibt es im Kontext mehr eine Bean des passenden Typs, muss zusätzlich die ID des gewünschten Objekts angegeben werden. Dies geschieht mit der Annotation @Qualifier("idDerBean").
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package depinjdemo; import org.springframework.beans.factory.annotation.Autowired; // Beispielklasse, die Config-Objekt verwenden soll. public class ReadController { // Config-Objekt wird von Spring injiziert. @Autowired private Config conf; public ReadController() {} public void printConfig() { System.out.println("Konfiguration: Test=" + this.conf.getValue("Test")); } } //------------------------------------------ package depinjdemo; import org.springframework.beans.factory.annotation.Autowired; // Beispielklasse, die Config-Objekt verwenden soll. public class WriteController { // Config-Objekt wird von Spring injiziert. @Autowired private Config conf; public WriteController() {} public void doConfig() { this.conf.setValue("Test", "42"); } }
Konfigurationsdatei für Spring
Schließlich muss Spring selbst konfiguriert werden. In einer XML-Datei werden die Namen (IDs) und Klassen der Beans angegeben.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http: //www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans.xsd http: //www.springframework.org/schema/context http: //www.springframework.org/schema/context/spring-context.xsd"> <!-- Beans über Annotationen konfigurieren --> <context:annotation-config /> <bean id="configBean" class="depinjdemo.ConfigImpl" /> <bean id="reader" class="depinjdemo.ReadController" /> <bean id="writer" class="depinjdemo.WriteController" /> </beans>
Das Hauptprogramm
Jetzt kann Spring gestartet und die konfigurierten Beans können benutzt werden:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package depinjdemo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /* * Hauptprogramm für Spring-DI-Demo */ public class Main { public static void main(String... args) { // Spring initialisieren und Kontext laden. // applicationContext.xml ist die oben angegebene Konfigurationsdatei. ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // Über das Context-Objekt können die konfigurierten Beans über ihre Namen abgerufen werden. WriteController wc = (WriteController) ctx.getBean("writer"); ReadController rc = (ReadController) ctx.getBean("reader"); wc.doConfig(); rc.printConfig(); } }
Singleton?
Standardmäßig werden Beans von Spring im Singleton-Scope erzeugt:
Dies darf aber nicht mit dem Singleton-Entwurfsmuster, wonach nur ein einziges Objekt dieser Klasse möglich ist, verwechselt werden. Spring legt in dieser Einstellung nur ein Objekt pro Kontext und pro Bean-ID an. Mehrere Aufrufe bzw. Referenzierungen des Beans liefern immer das selbe Objekt zurück. Es ist aber durchaus möglich, andere Beans dieses Typs mit unterschiedlichen IDs anzulegen.
Das Gegenteil des Singleton-Scopes ist der Prototyp. Prototype-Beans werden bei jedem Aufruf und jeder Referenzierung von Spring neu angelegt. Ändert man den Scope des Config-Beans im obigen Beispiel auf Prototype, so erhalten WriterController und ReaderController jeweils ein eigenes ConfigImpl-Objekt.
Ressourcen
- DI-Demo.zip - DI-Demo als Eclipse-Projekt. Spring JARs müssen selbst heruntergeladen und im lib-Verzeichnis gespeichert werden.
- SpringSource
- Spring Referenz-Dokumentation
- Chapter 3. The IoC container
- DI mit Google Guice - Alternative DI mit Guice im Blog von thE_29
Kommentare 0







