Viele Köche verderben (nicht) den Brei

OSGi mit Spring DM

Dr. Lofi Dewanto und Thorsten Kamann

Bob D. Veloper entwickelt eine Anwendung mit dem Namen Solution. Endlich ist Version 1.0.0 fertig und voller Stolz deployt Bob die Anwendung auf den Applikationsserver. Doch anstatt der durchgestylten JSF-Seiten bekommt er nur Exceptions, Fehlermeldungen und ähnliches Ungemach zu sehen.

Nun begibt sich Bob auf die Suche nach dem Verursacher. Nach einiger Zeit ist dieser auch gefunden. Er hat Bibliotheken für XML-Parser in unterschiedlichen Versionen in dem Classpath der Anwendung. Dieses Problem scheint schnell gelöst. Die alte Version des XML-Parsers wird von ihm entfernt. Die Anwendung schnell wieder deployen und… Nun, die ersten Seiten der Anwendung funktionieren wie erwartet. Aber auf einer Unterseite gibt es wieder eine Exception. Jetzt beschwert sich die Anwendung über eine fehlende Methode in einer Klasse des XML-Parsers. Jetzt steht Bob vor einem Problem. Die Anwendung verlangt nach beiden Versionen des Parsers. Sind allerdings beide im Classpath, dann funktioniert die Anwendung überhaupt nicht mehr.

How-To zum Ausprobieren der Beispiele von Spring-DM
Zutaten zum Ausprobieren von Spring-DM:

• Eclipse 3.4.x
• Beispiel von der Heft-CD laden
• Die Datei README.txt aus dem Verzeichnis [Installationsverzeichnis]/springdm-example/README lesen und sämtliche dort beschriebene Schritte durchführen

Die Anwendung

Schauen wir uns gemeinsam mit Bob die Anwendung genauer an. Sie besteht aus drei Modulen:

  • Modul Solution
  • Modul Pricing
  • Modul Udm (User Data Management)

Das Modul Solution hat eine Abhängigkeit sowohl zum Modul Pricing als auch zum Modul Udm. Zusätzlich besteht noch eine direkte Abhängigkeit zwischen dem Modul Pricing und Udm. Das ist nicht weiter schlimm. Allerdings braucht das Modul Solution das Modul Udm in Version 1.0.0, während das Modul Pricing Udm in Version 1.1.0 benötigt (Abb. 1). Das ist der Grund, warum die Anwendung beide XML-Parser braucht, aber das Modul Solution nicht mit der neueren Version des XML-Parsers kompatibel ist.

Abb. 1: Modulabhängigkeiten von Solution, Pricing und Udm

[ header = Seite 2: Spring DM ]

Spring DM (Spring Dynamic Modules für OSGi Platforms)

Bevor wir eine mögliche Lösung von Bobs Problem vorstellen, sollte Spring DM zunächst vorgestellt werden. Spring DM ist kein OSGi-Container (Laufzeitumgebung), sondern lediglich ein Framework zur Vereinfachung der Entwicklung von OSGi-Anwendungen. Warum sollte Spring DM zur Entwicklung von OSGi-Anwendungen verwendet werden? Das Hauptziel ist – analog zum allgemeinen Spring Framework – die Entwicklung von OSGi-Anwendungen ohne direkte Verwendung von OSGi-Bibliotheken. Ausschließlich POJO und POJI werden bei der Implementierung verwendet, kein Import von OSGi-Bibliotheken ist notwendig. Benötigte Informationen für Erstellung, Konfiguration, Dekoration und Zusammenstellung von OSGi-Modulen – in OSGi-Sprachjargon als „Bundle“ gekennzeichnet – werden durch Konfigurationen erledigt.

Gleichzeitig bedeutet dies, dass Spring-DM-Anwendungen theoretisch in sämtlichen OSGi-Containern wie EquinoxKnopflerfish und Felix laufen können. Zudem sollte die Erstellung von Tests vereinfacht werden. In diesem Fall werden sowohl Unit- als auch Integrationstests unterstützt. Letztere ermöglichen eine automatisierte Ausführung von Tests, die OSGi-Bundle innerhalb eines spezifischen OSGi-Containers in der Laufzeit testen sollten. Im Bereich der Services bietet Spring DM ebenfalls Vereinfachungen für die Verwaltung von OSGi-Services, sowohl aus der Sicht des Anbieters als auch des Nachfragers an. Hier werden ebenfalls Konfigurationen gegenüber direkter Einbettung von OSGi-Code bevorzugt.

