D) Dependency Injection mit Spring

Dieser Artikel befasst sich mit einem sehr mächtigem und sehr weit verbreitetem Framework für die Anwendungsentwicklung: Spring. Spring wird hauptsächlich im Bereich der „Enterprise-Anwendungen“ eingesetzt – also im Wesentlichen verteilten Systemen, die gleichzeitig von vielen Benutzern verwendet werden und dadurch besonderen Anforderungen bezüglich Erreichbarkeit, Geschwindigkeit und Sicherheit genügen müssen. Es kann aber auch den Alltag beim Erstellen von kleineren Programmen erleichtern.

Spring

Für Enterprise-Anwendungen gibt es in Java einen eigenen, von Sun zur Verfügung gestellten Werkzeugkasten, die Java Enterprise Edition. In dieser Ausgabe der Java Plattform hat die Herstellerfirma Sun Microsystems (mittlerweile von Oracle gekauft) festgelegt, mit welchen Technologien und in welcher Umgebung Anwendungen geschrieben werden sollen, damit sie den oben definierten Anforderungen an eine Enterprise-Anwendung gerecht werden. Andere Firmen wie zum Beispiel IBM oder JBoss haben dann auf Basis dieser Festlegungen sogenannte Application-Server programmiert, die dem Entwickler einer Enterprise-Anwendung die geforderte Umgebung und die benötigten Werkzeuge zur Verfügung stellen.
Diese Application Server sind meist sehr aufwendig zu wartende und zu bedienende Programme, die viel Fachwissen im Umgang erfordern, damit sie die gemachten Versprechungen auch tatsächlich einlösen können. Zudem legt sich der Programmierer durch die Verwendung von JEE und dem dazu gehörigen Application Server auf eine bestimmte Menge von APIs und Technologien fest. Die durch die JEE vorgegebenen APIs sind aber nicht in jeder Situation passend, sei es, weil das Problem mit einer anderen verfügbaren Technik einfacher zu lösen wäre, oder einfach durch persönliche Präferenzen des Entwicklers bedingt.
Spring schränkt den Programmierer hier bedeutend weniger ein: Für zahllose bekannte und beliebte Frameworks wie etwa Hibernate, iBatis, JDBC, JDO und auch das in die JEE integrierte JPA existieren Integrationsmöglichkeiten – und die gerade genannten beschränken sich nur auf den Bereich der Persistenz. Auf den anderen abgedeckten Gebieten sieht es ähnlich aus. Der Programmierer muss sich nicht von seinen liebgewonnenen Werkzeugen trennen, sondern er erhält einen Rahmen, der sie integriert und zusammenführt.
Den Rahmen bilden vor allem Dependency Injection (DI) und Aspektorientierte Programmierung (AOP), die es möglich machen, sehr viele Aufgaben des Programmieralltags sehr elegant zu lösen. Daneben bietet Spring eine Reihe von Funktionen, die zwar für sich genommen weniger spektakulär erscheinen, jedoch die große Auswahl an zur Verfügung stehenden Frameworks und deren nahtlose Integration ineinander erst möglich machen. Dazu gehört etwa das Exception-Übersetzer- und das Template-Pattern, die beide von Spring für unterschiedliche Bereiche implementiert werden.

Dependency Injection

