Plug-in basierte Entwicklung mit OSGi
Einführung
Java stellt von Haus aus nur begrenzte Möglichkeiten bereit, Anwendungen in Module aufzuteilen und diese untereinander abzugrenzen. Packages erlauben zwar die Gruppierung von Klassen - zur Laufzeit gelten jedoch keine Restriktionen für die Sichtbarkeit von Packages untereinander. Auch für die Sichtbarkeit der Inhalte eines Packages nach außen gelten kaum Einschränkungen. Die einzige Option, Klassen als package-protected zu markieren, ist für die Strukturierung und Abgrenzung von Softwaremodulen meist unzureichend.
Dieses Problem wird von OSGi adressiert: Die OSGi Service Plattform ist eine Spezifikation für ein Framework, welches die Entwicklung von modularen Anwendungen auf der Java-Plattform ermöglicht. Anwendungen für die OSGi-Plattform bestehen aus voneinander isolierten Modulen, sogenannten Bundles. Bundles haben zur Laufzeit einen Lebenszyklus und können dynamisch geladen, gestartet und gestoppt werden, es ist also z.B. ein Update von Teilen der Anwendung ohne einen Neustart der gesamten Anwendung möglich. Ferner können Bundles über Services zusammenarbeiten. OSGi selbst stellt einige Standard-Services z.B. für Logging, Http, Konfiguration, etc. bereit. Zudem erweitert OSGi die in Java bereits vorhandenen Sicherheitsmechanismen so, dass die Berechtigungen von Bundles zur Laufzeit beschränkt werden können.
Die OSGi-Spezifikation wird von einem Industriekonsortium, der OSGi Alliance, entwickelt und veröffentlicht. Es gibt verschiedene Implementierungen dieser Spezifikation. Die Referenzimplementierung stellt Eclipse Equinox, weitere Open-Source-Implementierungen sind Knopflerfish und Apache Felix.
Eclipse-Anwendungen bestehen aus OSGi-Bundles und werden auf der OSGi-Implementierung Eclipse Equinox ausgeführt.
OSGi Bundles
Ein Modul einer OSGi-basierten Anwendung bezeichnet man als Bundle. In der Eclipse-Welt wird häufig bedeutungsgleich der Begriff “Plug-in” verwendet. Ein Bundle wird als JAR-Archiv verteilt und besteht aus einer Beschreibung des Bundles (das Manifest), Java-Klassen und Ressourcen:
`-- com.example.firstbundle_1.0.0.jar
|-- META-INF
| `-- MANIFEST.MF
|-- de
| `-- rcpbuch
| |-- somebundle
| | |-- SomeClass.class
| | |-- SomeOtherClass.class
OSGi-Implementierungen stellen eine Laufzeitumgebung, einen Container zur Ausführung von Bundles, bereit:
Bundles haben innerhalb eines solchen OSGi Containers einen Lebenszyklus. Sie können geladen bzw. installiert werden und befinden sich zunächst im Zustand resolved. Wird ein Bundle gestartet, bekommt es den Status starting und ist nach erfolgreichem Start active. Bundles können auch wieder beendet (stopping, dann resolved) und deinstalliert werden. Das bedeutet, Bundles können dynamisch nachgeladen oder entfernt werden. Damit ist auch die Aktualisierung eines Bundles zur Laufzeit möglich, ohne die gesamte Anwendung neu zu starten.
Tutorial: Ein OSGi Bundle erstellen
Für den Moment lassen wir die Entwicklung von grafischen Anwendungen außen vor und betrachten ausschließlich den OSGi-Mechanismus. Um OSGi näher kennenzulernen, bietet es sich an, zunächst ein neues, leeres Bundle zu erstellen.
Um ein neues Bundle zu erstellen, wird Eclipse for RCP/Plug-in Developers benötigt. Diese Eclipse-Variante enthält die für die Entwicklung von Bundles bzw. Plug-ins notwendigen Werkzeuge.
Wählen Sie File > New > Project > Plug-in Development > Plug-in Project. Der Assistent führt nun durch die Erstellung des neuen Bundles:
- Project name: Der Name des Bundle-Projektes. Per Konvention sollte das Projekt genau so benannt werden wie das Java-Package, in dem später die Klassen des Bundles abgelegt werden. Geben Sie hier “com.example.firstbundle” an.
- Target Platform: Sie haben die Wahl zwischen den verschiedenen OSGi-Implementierungen oder “Eclipse”. Eclipse erweitert OSGi um einige zusätzliche Features - im Moment benötigen wir noch keine Eclipse-Features, wählen Sie hier daher das
OSGi Framework Equinoxaus.
Der Assistent fragt nun nach einigen allgemeinen Eigenschaften des zu erstellenden Bundles:
- Plugin-ID: Ein eindeutiger Bezeichner für das Bundle. Dieser sollte identisch mit dem Projektnamen sein.
- Plugin-Version: Die initiale Versionsnummer des Bundles.
- Plugin-Name: Beschreibender Name.
- Plugin-Provider: Herstellerangabe.
- Execution Environment: Die für die Ausführung des Bundles benötigte Mindestversion der Java-Laufzeitumgebung.
- Activator: OSGi Bundles können eine
Activator-Klasse angeben, die benachrichtigt wird, wenn das Bundle startet oder stoppt. Aktivieren Sie die Erstellung einer solchen Klasse.
Der Assistent bietet einige vorgefertigte Vorlagen für Bundles an. Wählen Sie diese Option zunächst ab, um ein komplett leeres Bundle zu erzeugen:
Eclipse erstellt nun ein neues Bundle-Projekt mit folgendem Inhalt:
Bundle-Manifest
Der Manifest-Datei META-INF/MANIFEST.MF kommt eine besondere Rolle zu. Sie enthält beschreibende Informationen zu dem Bundle:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: First OSGi bundle Bundle-SymbolicName: com.example.firstbundle Bundle-Version: 1.0.0 Bundle-Activator: com.example.firstbundle.Activator Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Import-Package: org.osgi.framework;version="1.3.0"
Öffnen Sie die Manifest-Datei, werden die Inhalte des Manifests in einem formularbasiertem Editor dargestellt. Über den Reiter “MANIFEST.MF” können Sie den Quelltext des Manifests anzeigen:
Für das Tutorial-Bundle wurden gemäß der Angaben im Projektassistenten folgende Eigenschaften im Manifest generiert:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2
Hier werden zunächst die Versionen der Manifest-Spezifikation angegeben, auf die wir uns mit den folgenden Angaben beziehen. Zum einen für das Format der Manifest-Datei (gemäß der Java-Spezifikation für JARs), zum anderen für die Eigenschaften eines OSGi Bundles (gemäß der OSGi Spezifikation).
Bundle-Name: Example OSGi bundle Bundle-SymbolicName: com.example.firstbundle
Bundle-Name ist eine kurze, aussagekräftige Beschreibung des Bundles. Bundle-SymbolicName ist ein eindeutiger Bezeichner für das Bundle, mit dem das Bundle künftig referenziert wird.
Bundle-Version: 1.0.0
Bundle-Version spezifiziert die Version des Bundles. Diese wird von uns immer dann erhöht werden, wenn eine neue Version des Bundles veröffentlicht wird.
Bundle-Activator: com.example.firstbundle.Activator
Bundle-Activator ist die optionale Angabe einer Activator-Klasse, die benachrichtigt wird, sobald das Bundle startet oder stoppt.
Bundle-ActivationPolicy: lazy
Bundle-ActivationPolicy gibt an, ob das Bundle automatisch gestartet werden soll. lazy bedeutet, dass das Bundle automatisch gestartet wird, sobald eine Klasse daraus geladen wird.
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-RequiredExecutionEnvironment spezifiziert die minimale Laufzeitumgebung für das Bundle. Dieses Bundle würde beispielsweise unter Java 1.4 oder 1.5 den Start verweigern.
Import-Package: org.osgi.framework;version="1.3.0"
Bundles können Abhängigkeiten zu anderen Bundles haben. Der Assistent hat automatisch eine Abhängigkeit zum OSGi-Framework erzeugt. Denn auch OSGi besteht selbst aus Bundles. Zu den Abhängigkeiten von Bundles in Kürze mehr.
Zudem hat der Assistent die Activator-Klasse generiert. Ergänzen Sie sie um jeweils ein System.out.println-Statement, um den Lebenszyklus des Bundles auf der Konsole verfolgen zu können:
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
System.out.println("Hello world!");
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye world!");
}
}
Tutorial: Ein OSGi Bundle starten
Um OSGi mit dem soeben erstellten Bundle zu starten, erzeugen Sie mit Run > Run Configurations > OSGi-Framework eine neue Start-Konfiguration:
Sie können hier die Bundles auswählen, die in dem OSGi-Container gestartet werden sollen. Standardmäßig sind alle verfügbaren Bundles inkl. aller Bundles der Eclipse IDE ausgewählt. Klicken Sie daher Deselect All und selektieren nur das soeben erzeugte Bundle.
Da dieses Bundle eine Abhängigkeit auf das Bundle org.eclipse.osgi hat, müssen Sie auch dieses starten. Sie können es mit Add Required Bundles automatisch hinzufügen. Danach sollten Sie mit Validate Bundles nachprüfen, dass keine weiteren Probleme bestehen und den OSGi-Container mit Run starten. Wenn alles funktioniert, erhalten Sie folgende Ausgabe auf der Konsole:
osgi> Hello world!
Das Bundle wurde also korrekt gestartet und die Activator.start()-Methode aufgerufen. Der Prompt “osgi>” stammt von der OSGi-Konsole. Equinox enthält eine einfache Textkonsole, die immer dann aktiv wird, wenn OSGi mit der Option -console gestartet wird (Eclipse legt Startkonfigurationen für OSGi standardmäßig mit dieser Option an). Eine Übersicht über alle Konsolenkommandos erhalten Sie durch Eingabe von help. Mit ss sehen Sie einen kompakte Übersicht über alle geladenen Bundles:
id State Bundle 0 ACTIVE org.eclipse.osgi_3.5.0.v20090520 1 ACTIVE com.example.firstbundle_1.0.0
Mit start [Bundle-SymbolicName] und stop [Bundle-SymbolicName] können Sie zur Laufzeit Bundles starten oder beenden:
osgi> stop com.example.firstbundle Goodbye world! osgi> start com.example.firstbundle Hello world!
Wenn Sie nun das Bundle verändern (z.B. indem Sie den Text in der start-Methode der Activator-Klasse ändern), können Sie die aktualisierte Version mit update [Bundle-SymbolicName] laden:
osgi> update com.example.firstbundle Goodbye world! Hello world after update!
An diesem einfachen Beispiel wird bereits gut erkennbar, dass es sich bei OSGi um eine sehr dynamische Laufzeitumgebung für Java-Komponenten handelt.
Bundle-Abhängigkeiten
OSGi verwaltet die Abhängigkeiten zwischen Bundles. Für ein Bundle wird im Manifest spezifiziert, welche anderen Bundles es benötigt, um zu funktionieren. Dazu verwendet man die Option Require-Bundle:
Require-Bundle: org.eclipse.osgi
Diese Angabe bedeutet: Dieses Bundle benötigt das Bundle org.eclipse.osgi. Steht dieses nicht zur Verfügung, kann auch com.example.firstbundle nicht starten. Hierbei kann optional eine Mindestversion angegeben werden:
Require-Bundle: org.eclipse.osgi;bundle-version="3.5.0"
Ein Bundle kann außerdem über das Manifest Java-Packages exportieren. Das Manifest von org.eclipse.osgi exportiert beispielsweise folgende Packages:
Export-Package: org.osgi.framework, org.eclipse.osgi.util, org.eclipse.osgi.event, ....
Ohne Abhängigkeiten sind Bundles komplett isoliert. Das heißt, sie sehen ihre Packages und Klassen untereinander nicht. Wird eine Abhängigkeit von Bundle A nach Bundle B eingeführt, werden die von B exportierten Packages für A sichtbar:
Neben Require-Bundle gibt es mit Import-Package noch eine weitere Manifest-Option, mit der eine Abhängigkeit zu einem anderen Bundle spezifiziert werden kann:
Import-Package: org.osgi.framework
Mit Import-Package wird die Entscheidung für ein konkretes Bundle dem OSGi-Framework überlassen. Im Beispiel wird lediglich ein Bundle gefordert, welches das Package org.osgi.framework exportiert. Welches Bundle genau ist dabei egal. Auf diesem Weg kann man die Unabhängigkeit von einer konkreten Implementierung erreichen - hier spielt es beispielsweise keine Rolle, welches Bundle org.osgi.framework bereitstellt. Dieses Bundle würde daher auch mit einem anderen OSGi-Framework funktionieren.
Tutorial: Bundle-Projekte importieren und Bundle-Abhängigkeiten angeben
In den folgenden Tutorial-Kapiteln werden wir Schritt für Schritt ein einfaches Adressbuch mit Eclipse RCP realisieren. Da wir uns dabei auf die Aspekte der GUI-Anwendung beschränken wollen, steht für die Datenhaltung der Anwendung ein Bundle com.example.addressbook.services zur Verfügung. Dieses stellt Klassen zum Laden und Speichern von Adressen bereit.
Sie finden dieses Bundle-Projekt unter Downloads sowie auf der beiliegenden CD. Importieren Sie dieses Projekt in Ihren Eclipse-Workspace. Vorhandene Projekte können Sie mit File > Import > General > Existing Projects into Workspace aus Ordnern oder ZIP-Dateien importieren:
Fügen Sie nun dem zuvor erstellten Bundle com.example.firstbundle eine Abhängigkeit zu com.example.addressbook.services hinzu, damit Sie die Klassen aus diesem Bundle verwenden können. Zum Bearbeiten der Bundle-Abhängigkeiten bietet Eclipse unter “Dependencies” einen grafischen Editor für die entsprechenden Manifest-Optionen an:
Verwenden Sie hierzu Import-Package, um die Packages com.example.addressbook.services und com.example.addressbook.entities zu importieren. Es wird empfohlen, generell Import-Package zu verwenden. So wird dem OSGi-Framework die Entscheidung für ein konkretes Bundle, welches das angeforderte Package bereitstellen kann, überlassen. Require-Bundle sollte nur zum Einsatz kommen, wenn tatsächlich ein ganz bestimmtes Bundle benötigt wird.
Für dieses Tutorial soll es zunächst nur darum gehen, Klassen aus dem importierten Bundle zu verwenden. Rufen Sie dazu testweise im Activator AddressbookServices.getAddressService().getAllAddresses() auf, um eine Liste aller Adressen zu erhalten. Geben Sie diese Adressliste auf der Konsole aus.
Wenn Sie nun den OSGi-Container erneut starten, sollten Sie einen Missing imported package-Fehler erhalten. Sie haben com.example.firstbundle zwar eine Abhängigkeit zu com.example.addressbook.services hinzugefügt - dies bewirkt jedoch nicht automatisch, dass das benötigte Bundle automatisch mit in die Startkonfiguration aufgenommen wird. Sie müssen es manuell hinzufügen oder mit Add Required hinzufügen lassen:
Literaturempfehlungen
- Die OSGI Service Platform - Eine Einführung mit Eclipse Equinox (Gerd Wütherich, Nils Hartmann, Bernd Kolb, Matthias Lübken)
- OSGi and Equinox: Creating Highly Modular Java Systems (Jeff McAffer, Paul Vanderlei, Simon Archer, erscheint im März 2010)



Hi,
ich bins nochmal . kann man das addressbook_services .zip schon iergendwo runterladen?