Komponentenbasierte Vorgehensweise beim Erstellen eigener Eclipse-Plug-ins

Das OSGi Framework

Holger Funke

Beim Erstellen eigener Eclipse-Plug-ins stößt man ab einer bestimmten Größe schnell auf Probleme, die sich in erster Linie durch Modularisierung lösen lassen. Dieser Artikel soll dem Leser zum einen die Grundlagen der OSGi-Plattform vermitteln und zum anderen eine Vorgehensweise an die Hand geben, mit der er eigene Plug-ins modularisieren kann.

Die OSGi Alliance (früher Open-Services-Gateway-Initiative) spezifiziert eine dynamische Softwareplattform, die es ermöglicht, Applikationen und dazugehörige Dienste per Komponentenmodell zu modularisieren und zu verwalten. Seit Version 3.0 ist OSGi das Eclipse zugrunde liegende Modell, das unter dem Namen Equinox als eigenständiges Eclipse-Projekt weiterentwickelt wird. Im Jahr 2003 sollte Eclipse auf eine Plug-in-Struktur umgestellt werden. Bis zu diesem Zeitpunkt gab es innerhalb von Eclipse keine standardisierten Komponenten und das Laden oder das Entfernen von Komponenten erforderte einen Neustart. Darüber hinaus war die statische Abhängigkeit der Module untereinander ein Problem. Um diese Mängel zu beheben, hat sich die Eclipse Foundation dafür entschieden, die OSGi-Plattform zu verwenden. Equinox implementiert die OSGi-Kernspezifikation. Zusätzlich zu den von der OSGi spezifizierten Anforderungen setzt Equinox verschiedene zusätzliche Features, z.B. die Extension Registry, um. Neben Eclipse setzen auch noch andere Applikationen auf Equinox auf (z.B. IBM Workplace).

OSGi Alliance

Die OSGi Alliance wurde 1999 gegründet. Ihr gehören Unternehmen aus den unterschiedlichsten Branchen an (z.B. Sun Microsystems, Nokia, Siemens, Red Hat, Eclipse Foundation, Deutsche Telekom, Motorola). Geleitet wird die OSGi Alliance von einem Direktorium, das sich aus den Vorständen des jeweiligen Arbeitsgebiets zusammensetzt. Technische Fragen – so auch die Weiterentwicklung der Spezifikationen – werden in diversen Arbeitsgruppen behandelt. Die aktuellen Spezifikationen stehen auf der OSGi-Website zur Verfügung. Aktuell ist das Release 4 in der Version 4.1. Die OSGi spezifiziert lediglich die API und die Testfälle für Implementierungen und stellt in diesem Zusammenhang auch eine Referenzimplementierung zur Verfügung, die jedoch nicht für den Produktiveinsatz, sondern lediglich als Vorlage gedacht ist. Neben Equinox gibt es noch einige andere freie und auch kommerzielle Umsetzungen der Spezifikation, beispielsweise Knopflerfish. Ein interessantes und auch hinter die Kulissen schauendes Blog, von einem der OSGi-Verantwortlichen geschrieben, findet sich unter www.osgi.org/blog/.

Die OSGi-Spezifikationen

Das OSGi Framework besteht aus einer Reihe von Schichten, die aufeinander aufbauen. Sie erweitern das durch Java und die entsprechende JVM vorgegebene Modell durch Konstrukte, die nicht nur die oben genannten Einschränkungen lösen, sondern die Funktionalität nochmals deutlich erweitern. Abbildung 1 zeigt die Bestandteile der OSGi-Architektur.

Abb. 1: Die Bestandteile der OSGi-Architektur

[ header = Seite 2: relevante Schichten ]