In objektorientierten Programmen hat jedes einzelne Objekt sehr viele Abhängigkeiten zu anderen Objekten. Üblicherweise werden diese Abhängigkeiten von der Klasse direkt aufgelöst. Meistens steht dann irgendwo ein new im Code, mit dem ein konkretes Objekt einer Klasse erzeugt wird. Dies führt spätestens dann zu Problemen, wenn man dieses Objekt durch ein anderes ersetzen möchte. Denn dann muss man an jeder Stelle an der eine Instanz erzeugt wird, den Code ändern.
Zunächst mag dieses Vorhaben weit hergeholt erscheinen, denn der Programmierer sollte ja eine fundierte und begründete Entscheidung getroffen haben, warum er gerade ein Objekt dieses Typs erzeugen wollte. Doch denkt man einmal über die reine Implementierung hinaus etwa an die Testphase, so wird offensichtlich, dass es mitunter eine gute Idee sein kann, andere Objekte als die tatsächlichen zu benutzen. Ein klassisches Beispiel ist die Verwendung einer Klasse, die Daten aus einer Datei oder aus dem Netzwerk liest.
Im Test möchte man im Allgemeinen auch Situationen testen, in denen diese Lesevorgänge scheitern, da etwas außerhalb des Programms nicht in Ordnung ist – zum Beispiel weil das Netzwerkkabel nicht angeschlossen wurde oder die betreffende Datei fehlt. Solche Fehlersituationen sind in automatisierten Tests schwierig herbeizuführen, wenn man dazu auf die tatsächliche, physikalische Welt angewiesen ist. Dagegen ist es sehr leicht, einen InputStream zu schreiben, der einfach so tut, als könne er die benötigten Daten nicht liefern. Diesen Ansatz des Tests nennt man Mocking, die untergeschobenen Objekte, die ihre Funktionalität nur vortäuschen, heißen Mock-Objekte.
Für Situationen, in denen das Bedürfnis später einmal eine andere Implementierung einer Klasse nutzen zu wollen rechtzeitig erkannt wurde, gibt es unter anderem das Factory Design Pattern. Hier wird die Erzeugung eines Objekts in eine spezialisierte Klasse ausgelagert, man selbst ruft nur noch eine (meist) statische Methode auf, die die neue Instanz zurückgibt. Nun muss nur noch an einer zentralen Stelle geändert werden, wenn man die Implementierung austauschen möchte. Ein Problem dieser Herangehensweise ist, dass die Factories sehr komplex werden können – für statische Typsicherheit müssen sehr viele Methoden eingebaut werden und soll die zeitweise Auslieferung von Mock-Objekten möglich sein, muß dafür eine entsprechende Konfigurationsschnittstelle geschaffen werden. Vor allem aber handelt man sich an sehr vielen Stellen des Programms eine Abhängigkeit zur Factory ein, was die isolierte Wiederverwendung sehr erschwert oder gar unmöglich macht.
Dependency Injection geht einen anderen Weg. Die Klasse deklariert nur noch, welche anderen Objekte sie benötigt. Diese Deklaration kann durch Konstruktorparameter oder Instanzvariablen mit passenden Settern geschehen. Anschließend übergibt eine Klasse, die ein neues Objekt erstellen will, diesem alle abhängigen Objekte über die vorgesehenen Schnittstellen. Durch diesen Ansatz ist der Client der Klasse selbst dafür verantwortlich, wie die Klasse konfiguriert wird. Handelt es sich beim Client um eine Testklasse, die auf Mock-Objekte zurückgreifen möchte, so kann sie das problemlos tun.
Dependency Injection ist kein von Spring abhängiges Pattern. Untenstehendes Beispiel zeigt die Verwendung einer durch Dependency Injection konfigurierten Klasse ohne den Einsatz von Spring.

Motor motor = new Viertakter();
Felge felge = new AluFelge();
List<Rad> raeder = new Array List<Rad>();
for(int i = 0; i < 4; i ++){
  raeder.add(new PkwRad(felge));
}
Auto bmw = new Auto(Marke.BMW, motor, raeder, Color.BLACK);
bmw.setRadio(new Radio()) ;
bmw.setBordComputer(new BordComputer());
System.out.println("BMW -Stylefaktor: " + bmw.getStyleFaktor());

In den Zeilen 1 – 6 werden zunächst die Objekte erstellt, von denen die Klasse Auto abhängt. Das Auto selbst wird in Zeile 8 erzeugt, dabei werden die Marke, der Motor, die Räder und die Farbe als Konstruktorargumente injiziert. Anschließend werden weitere Objekte durch Aufruf von Setter-Methoden übergeben. Da der Aufruf von Setter-Methoden nicht erzwungen werden kann, macht es Sinn, alle zwingend erforderlichen Objekte als Konstruktorargumente zu verlangen und lediglich optionale Abhängigkeiten über Setter zu konfigurieren.
Wollte man alle Objekte in einer größeren Applikation wie oben direkt im Code konfigurieren, würde man aufgrund der Vielzahl von direkten und transitiven Abhängigkeiten eine sehr große Menge Code schreiben müssen.

Transitive Abhängigkeiten sind Abhängigkeiten um (mindestens) zwei Ecken – wenn Sie Objekt A konfigurieren, welches von Objekt B abhängt, welches wiederum Objekt C benötigt, so ist die Abhängigkeit A-B eine direkte und A-C eine transitive.

Diese Arbeit kann Spring Ihnen sehr erleichtern.

Eine Umgebung für Spring schaffen

Im Vergleich zu JEE ist die Ablaufumgebung für Spring sehr schnell hergestellt, denn im Gegensatz zu Suns Standard benötigt Spring keinen mächtigen Application-Server, sondern nur einige JAR-Dateien. Laden Sie Spring herunter und entpacken Sie die Datei. Im Ordner /dist finden Sie 20 JARs. In diesen Dateien versteckt sich die gesamte Funktionalität des Spring-Frameworks. Da Spring weit mehr als nur Dependency Injection kann, brauchen wir für diese Einführung nur die zum Kern des Frameworks gehörenden JARs:

  • org.springframework.asm-3.0.0.RELEASE.jar
  • org.springframework.beans-3.0.0.RELEASE.jar
  • org.springframework.context-3.0.0.RELEASE.jar
  • org.springframework.context.support-3.0.0.RELEASE.jar
  • org.springframework.core-3.0.0.RELEASE.jar
  • org.springframework.expression-3.0.0.RELEASE.jar

