Ein logisches Modul wird in OSGi "Bundle" genannt und besteht aus einem JAR. Die OSGi Metadaten finden sich in der Manifest-Datei "META-INF/MANIFEST-MF" wieder. Somit kann man durch anreichern von Metainformationen aus einem normalen JAR ein OSGi Bundle machen.
Der Standard-Aufbau eines OSGi-Manifest sieht folgendermaßen aus:
Bundle-ManifestVersion: 2
Bundle-Name: Human readable name
Bundle-SymbolicName: unique bundle identifier
Bundle-Version: 1.0.0.qualifier
Das Attribut "Bundle-ManifestVersion" wurde aus Kompatibilitätsgründen eingeführt. Version "2" bedeutet, dass sich hier um ein OSGi-Bundle der Spezifikation R4 oder später handelt. Der "Bundle-Name" ist ein beliebiger Kurzname, ist aber für die Identifikation des Bundles irrelevant und stellt nur eine Hilfe für den Benutzer dar. OSGi verwendet zur Identifikation die Attribute "Bundle-SymbolicName" und "Bundle-Version". Das OSGi-Versionsschema sieht drei numerische Komponenten (Major, Minor, Micro) und einen optionalen alphanumerischen "qualifier" vor. Wird eine numerische Komponente nicht angegeben, wird 0 angenommen. Die Version "1" wird also als "1.0.0" aufgelöst.
Das HelloWorldProvider Bundle bietet einen HelloWorldService an. Damit man gegebenfalls auch die Implementierung austauschen kann, wird zuerst ein HelloWorldService-Interface angelegt.
package org.jvcode.osgi.provider;
public interface HelloWorldService
{
void helloWorld();
}
HelloWorldService.java
Der eigentliche Service HelloWorldProvider implementiert dieses Service Interface:
package org.jvcode.osgi.provider;
public class HelloWorldProvider implements HelloWorldService
{
public void helloWorld()
{
System.out.println("=== Hello World! ===");
}
}
HelloWorldProvider.java
OSGi bietet eine Service-Registry, für welche man Dienste registrieren kann. Um den Dienst automatisch beim Starten des Bundles zu registrieren, wird ein OSGi-BundleActivator implementiert.
package org.jvcode.osgi.provider;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator
{
public void start(BundleContext bundleContext) throws Exception
{
System.out.println("HelloWorldProvider is being started...");
System.out.println("Registering HelloWorldService...");
bundleContext.registerService(HelloWorldService.class.getName(),
new HelloWorldProvider(), null);
}
public void stop(BundleContext bundleContext) throws Exception
{
System.out.println("HelloWorldProvider is being stopped...");
}
}
Activator.java
Das BundleActivator-Interface fordert die Implementierung der beiden Methoden "start" und "stop". In der Methode start wird der Service über die Methode "registerService" des Objekts "BundleContext" registriert. Der erste Parameter gibt den Namen an, unter welcher der Service verfügbar sein soll. Als zweiter Parameter wird das eigentliche Service-Objekt übergeben. In diesem Falle wird eine neue Instanz von HelloWorldProvider erzeugt. Der dritte Parameter ermöglicht es zusätzliche Properties zu übergeben.
Damit die Activator-Klasse von OSGi aufgerufen wird und die Service-Klassen nach außen sichtbar sind, muss das Manifest erweitert werden.
Bundle-Activator: org.jvcode.osgi.provider.Activator
Import-Package: org.osgi.framework
Export-Package: org.jvcode.osgi.provider
Das Attribut "Bundle-Activator" definiert die Klasse, welche vom OSGi-Framework beim Aktivieren eines Bundles aufgerufen wird. Das Import-Package-Attribut definiert die Packages als Abängigkeiten, welche das Bundle von anderen Bundles benötigt. Da das "BundleActivator"-Interface vom package "org.osgi.framework" des OSGi-Framework kommt, muss eine Abhängigkeit zu diesem Package definiert werden. Das Attribut "Export-Package" bewirkt genau das Gegenteilige. Hierin wird definiert, welche Packages exportiert werden, und somit anderen Bundles zur Verfügung stehen. Da der HelloWorldService zur Verfügung gestellt werden soll, wird das Package "org.jvcode.osgi.provider" angegeben.
Hinweis
Nach dem Ändern des Manifests will IntelliJ IDEA die Abhängigkeiten synchronisieren. Leider gehen diese dabei verloren, so dass für das Compilieren die Abhängigkeiten zum Apache Felix-Framework und dem HelloWorldConsumer-Bundle manuell bzw. über Quick-Fixes wiederhergestellt werden müssen.
Das HelloWorldConsumer-Bundle besteht nur aus einem Bundle-Activator, in dem der vom HelloWorldProvider zur Verfügung gestellte Dienst aufgerufen wird.
package org.jvcode.osgi.consumer;
import org.jvcode.osgi.provider.HelloWorldService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
public class Activator implements BundleActivator
{
public void start(BundleContext bundleContext) throws Exception
{
System.out.println("HelloWorldConsumer bundle is being started...");
System.out.println("Getting reference of HelloWorld Service...");
ServiceReference reference = bundleContext.getServiceReference(HelloWorldService.class.getName());
HelloWorldService helloWorldService = ((HelloWorldService) bundleContext.getService(reference));
helloWorldService.helloWorld();
}
public void stop(BundleContext bundleContext) throws Exception
{
System.out.println("HelloWorldConsumer is being stopped...");
}
}
Activator.java
Zuerst wird ein ServiceReference-Objekt unter Angabe des Service-Namens aus dem BundleContext geholt. Da der Service unter dem Namen des Service-Interfaces registriert wurde, kann man hier auch wieder den Klassennamen des Interfaces verwenden. Mit Hilfe der Service-Referenz kann dann der eigentliche Service aus dem BundleContext geholt werden. Zuletzt wird die gewünschte Methode "helloWorld" des Services aufgerufen.
Im obigen Beispiel wurde auf Fehlerbehandlung verzichtet, da eine Fehlersituation unwahrscheinlich ist. In einer Real-Life-Applikation ist diese aber unabdingar, da ein Service mit dem Namen nicht vorhanden sein könnte, das Service-Objekt nicht dem erwarteten Interface entsprechen könnte oder Ähnliches.
Zuletzt muss noch die Manifest-Datei angepasst werden. Zum einen muss noch der BundleActivator aufgenommen werden, zum anderen müssen die benötigten Packages des OSGi-Frameworks und des HelloWorldProvider-Bundles importiert werden.
Import-Package: org.jvcode.osgi.provider,org.osgi.framework
Bundle-Activator: org.jvcode.osgi.consumer.Activator
Das untenstehende Komponenendiagramm zeigt die OSGi-Bundles mit ihren exportierten Packages und die Abhängigkeiten von anderen Bundles zu diesen.
Das OSGi-Framework-Bundle exportiert das Package "org.osgi.framework" das von beiden HelloWorld-Bundles für den BundleActivator benötigt wird. Das HelloWorldProvider-Bundle exportiert das Package "org.jvode.osgi.provider", welches das HelloWorldService-Interface und dessen Implementierung enthält. Das HelloWorldConsumer-Bundle exportiert keine Packages. Es benötigt nur die Packages aus dem HelloWordProvider- und OSGi-Bundle.