Folgende Schichten sind dabei relevant:

  • Execution Environment: Die Ausführungsumgebung ist so spezifiziert, dass sie auf unterschiedlichen Java-Plattformen lauffähig ist. Statt eine konkrete Java-Version zu referenzieren, wird mit einer Execution Environment ein Repräsentant einer Laufzeitumgebung definiert, die festlegt, welche Klassen, Interfaces und Methoden vorhanden sein müssen.
  • Module Layer: Die unterste logische Schicht des OSGi Frameworks definiert ein Bundle (Plug-in) als kleinste Einheit. Ein Bundle kann eigenständig im Framework installiert und genutzt werden und besteht aus Java-Klassen und den benötigten Ressourcen. Die enthaltene Datei MANIFEST.MF wird um eine Anzahl von Metadaten angereichert, die explizit die Abhängigkeiten und angebotenen Pakete auflisten. Exemplarisch ist der Inhalt eines Bundle-Manifests in Listing 1 dargestellt. Eine spezielle Eigenschaft des OSGi Frameworks ist die Möglichkeit, Abhängigkeiten zwischen verschiedenen Bundles explizit verwalten zu können. Die Klassen eines Bundles sind zunächst nicht für andere Bundles sichtbar. Um diese in anderen Bundles nutzen zu können, muss das implementierende Bundle diese Klassen zuerst über die Manifest-Datei exportieren.
  • Life Cycle Layer: Während in der Modulschicht in erster Linie statische Aspekte eines Moduls thematisiert werden, stehen in der Lebenszyklusschicht die dynamischen Aspekte im Vordergrund. In Abbildung 2 werden die Zustände eines Bundles dargestellt. Die Bundle-Zustände werden dabei von einem Managementagenten geändert, der ein Interface zum OSGi Framework implementiert, über die das Framework von außen gesteuert werden kann.
  • Service Registry: Die Service Registry dient dazu, Bundles im OSGi Framework zu registrieren, sodass diese Bundles genutzt werden können. Ein Service ist ein simples Java-Objekt, das in einer zentralen Registratur unter seinem Interfacenamen angemeldet wird. Dort kann es von anderen Bundles abgefragt und genutzt werden. Die Registratur existiert nur während der Laufzeit, ist also nicht persistent. Selbst während der Laufzeit muss man damit rechnen, dass Services an- und abgemeldet werden, bzw. nicht mehr zur Verfügung stehen. Um das Arbeiten mit diesen dynamischen Services zu erleichtern, sind in der OSGi-Spezifikation drei Mechanismen beschrieben, um auf entsprechende Änderungen zu reagieren: der Service Listener, der Service Tracker und der Declarative Service.
  • Standardservices: Das OSGi Framework definiert eine Reihe von Standardservices, die Lösungen für unterschiedliche Probleme implementieren und in anderen Bundles genutzt werden können. Zu diesen Standardservices gehört z.B. der http-Service, der einen einfachen Webserver spezifiziert, an dem Bundles Ressourcen anmelden können, die über das http-Protokoll ansprechbar sind.
  • Security Layer: Mit den Mechanismen dieser Schicht lassen sich die Ausführungsrechte einzelner Bundles gezielt einschränken. Diese Schicht basiert auf dem Java-Sicherheitsmodell, das mit dem JDK 1.2 eingeführt wurde, erweitert diese jedoch, sodass den OSGi-spezifischen Anforderungen Genüge getan wird.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: MyFirstBundle
Bundle-SymbolicName: myFirstBundle
Bundle-Version: 1.2.3

Abb. 2: Die unterschiedlichen Zustände eines Bundles

[ header = Seite 3: Abhängigkeiten zwischen Bundles ]

Abhängigkeiten zwischen Bundles

Ein einzelnes Plug-in zu entwickeln, ist relativ einfach; die Komplexität kommt dann ins Spiel, wenn ein Plug-in zu groß wird und es in verschiedene Komponenten aufgeteilt werden muss. In diesem Fall muss ein Bundle auf die Klassen und Ressourcen eines anderen Bundles zugreifen können. Im Rahmen des OSGi Frameworks wird dies durch das Im- und Exportieren von Packages erreicht, somit spricht man hier auch von Package-Abhängigkeiten. Als Basis für das explizite Im- und Exportieren dient der Java-Class-Loading-Mechanismus, der es ermöglicht, die einzelnen Bundles voneinander zu isolieren, indem er die sichere Zuführung von Klasseninformationen zur JVM steuert.

Damit die Klassen eines Bundles auch für andere Bundles nutzbar bzw. überhaupt sichtbar sind, müssen diese explizit exportiert werden. Das Exportieren passiert im OSGi Framework auf Package-Ebene. Dabei wird die Datei MANIFEST.MF um folgenden Eintrag erweitert: Export-Package: org.bla.myPackage. Mit dieser Anweisung können andere Plug-ins das Package myPackageentsprechend importieren und nutzen. Praktischerweise kann man Bundles und die von ihnen exportierten Packages versionieren. Auf diese Weise lässt sich die Abhängigkeit noch weiter verfeinern, indem man nämlich beim Importieren eines Packages bereits angeben kann, welche Versionsnummer gefordert ist. Eine Versionsnummer setzt sich aus maximal vier, durch einen Punkt getrennte Stellen zusammen, wobei die ersten drei numerisch (major, minor, micro) und die letzte (qualifier) auch ein String sein darf. Die Manifest-Datei dient wiederum dazu, das Bundle mit einer entsprechenden Versionsnummer zu versehen. In Listing 1 hat das Bundle die Versionsnummer 1.2.3. Da ein Bundle sowohl über die Versionsnummer als auch über den symbolischen Namen im System identifiziert wird, kann man in einem System durchaus verschiedene Versionen eines Bundles nutzen.

Bei der Spezifikation der Bundle-Abhängigkeiten kann man einen Versionsbereich festlegen, aus dem ein Package kommen muss. Ein Versionsbereich setzt sich dabei aus einem Minimal- und einem Maximalwert zusammen, wobei man runde und eckige Klammern nutzen kann, um ein „größer“ bzw. ein „größer-oder-gleich“ und umgekehrt auszudrücken. Beispiele für mögliche Wertebereiche zeigt Tabelle 1.

