Eclipse RCP
20.09.2010 - 24.09.2010, Hamburg
git
07.10.2010 - 08.10.2010, Essen
Eclipse RCP

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:

OSGi Container

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.

OSGi Plug-in Zustände

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:

New Plug-in Project

Der Assistent fragt nun nach einigen allgemeinen Eigenschaften des zu erstellenden Bundles:

New Plug-in Project

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:

New Plug-in Project

Eclipse erstellt nun ein neues Bundle-Projekt mit folgendem Inhalt:

Dateien com.example.firstbundle

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:

Manifest-Editor com.example.firstbundle

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:

OSGi Startkonfiguration

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:

Require-Bundle / Export-Package

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:

Bundle-Abhängigkeiten im Manifest-Editor bearbeiten

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

Michael Beerens, 12. November, 19:09 Uhr

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

Ralf Ebert, 15. November, 20:02 Uhr

Danke für den Hinweis, es gibt jetzt eine Download-Seite.

Marco Bienlein, 15. November, 21:51 Uhr

Hallo Herr Ebert,

danke für das Hinzufügen der Download-Seite. Ggf. sollten Sie die Package-Namen entweder im Bundle im Zip-File oder im Tutorial anpassen, diese stimmen nicht überein.

MfG
Marco Bienlein

Ralf Ebert, 17. November, 16:14 Uhr

Richtig, ich habe alle Packages von de.rcpbuch nach com.example umbenannt, mit dem nächsten Update werden auch die Texte und Screenshots aktualisiert.

Gabi Heimann, 05. Januar, 14:32 Uhr

Hallo,
Ich probiere RCP und OSGI zum ersten Mal aus. Das o.g. Beispiel funktioniert nicht, da Address nicht bekannt ist. Die Meldung lautet, das ein Required Bundle fehlt. Wird dieses importiert, kann Address auch aufgelöst werden. Jetzt habe ich aber ein Import und ein Required Bundle.

Ralf Ebert, 08. Januar, 09:16 Uhr

Hallo Frau Heimann, an welcher Stelle genau trat das Problem auf? Es sollte wie im Text beschrieben nur ein Import-Package oder Require-Bundle im Manifest notwendig sein. Diese Angaben sind natürlich komplett unabhängig von dem Java-Import in der jeweiligen Quelltextdatei.

Gabi Heimann, 10. Januar, 16:14 Uhr

Hallo Herr Ebert,
ich habe als Ergebnis für den Service-Zugriff AddressbookServices.getAddressService().getAllAddresses() schon List<Address> eingefügt. Dies funktioniert mit dem importieren ...services nicht, da fehlen noch die entities.

Thomas Schindler, 02. Februar, 17:06 Uhr

Hallo,
im Dialog "Run Configurations" habe ich unter Targetplattform
org.eclipse.osgi(3.5.0.v20090520).
Wenn ich ein "Validate Bundles" mache, dann habe ich die Fehlermeldung:
Missing Constraint: ...Bundle-version="3.5.1".
Was kann ich tun, wenn ich die Eclipse Version nicht ändern kan?

Ralf Ebert, 02. Februar, 20:01 Uhr

Hallo Herr Schindler,
die Mindestversion wird in der META-INF/MANIFEST.MF festgelegt, in der Tat gibt es keinen speziellen Grund warum mindestens diese Version benötigt wird. Ich habe das soeben korrigiert.

Jochen Schindler, 19. Mai, 08:06 Uhr

Hallo,
nach vielem vergeblichem Suchen, habe ich bis jetzt nichts gefnunden welches meiner Frage entspricht. Daher hier nun meine Frage.
Wenn man einen eigenen Server in Java geschrieben hat, und diesen nun mit Hilfe von OSGI modularisieren will, WIE FÄNGT MAN DIES AM BESTEN AN. Läuft dann der eingene Server als Service/Modul innerhalb eines OSGI Containers, oder lädt man sich einfach das Equninox z.B in den Klassenpfad der eigenen Applikation, startet diesen hieraus ( WIE? ), oder was ist hier ein gangbarer Weg?
Vielen Dank für weiterführende Informationen für einen Laien in OSGI.

Ralf Ebert, 19. Mai, 08:16 Uhr

Man startet einen OSGi Container und verwendet diesen als Laufzeitumgebung für die eigenen Bundles, die dann die Server-Dienste bereitstellen. So kann man bspw. die Jetty-Bundles in einem Equinox starten, um HTTP-Requests entgegenzunehmen und per Servlet zu beantworten. Ich werde mal ein Tutorial schreiben, wie man das mit Bordmitteln von OSGi erreicht, im Moment ist die Zeit nur etwas knapp. Ein guter Ansatzpunkt ist evt. auch der SpringSource dm Server, eine fertige und gut dokumentierte Serverumgebung basierend auf OSGi.

Amruta Arvind Chitale, 20. Mai, 12:27 Uhr

Can you please provide these tutorials in English?

Ralf Ebert, 20. Mai, 12:54 Uhr

I'd like to, but unfortunately there will be not enough time in 2010 for such an undertaking. I need the materials in German for my training courses which fund all the writing I'm doing. Maintaining two different languages is a lot of work, maybe I'll do an English translation when the book is finished.

Lodda, 13. Juli, 13:23 Uhr

Ich habe die toString-Methode in Address modifiziert und wollte anschließend das service-bundle mit update neuladen. Das hat allerdings nicht funktioniert, beim "firstBundle" funktioniert es hingegen. Ist das ein Bug?

Über Ihre Kommentare und Hinweise freue ich mich sehr:
Ralf Ebert | Eclipse RCP Buch | Plug-in basierte Entwicklung mit OSGi