Fügen Sie diese Dateien dem Classpath Ihrer Anwendung hinzu.

Natürlich können Sie auch einfach alle JARs aus dem Distributionsverzeichnis in den Classpath aufnehmen. Sie erhalten dadurch zwar weit mehr Funktionalität als wir in dieser Einführung benutzen werden, aber das schadet keineswegs.

Sie können nun Dependency Injection auf Basis von Spring nutzen. Die restlichen 14 JARs teilen sich auf die Module Web, Data Access, AOP, Classloader Instrumentation und Test auf, die jeweils nützliche Funktionen in ihrem speziellen Bereich anbieten. Für Details sei hier auf die Referenzdokumentation verwiesen, die im Verzeichnis /docs des Download-Pakets mitgeliefert wird. Durch die Aufteilung des Frameworks in verschiedene, funktional getrennte Module können Sie sich die Rosinen aus Springs Funktionsumfang herauspicken und jeweils nur die Teile einsetzen, die Sie brauchen und verwenden wollen.
Neben den Dateien direkt aus dem Framework benötigt Spring nur noch ein weiteres JAR: Apache Commons Logging. Diese Datei sorgt dafür, dass Sie auf der Konsole einige Ausgaben von Spring zu sehen bekommen, anhand derer Sie nachvollziehen können, was gerade im Framework passiert. Dabei ist Apache Commons Logging kein eigenständiges Logging-Framework, sondern vielmehr eine Abstraktionsschicht, die es Spring erlaubt, mit zahlreichen anderen Logging-Lösungen zusammenarbeiten. Wie Sie Commons Logging und Spring so konfigurieren, dass es auf Ihre spezielle Logging-Bibliothek zurückgreift, ist in der Referenzdokumentation beschrieben. Wir werden das Commons-Logging-JAR einfach dem Classpath hinzufügen und uns mit den so gelieferten Ausgaben via java.util.logging zufrieden geben.

Ein Auto mit Spring bauen

Mittels Dependency Injection werden Objektnetze konstruiert – also ein Objekt einer Klasse mit all seinen direkten und transitiven Abhängigkeiten. Als Beispiel für so ein Objektnetz soll ein Klassiker der Literatur über Objektorientierung dienen: das Auto. Darstellung 1 zeigt das Auto mit seinen Abhängigkeiten zu Motoren, Rädern und ähnlichen Komponenten.

Das Auto und seine Abhängigkeiten

Die Anwendung, die auf dieses Objektnetz zugreift, ist dagegen eher spartanisch: Anhand der benutzten Bauteile und deren Eigenschaften berechnet das Auto seinen „Stylefaktor“, der anschließend auf der Konsole ausgegeben wird. Dieser „Stylefaktor“ ist vollkommen subjektiv und soll nur demonstrieren, dass das Objektnetz vollständig initialisiert ist und mit normalen Java-Code abgerufen werden kann.
Mit Spring werden die Abhängigkeiten innerhalb des Objektnetzes in einer XML-Datei deklariert. Seit geraumer Zeit ist es alternativ auch möglich, diese Arbeit in Form von Annotationen direkt im Java-Code zu erledigen, gebräuchlicher ist aber weiterhin der Weg über XML. Darüberhinaus empfinde ich es als Verletzung des Dependency-Injection-Prinzips, wenn die Information, wie eine Abhängigkeit aufgelöst werden soll, direkt in einer Klasse verankert ist. Dies ist natürlich ein Thema, über das sich trefflich diskutieren lässt, doch für mich ist klar, dass die Beschreibung des Objektnetzes als Ganzem an einem zentralem Platz außerhalb der betroffenen Klassen gesammelt werden muss.
Betrachten wir also zunächst die von Spring verwendeten XML-Dateien. Jede Spring-Konfigurationsdatei hat folgenden Rahmen:

<?xml version="1.0" encoding="UTF−8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema−instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring−beans−3.0.xsd">
  <!−− Hier Beans definieren −−>
</beans>

In der ersten Zeile wird die verwendete Version des XML-Standards und die verwendete Zeichenkodierung festgelegt. Anschließend werden im Root-Element der Konfigurationsdatei beans die verwendeten XML-Namespaces festgelegt. Im Laufe dieser Einführung wird fast immer nur der Basisnamespace beans benutzt, der als Standardnamespace definiert wird. Spring bringt noch andere Namespaces mit, zum Beispiel aop für die Verwendung aspektorientierter Programmierung oder util um häufig auftretende Sachverhalte besser ausdrücken zu können. Innerhalb des oben notierten Rahmens (auf den im Folgenden aus Platzgründen verzichtet wird) werden nun die von Spring verwalteten Beans definiert. Als erstes soll der Übersichtlichkeit halber nur ein kleiner Ausschnitt des Autos – ein Rad mit seiner Felge – zusammengebaut werden.