Tabelle 1: Beispiele für Abhängigkeiten mit Versionsbereichen

Der Eintrag Import-Package: myPackage; version=“[1.1.0,1.3.0)“ in der Manifest-Datei bewirkt also, dass das Package myPackage nur in der Version größer oder gleich 1.1.0 und kleiner als 1.3.0 ist.

Nutzung von dynamischen Services

Die OSGi Service Registry dient dazu, dass Bundles an dieser zentralen Stelle angemeldet werden können, um ihre Dienste anzubieten. Andere Bundles dagegen können diese Registry nutzen, um nach erforderlichen Diensten zu suchen. Bundles können zu diesem Zweck Properties und Filter verwenden, mit denen Services detailliert beschrieben oder auch gesucht werden können.

Beim Arbeiten mit Services sind folgende Schritte notwendig:

  • Service registrieren: Bundle muss in Service Registry angemeldet warden.
  • Service abfragen: Registrierte Bundles können über Properties gesucht werden.
  • Service verwenden: Direkter Zugriff auf die vom Bundle bereitgestellten Funktionen.
  • Service freigeben: Nach Nutzung muss das Bundle den Service wieder freigeben.
  • Service deregistrieren: Bundle soll seinen Service dem System nicht mehr zur Verfügung stellen.

Somit ist es also möglich, dass zu einem beliebigen Zeitpunkt ein Service registriert bzw. deregistriert wird. Das OSGi Framework muss deshalb Mechanismen bereitstellen, die es ermöglichen, während der Laufzeit auf Änderungen der Service Registry oder der angemeldeten Services zu reagieren. Im Kern basieren diese Mechanismen auf dem Konzept des Service Listeners. Dieses Konzept wird sowohl im Service Tracker (unterstützt programmatischen Umgang mit dynamischen Services) als auch im Declarative Service (Serviceabhängigkeiten werden in deklarativer Form beschrieben) umgesetzt. Da der Einsatz von Service Listenern recht fehleranfällig werden kann (Nebenläufigkeitsprobleme), wird von diesen abgeraten. Stattdessen sollten Mechanismen wie der Service Tracker verwendet werden, der eine deutlich einfachere Handhabung gewährleistet und selbst wiederum auf den Service Listenern basiert.

Über unterschiedliche Methoden können nach dem Erzeugen eines Service Trackers die beobachteten Services und Servicereferenzen abgefragt werden. Der Service Tracker bietet darüber hinaus Mechanismen an, um auf Änderungen (Hinzufügen, Ändern, Entfernen) von Services unmittelbar zu reagieren. Eine andere Alternative ist der Einsatz des so genannten Whiteboard Patterns. Dieses ersetzt im OSGi Framework das traditionelle Listener Pattern. Es wird eingesetzt, wenn man innerhalb eines Bundles einen Event Listener an einen Service anmelden möchte, um bestimmte Events dieses Service zu empfangen. In diesem Pattern werden die Listener nicht über add()- und remove()-Methoden an der Event Source angemeldet, sondern als OSGi Services in der Service Registry angemeldet. An dieser Stelle können sie beim Feuern eines Events vom jeweiligen Service abgefragt werden.

[ header = Seite 4: Zusammenfassung und Ausblick ]

Zusammenfassung und Ausblick

Das OSGi Framework ist hervorragend dazu geeignet, eigene Plug-ins modular aufzubauen. Die Werkzeuge, die zur Verfügung gestellt werden, sind ausgereift und stabil, wie man bereits bei der Arbeit mit Eclipse feststellen kann. Für eine tiefergehende Beschäftigung mit dem Thema OSGi und Equinox sei an dieser Stelle auf das hervorragende Buch von Wütherich, Hartmann, Kolb und Lübken verwiesen. Aktuell ist die Version 4.2 der OSGi-Spezifikationen in Arbeit. In einem ersten Entwurf wurden dabei bereits die Marschrichtungen vorgegeben. Hier wird es vor allem Erweiterungen im Enterprise-Bereich geben. So soll der Transaktionsbereich – bisher in OSGi noch nicht spezifiziert – mit der Version 4.2 standardisiert werden. Man kann also gespannt sein, was die Zukunft im Bereich OSGi noch bringen wird.

Holger Funke arbeitet als Senior Consultant bei der HJP Consulting GmbH in Paderborn. Er leitet die Entwicklung des Eclipse-Plug-ins GlobalTester (www.globaltester.org), das zum Testen von Chipkarten dient. GlobalTester wird weltweit eingesetzt, um die Konformität von elektronischen Reisepässen zu gewährleisten. Daneben veröffentlicht er Artikel in Fachzeitungen und spricht auf internationalen Konferenzen.
Geschrieben von
Holger Funke
Kommentare

Schreibe einen Kommentar

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