Artikel

 
April 2009 | Artikel

Services à la OSGi Fortsetzung, Teil 2

Teil 1   Teil 2   

Services nutzen
Zur Nutzung von Services sind zwei Schritte erforderlich. Zunächst wird unter Angabe eines Service Interfaces eine oder mehrere ServiceReferences erfragt, danach werden mithilfe dieser der oder die tatsächlichen Services abgerufen. Die hierfür wichtigsten Methoden des BundleContext lauten:

  1. ServiceReference getServiceReference(String clazz);
  2. ServiceReference[] getServiceReferences(String clazz, String filter);
  3. Object getService(ServiceReference reference);


Der Grund für die gerade aufgeführte Mehrdeutigkeit ist einfach zu erklären: Es können unter demselben Service Interface beliebig viele Services registriert werden, wohlgemerkt auch gar keiner. Der Nutzer kann daher a priori nicht wissen, wie viele Services registriert sind, und muss mit dieser inhärenten Mehrdeutigkeit umgehen. Die Methode getServiceReferences() ermöglicht durch die Verwendung eines Filters, die Ergebnismenge einzuschränken. Sie liefert für alle passenden Services eine ServiceReference zurück. Anders die Methode getServiceReference(). Hier wendet bereits das OSGi Framework eine Heuristik an, die die Ergebnismenge auf einen Service einschränkt, sofern überhaupt passende registriert sind. Dabei wird der Service zurückgeliefert, dessen Property service.ranking (Tabelle 1) den höchsten Wert hat. Falls dies zu keiner eindeutigen Entscheidung führt, wird der Service mit der kleinsten ID verwendet.

In unserem Beispiel erstellen wir ein neues Bundle com.weiglewilczek.example.osgi.contacts.shell, das beim Starten alle registrierten ContactRepository Services aufruft, sodass wir getServiceReferences() verwenden. Beide Methoden bedürfen der Prüfung auf null, denn dies ist der Rückgabewert, auch für getServiceReferences(), falls kein Service zur Anfrage passt. Anschließend kann getService() aufgerufen werden, wobei eine zuvor zurückgelieferte ServiceReference als Parameter übergeben wird. Aufgrund der Dynamik von OSGi muss unbedingt nochmals auf null geprüft werden (Listing 2), denn es könnte ja vorkommen, dass im Moment zwischen der Abfrage der ServiceReference und des Services dieser deregistriert wurde.