<bean id="aluFelge" class="de.jbb.springtut.auto.AluFelge" />
<bean id="bmwRad" class="de.jbb.springtut.auto.PkwRad">
  <constructor−arg ref="aluFelge" />
  <property name="markierung" value="mein Rad" />
</ bean>

Im Beispielprogramm hat jedes Rad eine Felge, die dem Konstruktor als Argument übergeben wird. Optional kann das Rad zudem mit einem Text als Markierung versehen werden, etwa um seine Position festzuhalten, wenn dem Auto mal Winterreifen aufgezogen werden sollen.
In der XML-Datei wird Spring zunächst die Felge bekanntgemacht. Dazu wird die Felge als bean-Element beschrieben. Im Attribut id wird ihr ein eindeutiger Namen gegeben, mit dessen Hilfe diese Definition durch andere Beans referenziert werden kann.
Im Attribut class wird festgelegt, von welchem Typ das zu konstruierende Java-Objekt sein soll. Da die Felge weiter keine Daten erwartet, ist sie damit vollständig beschrieben. Das Rad wird analog dazu beschrieben. Das Rad ist vom Typ PkwRad und soll bmwRad heißen. Anschließend werden die Abhängigkeiten des Rades aufgelöst: Als Konstruktorargument muß eine Felge übergeben werden. Dies geschieht durch das eingebettete Element constructor-arg, das auf die gerade definierte Felge Bezug nimmt. Dazu wird dem Attribut „ref“ die Id der Felge übergeben. Alternativ könnte man hier mit dem Attribut value direkt einen primitiven Wert (zum Beispiel einen String oder eine Zahl) angeben.
Die Markierung des Rad mit einem Text ist nicht als Konstruktorargument sondern per Setter zu erreichen. Daher wird diese Eigenschaft über das Element property gesetzt. Dieses Element braucht im Attribut name die Angabe des Eigenschaftsnamens und anschließend entweder eine Referenz auf eine Bean (per Attribut ref) oder wie hier einen primitiven Wert (per value). Der Name der Eigenschaft ist übrigens nicht zwangsläufig der Name der Instanzvariablen der Klasse, sondern wird durch den Namen des zu verwendeten Setters bestimmt, indem das Präfix „set“ des Setter-Namens weggelassen wird.
Nachdem nun die Beans im XML beschrieben sind, sollen sie natürlich noch in Java ausgelesen und benutzt werden. Dazu muss ein ApplicationContext instanziiert werden, dem die Position der XML-Datei übergeben wird. Anschließend können aus diesem ApplicationContext alle benötigten Beans ausgelesen werden. Im Code sieht das wie folgt aus:

public static void main (String[]args){
  ApplicationContext context = new ClassPathXmlApplicationContext("de/jbb/springtut/xml/bsp1Einstieg.xml") ;
  PkwRad bmwRad = context.getBean("bmwRad", PkwRad.class);
  System.out.println(bmwRad);
}

ApplicationContext ist ein Interface, das von einer Vielzahl unterschiedlicher Klassen im Spring-Framework implementiert wird. Diese Implementierungen unterscheiden sich im Wesentlichen dadurch, welche Art von Konfigurationsdaten sie akzeptieren und woher sie diese Daten laden. Da ich bereits festgelegt habe, dass es in dieser Einführung nur um XML-Konfigurationen gehen soll und wir obendrein keine Webanwendung entwickeln wollen, bleiben zwei wesentliche Implementierungen übrig: ClassPathXmlApplicationContext und FileSystemXmlApplicationContext.
Diese beiden unterscheiden sich dadurch, dass sie die Konfigurationen von unterschiedlichen Orten laden, nämlich aus dem Classpath oder dem Dateisystem. Im Beispielcode liegen alle Konfigurationsdateien im Classpath.
Im obigen Beispiel wird zunächst ein ApplicationContext instanziiert und mit der XML-Datei gefüttert. Anschließend kann das beschriebene Rad einfach durch einen Aufruf der Methode getBean() geholt werden. Der Rückgabewert ist ein ganz normales Java-Objekt, das wie gewohnt benutzt werden kann.

Lesen Sie auf der nächsten Seite weiter.

5 Replies to “D) Dependency Injection mit Spring”

  1. marcel

    „Integration von Factories“ kurz daruter ist der Quellcode verschwunden da steht nur ne Leerezeile mit Zeilennummer 1

  2. Stefan Kiesel

    Hallo Marcel,

    stimmt. Da ist irgendwie, irgendwo was schief gelaufen. Ich werde das asap ausbessern. Kann aber noch ein bisschen dauern, da ich zuerst den Originalartikel von Tobias Demuth raussuchen muss.

    Grüße
    Stefan

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.