OSGi-Bundles und Spring

Die Anatomie eines Spring-Bundle entspricht dem allgemeinen OSGi-Bundle. Zusätzlich müssen Spring-Konfigurationsdateien definiert werden, die im folgenden Verzeichnis liegen müssen: META-INF/spring/*.xml. Wie wird ein Spring-OSGi-Bundle in der Laufzeit innerhalb einer OSGi-Plattform erkannt und verarbeitet? Hierfür bedient sich Spring DM des Extender-Konzepts in OSGi (Spring DM Extender). Der Spring OSGi Extender erkennt zunächst ein Spring-Bundle, indem es die Spring-Konfigurationsdateien im Verzeichnis META-INF/spring inspiziert. Falls nötig wird ein entsprechender Applikationskontext für das gefundene Bundle erzeugt. Das wird solange verzögert, bis sämtliche vom Applikationskontext benötigte Services vorhanden sind. Die Erzeugung des Applikationskontexts findet standardmäßig asynchron statt, um Deadlock-Situationen im Applikationskontext zu vermeiden. Dieses gesamte Verhalten kann per Konfiguration in der ManifestdateiMETA-INF/MANIFEST.MF entsprechend angepasst werden (Spring DM Reference Guide, Kapitel 5.1).

Ein wichtiges Konzept in Spring DM ist das Anwendungsentwicklungsmodell für die Services. Sie werden von Serviceanbietern in das vorhandene Service-Registry angemeldet, sodass diese wiederum von Servicenachfragern genutzt werden können. Abbildung 2 stellt insgesamt das beschriebene Anwendungsentwicklungsmodell grafisch dar.

Abb. 2: Anwendungsentwicklungsmodell von Spring DM

Spring DM Extender
Das Extender-Konzept gibt die Möglichkeit, ein installiertes bzw. ein neu hinzugekommenes Bundle zu beobachten, zu inspizieren und gegebenenfalls bestimmte Aktionen durchzuführen. Dieses Konzept basiert auf dem Hollywood-Prinzip: „Don’t call us, we call you“. Ein plastisches Beispiel liefert ein Beitrag des OSGi-Blogs, in dem ein Vergleich des Installationsmechanismus zwischen Windows und Mac vorgestellt wird.

In Windows muss ein Installationsanwendungsentwickler genau wissen, wie eine Windows-Anwendung installiert werden kann. Das bedeutet, dass er ein explizites Wissen über Windows-Registry haben muss. Sämtliche Aktionen wie Erstellung von Registry-Einträgen müssen implementiert werden. Eine Deinstallation der Anwendung bedeutet ebenfalls, dass sämtliche Einträge explizit gelöscht werden müssen. Zusammenfassend liegt in Windows-Installationsmechanismus eine imperative Vorgehensweise vor. In der Mac-Welt ist das nicht der Fall. Der Entwickler muss ausschließlich die zu installierende Anwendung beschreiben (deskriptive Vorgehensweise). Er muss Einträge nicht explizit für eine bestimmte Registry erstellen, dafür sorgt das Betriebssystem bzw. ein Extender. Sobald eine Anwendung in einen Ordner kopiert wird, werden die entsprechenden Anwendungsbeschreibungen gelesen und interpretiert. Das bedeutet ebenfalls, dass eine Verschiebung des Anwendungsordners kein Problem bereiten wird. Der Extender bemerkt, dass die Anwendung verschoben wird, und er muss nur die entsprechenden Beschreibungen lesen und bestimmte Aktionen durchführen. Die Anwendung bzw. der Anwendungs-Installer selbst muss nichts tun. In Windows ist eine solche Vorgehensweise einfach nicht möglich, eine Deinstallation und Neuinstallation der Anwendung ist hierfür notwendig, da sonst die Registry-Einträge nicht mehr gültig sind.

[ header = Seite 3: OSGi-Serviceanbieter und -nachfrager ]

OSGi-Serviceanbieter und -nachfrager

Das Konzept von Services in OSGi ähnelt dem von Web Services. Serviceanbieter, -nachfrager und -Registry gehören zu den Hauptkomponenten dieses Konzepts. Durch den derzeit eingeschränkten Einsatz innerhalb einer JVM, wird dieses Konzept als „Web Services im Kleinen“ bezeichnet. In Spring DM exportiert der Anbieter eines Service in ein Service-Registry in Form einer Spring-XML-Beschreibung. Eine Beschreibung aus dem Modul Udm unseres Beispiels sieht wie folgt aus:

<bean name="udmService"
class="osgi.udm.UdmServiceImpl"
init-method="start"
destroy-method="stop" />
<osgi:service id="udmServiceOsgi"
ref="udmService"
interface="osgi.udm.UdmService" />

Zunächst wird die Klasse UdmServiceImpl mit einem Interface UdmService in der Datei udmservice.xml als Spring Bean namens „udmService“ definiert (Listing 1). Die Implementierungsmethodestart und stop werden bei der Aktivierung und Deaktivierung der Klasse aufgerufen. Anschließend wird in der Datei udmservice-osgi.xml die bereits definierte Spring Bean udmService als OSGi-Service exportiert (Listing 2). Als Nachfrager eines Service sieht die Konfiguration ähnlich aus. Eine Beschreibung des Moduls Pricing, das das Modul Udm verwendet, sieht wie in Listing 3 dargestellt aus.

<osgi:reference id="udmService" interface="osgi.udm.UdmService" />
<bean name="pricingService"
class="osgi.pricing.PricingServiceImpl"
init-method="start"
destroy-method="stop"
p:udmService-ref="udmService">
</bean>

Zunächst wird eine Referenz zu dem OSGi-Service udmService in der Datei pricingservice-osgi.xml definiert (Listing 3). Dieser Service wird anschließend in der Spring Bean pricingService in der Dateipricingservice.xml referenziert und in die Klasse PricingServiceImpl mithilfe der Setter-Methode setUdmService injiziert. Ebenfalls werden die Implementierungsmethoden start und stop bei Aktivierung und Deaktivierung der Klasse PricingServiceImpl angewandt. In den Implementierungsklassen (PricingServiceImpl und UdmServiceImpl) werden keine einzigen OSGi-Bibliotheken referenziert. Sämtlicher Infrastrukturcode wird von Spring DM verwaltet. In den Spring-Konfigurationsdateien können einige Einstellungen gemacht werden, um die vorgestellten Beziehungen zwischen Anbieter, Registry und Nachfrager steuern zu können. Zusammenfassend können bestimmte Elemente konfiguriert werden (Tabelle 1).

Tabelle 1

[ header = Seite 4: Lösung des Versionierungsproblems mit Spring DM ]

Lösung des Versionierungsproblems mit Spring DM

Genug von Spring-DM-Theorie, nun kommen wir zurück zu Bobs Problem. Im Prinzip basiert die Lösung auf dem OSGi-Konzept. Durch Spring DM wird jedoch der geschriebene Code deutlich sauberer, da Infrastrukturcode komplett aus dem tatsächlichen Code ausgelagert werden kann. Eine wichtige Erkenntnis ist, dass Spring DM bei der Initialisierung der Abhängigkeiten sämtliche Methoden nur einmal berührt. Das verursacht je nach Situation ein anderes Verhalten, als wenn reines OSGi-Anwendungsentwicklungsmodell verwendet wird. Transitive Abhängigkeiten, die im Folgenden detaillierter dargestellt werden, verursachen innerhalb des Spring DM dann stets eine ClassLoader-Exception. Bei diesem Problem geht es um die zwei Module mit unterschiedlichen Versionen (Udm 1.0.0 und Udm 1.1.0). Als Lösung gilt, dass Methoden mit Vorsicht nach außen exportiert bzw. innerhalb der Interfaces definiert werden sollten. In unserem Beispiel ist es nicht notwendig, die Methoden aus Listing 5 in der Klasse PricingService zu exportieren.

public void setUdmService(UdmService udmService);

public UdmService getUdmService();

Das Modul Pricing 1.0.0 wird vom Modul Solution 1.0.0 verwendet. Gleichzeitig verwendet das Modul Solution 1.0.0 das Modul Udm 1.0.0. Im Fall, dass das Modul Pricing die oben genannten Methoden nach außen exportiert, bekommt das Modul Solution ebenfalls eine direkte Abhängigkeit zum Modul Udm 1.1.0, da das Modul Pricing 1.0.0 dieses benutzt. So ein Fall macht es unmöglich für das Modul Solution 1.0.0 die Entscheidung zu treffen, wann es das Modul Udm 1.0.0 bzw. Udm 1.1.0 verwenden sollte. Dieses Problem ist jedoch eine allgemeine Art und nicht OSGi-spezifisch. Durch die Löschung der oben genannten Methoden wird die direkte Abhängigkeit zwischen dem Modul Solution 1.0.0 und Udm 1.1.0 entfernt. In diesem Fall ist sie sowieso nicht notwendig. Spring DM kann die Abhängigkeiten trotzdem injizieren, da die genannten Methoden in der Implementierungsklasse PricingServiceImpl vorhanden sind und Spring nicht auf Interface-Methoden angewiesen ist. Das können Sie detailliert in dem mitgelieferten Beispiel ansehen (Projekte osgi.udm, osgi.pricing und osgi.solution). Weiterhin ist es wichtig, dass es eine Trennung zwischen Interface und Implementierung gibt. Beides sollte in verschiedenen Bundles untergebracht werden, um einen möglichen Austausch von Implementierungen zu vereinfachen. Für eine Referenzimplementierung kann dieses Konzept jedoch aufgeweicht werden, man darf nur die Schnittstellen nicht zu fest mit der Implementierung verdrahten.

Unit- und Integrationstest

Um Unit-Tests für die Serviceimplementierungen umzusetzen, gibt es keine Besonderheiten, da die Serviceimplementierungen stets POJO sind. Im Fall, dass die OSGi-Infrastruktur ebenfalls getestet werden sollte, stellt Spring DM bereits einige Mock-Klassen zur Verfügung, sodass die OSGi-Infrastrukturklassen nicht selbst gemockt werden müssen. Beispiele von solchen Mock-Klassen (MockServiceReference, MockBundleContext, usw.) liegen unter dem Paket org.springframework.osgi.mock bereit. Interessant ist ebenfalls die Möglichkeit, Integrationstests durchzuführen. In diesem Kontext bedeutet „Integrationstest“ die Ausführung von OSGi-Bundles in einem Container und die Ausführung von Testmethoden. Hierfür bietet Spring DM einige Hilfsklassen an, die auf Basis der Klasse AbstractOsgiTests implementiert worden sind. Beide Testarten sind in den Projekten osgi.pricing.integrationstest und org.springframework.osgi.simple.test veranschaulicht. Letzteres stammt hierher.

Spring DM und Webanwendungen

Es liegt nahe, dass Bob ebenfalls Webanwendungen auf Basis von OSGi schreiben möchte, da die Versionierung der einzelnen Bundles funktioniert. Bevor Spring DM existierte, hätte er sich an die Webanwendungen auf Basis von Equinox halten müssen. Das ist ziemlich kompliziert und oft mit viel Trial-And-Error verbunden. Mithilfe von Spring DM hat er eine einfache Möglichkeit, eine Webanwendung auf Basis von OSGi-Bundle zu implementieren. Es existiert ein Web-Extender (Spring DM Extender), der dafür sorgt, dass eine Bundle-fizierte WAR-Datei richtig als eine Webanwendung verarbeitet und in einen Webcontainer installiert werden kann, sobald diese als Bundle gestartet wird. Zudem ermöglicht Spring DM „native“ Unterstützung verschiedener Webcontainer und gestattet den Einsatz von unterschiedlichen OSGi-Plattformen. Wie geht Bob bei der Entwicklung einer Webanwendung mit Spring DM vor? Hier sind die Schritte:

  • Zunächst wird ein Bundle mit einer Servlet-Klasse erstellt.
  • Die Datei META-INF/MANIFEST.MF muss anschließend erstellt und notwendige Bundles müssen importiert werden (Listing 6). In Eclipse kann der Plug-in-Editor für diesen Zweck verwendet werden.
  • Die restlichen Schritte sind analog zu einer normalen Webanwendungsentwicklung. Je nachdem unterschiedlich durch die Verwendung von bestimmten Webframeworks.

Als Beispiel können folgende Projekte org.springframework.osgi.springdm.simpleweb und simple-web-app aus den mitgelieferten Beispielen angeschaut werden.

Web-ContextPath: simple-web-app
Bundle-ManifestVersion: 2
Bundle-Name: Simple OSGi War
Bundle-SymbolicName:
org.springframework.osgi.samples.simplewebapp
Bundle-ClassPath: WEB-INF/classes
Import-Package: javax.servlet;version="2.4.0",
javax.servlet.http;version="2.4.0",
javax.servlet.resources;version="2.0.0",
javax.servlet.jsp;version="2.0.0", ...

[ header = Seite 5: Fazit ]

Fazit

Spring DM besitzt die typischen Vorteile einer Spring-Anwendung. Es gibt keine direkte Verwendung von OSGi-Bibliotheken und es sind keine expliziten Activator-Klassen notwendig. POJO und POJI bleiben, wie sie sind. Im Testbereich bietet Spring DM nicht nur die gewohnte Vereinfachung der Unit-Tests, sondern umfangreiche Unterstützung bei Integrationstests an. Somit können Bundles in der tatsächlichen bzw. in der gemockten Umgebung automatisiert getestet werden. In der Entwicklung der Webanwendungen unterstützt Spring DM die Unabhängigkeit der OSGi-Plattform und ermöglicht eine durchdachte Entwicklung in Webanwendungen. Es ist einfach nicht mit der Notlösung von Equinox zu vergleichen (Webanwendung mit Equinox).

Insgesamt kann die Verwendung von Spring DM für die OSGi-Entwicklungen empfohlen werden. Mit dem reinen OSGi-Anwendungsentwicklungsmodell bekommt man sehr schnell Probleme, wenn es darum geht, Schnittstellen und Implementierungen ohne Abhängigkeiten zu OSGi zu entwickeln. Konzeptionell ist das vergleichbar mit Spring im JEE-Umfeld. Auch dort kann die eigentliche Anwendung entwickelt werden ohne explizite Abhängigkeiten zu Runtime-Bibliotheken aufzubauen. Natürlich gibt es auch noch Probleme und Schwierigkeiten. Sie sind meistens OSGi-spezifisch und liegen selten an Spring DM. Die Frage, ob die OSGi-Plattform für die Umsetzung einer Webanwendung allgemein für jeden Entwickler bereits einsetzbar ist, muss leider anders beantwortet werden. Es sind noch einige Themen offen. Daher hat Bob D. Veloper seine Webanwendung doch erstmal ohne OSGi implementiert und das Problem mit den Versionen so gelöst, dass nur eine Version des XML-Parsers verwendet wird.

Thorsten Kamann ist als unabhängiger Softwarearchitekt und Coach bei itemis tätig. Seine Schwerpunkte sind webbasierte Technologien, MDSD, leichtgewichtige und flexible Architekturen und Open Source. Er ist Projektlead bei der Fornax Platform, einer Plattform für die Entwicklung von MDSD-related Tools und aktiver Groovy Commiter. Darüber hinaus schreibt er Bücher, veröffentlicht regelmäßig Artikel in Fachmagazinen und hält Vorträge auf Fachkonferenzen zu den oben genannten Themen. Dr. Lofi Dewanto (http://lofidewanto.blogspot.com) arbeitet als Softwareentwickler bei der Deutschen Post Com GmbH. Er engagiert sich insbesondere für „javanische“ Open-Source-Software sowie MDA.
Geschrieben von
Dr. Lofi Dewanto und Thorsten Kamann
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.