Listing 2
  1. ServiceReference[] references =
  2. context.getServiceReferences(ContactRepository.class.getName(), null);
  3. if (references != null) { // Check if any service registered
  4. for (ServiceReference reference : references) {
  5. ContactRepository contactRepository = (ContactRepository)
  6. context.getService(reference);
  7. if (contactRepository != null) { // Check again!
  8. System.out.println(MessageFormat.format(
  9. "All contacts of {0}:", reference.getProperty(ContactRepository.NAME)));
  10. Contact[] contacts = contactRepository.getAllContacts();
  11. for (Contact contact : contacts) { System.out.println(MessageFormat.format("{0} {1}",
  12. contact.getFirstName(), contact.getLastName()));
  13. ...


In unserem Beispiel geben wir zum einen den Wert der Service Property contactRepository.name aus, also den Namen des ContactRepository. Anschließend geben wir die Namen aller enthaltenen Contacts aus. Um das Beispiel auszuführen, legen wir eine OSGi Framework Run Configuration an, nehmen unsere Bundles sowie deren Abhängigkeiten auf und starten dann zuerst com.weiglewilczek.example.osgi.contacts.inmemory und danach com.weiglewilczek.example.osgi.contacts.shell. Diese Startreihenfolge ist wichtig, da sowohl das Registrieren, als auch das Konsumieren der Services im Beispiel beim Starten erfolgt. Wenn die Reihenfolge umgedreht wird, werden wir keinerlei Ausgabe sehen. Dieses Verhalten ist höchst problematisch, denn bei einem dynamischen modularen System kann die Startreihenfolge kaum kontrolliert werden.

Service Properties und Filter
Wir haben bereits eine Möglichkeit kennengelernt, wie man Service Properties nutzen kann, und zwar als Informationsträger. Eine weitere Möglichkeit von besonderer Bedeutung ist die Verwendung in Filtern, um die Ergebnismenge beim Abrufen von ServiceReferences zu einzuschränken. Wie bereits beschrieben, verwendet das OSGi Framework die Service Property service.ranking dazu, beim Aufruf von getServiceReference() einen eindeutigen Treffer zu ermitteln. Natürlich können wir auch beliebige eigene Service Properties definieren und diese in Filtern nutzen.

Wie sieht nun ein Filter aus? OSGi verwendet dazu ein besonderes Format: Die "String Representation of LDAP Search Filters" [3]. Die Syntax beruht auf der polnischen Notation, bei der zuerst die Operatoren und danach die Operanden geschrieben werden. Abbildung 2 visualisiert die Filtersyntax in einem Syntaxdiagramm. Im Folgenden zwei Beispiele:

(objectClass=com.weiglewilczek*)
(&(objectClass=com.weiglewilczek*)(service.ranking>=10))

Unser Beispiel ist zu einfach, um Filter im Code zu verwenden. Aber die Equinox Console ermöglicht bei der Verwendung des Kommandos die Angabe eines Filterausdrucks. Wenn wir das Beispiel starten und den ersten oben aufgeführten Beispielfilter eingeben, werden genau die beiden Services ausgegeben (Abb. 3).

Services und Dynamik
OSGi ist ein dynamisches System und dies gilt insbesondere für Services. Konsumenten müssen mit dieser inhärenten Dynamik umgehen. Dafür bietet das OSGi Framework die Möglichkeit, auf ServiceEvents zu reagieren, also insbesondere auf das Registrieren und Deregistrieren. Über den BundleContext können ServiceListeners angemeldet werden, die entweder alle ServiceEvents oder eine über Filter eingeschränkte Untermenge erhalten.

void addServiceListener(ServiceListener listener);
void addServiceListener(ServiceListener listener, String filter);

Aufgrund von Nebenläufigkeit kann der Umgang mit diesen ServiceListeners recht diffizil sein. Soll beispielsweise eine jederzeit aktuelle Liste von Services eines bestimmten Typs vorgehalten werden, so besteht die Gefahr, Duplikate zu erzeugen oder einzelne Services auszulassen. Daher spezifiziert das OSGi Service Compendium mit dem ServiceTracker eine Hilfsklasse, die mögliche Race Conditons und andere Schwierigkeiten berücksichtigt und damit die Beherrschung der Servicedynamik vereinfacht. Wir empfehlen, in der Regel nicht direkt mit ServiceListeners zu arbeiten, sondern den ServiceTracker zu verwenden. Dieser bietet nicht nur die gerade beschriebene Möglichkeit der Serviceliste, sondern u. a. auch Methoden, um auf Registrieren und Deregistrieren zu reagieren oder eine bestimmte Zeit auf einen Service zu warten.

public Object[] getServices()
public Object addingService(ServiceReference reference)
public Object waitForService(long timeout)

Im Beispiel wird der ServiceTracker dazu verwendet, obiges Problem mit der Startreihenfolge zu lösen (Listing 3). Indem nicht mehr zu einem festen Zeitpunkt aktiv ContactRepository Services abgerufen wird, sondern auf deren Registrierung "gelauscht", spielt es keine Rolle mehr, welches Bundle zuerst gestartet wird.

  1. private final class ContactRepositoryTracker extends ServiceTracker {
  2. @Override
  3. public Object addingService(ServiceReference reference) {
  4. ContactRepository contactRepository = (ContactRepository)
  5. super.addingService(reference);
  6. if (contactRepository != null) { // Check again! System.out.println(MessageFormat.format("All contacts of {0}:", reference.getProperty(ContactRepository.NAME)));
  7. Contact[] contacts = contactRepository.getAllContacts();
  8. for (Contact contact : contacts) {
  9. System.out.println(MessageFormat.format("{0} {1}",
  10. contact.getFirstName(), contact.getLastName()));
  11. ...


Schlussbemerkung und Ausblick
Das OSGi Service Model komplettiert die bereits im letzten Teil vorgestellten Eigenschaften der Modularisierung und Laufzeitdynamik. Das OSGi Framework vereinigt diese Kernprinzipien zu einem dynamischen und serviceorientieren Modulsystem, das großen und vielfältigen Nutzen für die Java-Entwicklung bringt, z. B. durch Reduktion von Komplexität, Gewinn an Flexibilität oder Chancen für Wiederverwendung. Im nächsten Teil wenden wir uns einem fortgeschrittenen Thema zu: Der Möglichkeit, OSGi deklarativ und komponentenorientiert einzusetzen.

Heiko Seeberger ist als Technical Director für die Weigle Wilczek GmbH tätig. Sein technischer Schwerpunkt liegt in der Entwicklung von Unternehmensanwendungen mit OSGi, Eclipse RCP, Spring, AspectJ und Java EE. Seine Erfahrungen aus über zehn Jahren IT-Beratung und Softwareentwicklung fließen in die Eclipse Training Alliance ein. Zudem ist Heiko Seeberger aktiver Committer in Eclipse-Projekten, Autor zahlreicher Fachartikel und Redner auf einschlägigen Konferenzen.
  1. Eclipse SDK: www.eclipse.org/downloads
  2. Download des Beispiel-Projekts
  3. String Representation of LDAP Search Filters

Teil 1   Teil 2   

andere Artikel dieser Serie


Anzeige

Kommentare


Anzeige

zurück zum Seitenanfang