OSGi erwacht zum Leben

Regeln sind da, um gebrochen zu werden

Das Service-API-Bundle und die dazugehörigen Implementierungen weichen in Bezug auf die Nomenklatur etwas vom OSGi-Standard ab. Per Konvention sollte der Name des Root-Pakets eines Bundles jeweils dem Namen des Bundles gleichen. Da API und Implementierung jedoch eine logische Einheit bilden, empfehlen wir, das jeweilige Suffix (.api, .local, .remote) für das Root-Paket des Bundles zu ignorieren. Alle Bundles haben somit ein identisches Root-Paket com.zuehlke.contacts.service. Die Unterstruktur innerhalb der Bundles ist dann selbstverständlich spezifisch zu definieren.

Require-Bundle vs. Import-Package

Unabhängig vom Komponentenschnitt stellt sich im OSGi-Kontext die Frage, wie die Abhängigkeiten zwischen den Bundles definiert werden. Grundsätzlich gibt es zwei Varianten: Bundle- („Require-Bundle“) und Paketabhängigkeiten („Import-Package“). Im Wesentlichen ist diese Entscheidung wieder eine Abwägung von Flexibilität und Komplexität. Die Diskussion über den richtigen Weg ist äußerst spannend [2]. Wir haben uns in der Beispielanwendung für die Verwendung von Bundle-Abhängigkeiten entschieden. Die Grundlagen dieser Entscheidung haben wir im Kasten „Require-Bundle vs. Import-Package“ erläutert. Die Pfeile in Abbildung 2, die die Bezeichnung „Dependency“ tragen, repräsentieren also Bundle-Abhängigkeiten.

Bundle API = Exported packages

Ein Bundle hat in der Regel auch dann ein API, wenn es nicht als solches in einem separaten Bundle ausgezeichnet wurde („Stand-alone API“). Und zwar müssen im OSGi-Umfeld alle Pakete des Bundles dazugezählt werden, die im Manifest als exported markiert wurden. Ein abhängiges Bundle kann klassenpfadtechnisch nur auf diese Pakete zugreifen und versteht sie daher als zugesichertes API. Natürlich gelten auch hier die Java-Sichtbarkeiten, sodass nur die mit public deklarierten Klassen, Methoden etc. Teil der Schnittstelle sind.

Nicht exportierte Pakete enthalten Implementierungsdetails und können problemlos verändert werden, solange sie dem API genügen. Per Konvention werden diese „internen“ Pakete in einem Unterpaket internal zusammengefasst [3]. Das Bundle com.zuehlke.contacts.ui beispielsweise exportiert die Pakete com.zuehlke.contacts.ui.addresscheck und com.zuehlke.contacts.ui.intent zwecks Erweiterbarkeit. Alle anderen Klassen befinden sich in Unterpaketen von com.zuehlke.contacts.internal.ui.

Es stellt sich nun die Frage, wie ein abhängiges Bundle (Consumer) an die konkrete Implementierung gelangt. Dafür gibt es mehrere Möglichkeiten:

  1. direkte Instanziierung
  2. Static Factories/Factory Method Pattern
  3. OSGi Service Registry
  4. Eclipse Extension Registry

Die Möglichkeiten 1 und 2 sind nicht OSGi-spezifisch und an anderer Stelle bereits umfangreich beschrieben worden. Im Folgenden gehen wir detailliert auf die OSGi Service Registry (siehe „Abhängen via OSGi Services“ und „Abhängen via Gemini Blueprint“) und die Eclipse Extension Registry („Erweitern via Eclipse Extension Registry“) ein.

Abhängen via OSGi Services

OSGi bietet für das Registrieren und Auffinden von Implementierungen die so genannte OSGi Service Registry an. Hier werden Implementierungen, häufig Services, unter dem vollqualifizierten Namen eines bestimmten Interface registriert und können später mit eben diesem Interfacenamen von Consumern bezogen werden. So registriert das Provider-Bundle com.zuehlke.contacts.service.local die Implementierungen beispielsweise programmatisch im Activator, wie in Listing 1 zu sehen.

Listing 1
public void start(BundleContext bundleContext) throws Exception {
  super.start(bundleContext);
  instance = this;
  registerServices();
}

private void registerServices() {
BundleContext bundleContext = getBundle().getBundleContext();
bundleContext.registerService(CustomerService.class.getName(),
    new LocalCustomerService(), null);
  bundleContext .registerService(ContactService.class.getName(),
    new LocalContactService(), null);
}
Kommentare

Schreibe einen Kommentar

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