JAR Service Provider Abstraktion

Die Class Loader Isolation ist eines der prominentesten Features von OSGi. Unglücklicherweise führt diese zu einer Inkompatibilität mit dem JAR Service Provider Mechanismus aus der JAR Spezifikation. Dieser Mechanismus stützt auf die hierarchische Organisation der Class Loader von klassischen Java Applikationen ab. Dies funktioniert in OSGi aber nicht, weil der Plug-In Host die einzelnen Plug-Ins gar nicht „sehen“ kann.

Ich habe mich mit diesem Thema beschäftigt, weil ich Apache FOP in einer OSGi Umgebung benutzen wollte. Das Apache XML Graphics Projekt (Batik & FOP) machen intensiv vom JAR Service Provider Mechanismus Gebrauch. Zum Beispiel ist jedes Ausgabeformat in FOP eigentlich einfach ein Plug-In, welches über diesen Mechanismus gefunden wird. Es geht viele Erweiterungspunkte mehr wie diesen. Das Ziel war es eine Lösung zu finden, welche sowohl in klassischen Java Umgebungen wie auch unter OSGi funktioniert. Unglücklicherweise ist dies nicht ohne Änderungen an den Produkten des Apache XML Graphics Projektes möglich. Verschiedene Blog-Einträge im Netz zeigten diese Probleme und mögliche Lösungswege auf. Diese halfen mir sehr auf der Suche nach einem geeigneten Ansatz für FOP und Batik.

Das Resultat meiner Bemühungen habe ich hier unter der Apache License v2.0 veröffentlicht.

Download Seite

Bitte lesen Sie die README.txt Datei in der Distribution. Es beschreibt alle Details.

Verwendungsbeispiel

Der folgende Code zeigt auf, wie dieses Paket benützt werden kann.

public class ExampleServiceClassConsumer {

    //volatile used because we do copy/replace rather than use synchronization
    private volatile Collection providers = new java.util.ArrayList();

    public ExampleServiceClassConsumer() {
        ServiceProviderListener listener = new ServiceProviderListener() {

            public void providerAdded(Class clazz) {
                register(clazz);
            }

            public void providerRemoved(Class clazz) {
                unregister(clazz);
            }

        };
        ServiceTracker tracker = Services.getInstance().getServiceTracker(
                ImageReaderSpi.class);
        tracker.addServiceProviderListener(listener);
    }

    private void register(Class provider) {
        //Use copy and replace to avoid synchronization
        Collection newList = new java.util.ArrayList(this.providers);
        newList.add(provider);
        this.providers = newList;
        System.out.println(provider.getName() + " added.");
    }

    private void unregister(Class provider) {
        //Use copy and replace to avoid synchronization
        Collection newList = new java.util.ArrayList(this.providers);
        if (newList.remove(provider)) {
            this.providers = newList;
            System.out.println(provider.getName() + " removed.");
        }
    }
} 

Diese Klasse erstellt einen ServiceProviderListener, der Notifikation erhält, wenn Service Provider hinzugefügt oder entfernt werden. Im zweiten Schritt wird ein ServiceTracker (nicht in der OSGi Variante!) geholt um auf ImageReaderSpi Implementations-Klassen zu hören in diesem Fall. Wenn der Service Listener einmal hinzugefügt ist, kriegt man Notifikationen über die verfügbaren Service Provider.

Man beachte die Notwendigkeit für Thread-Safety des Listener Codes. Irgendwelche Threads können diesen Listener jeder Zeit aufrufen.

Ein paar Notizen

Aus Performance-Gründen inspiziert der in das Paket eingebaute OSGi extender nur Bundles, welche den folgenden Manifest Header Eintrag haben:

JAR-Service-Provider: true

Das heisst, dass alle Service Provider den obigen Eintrag haben müssen, damit sie gefunden werden können. Diese Einschränkung kann über folgendes System Property abgeschalten werden: -Dch.jm.util.services.osgi.checkAllBundles=true

Apache Aries bietet mit SPI Fly eine Lösung, die etwas ähnliches tut. Allerdings stützt diese Lösung auf die Klasse java.util.Services ab, welche erst ab Java 6 zur Verfügung steht. Meine Lösung funktioniert auch mit Java 5. Ich arbeite an einem Nachfolger der obigen Lösung, welche teilweise auf SPI Fly abstützt, aber die Service Dynamik (den Listener) und Java 5 Kompatibilität bietet. Dies habe ich bereits in Produktion, aber leider hatte ich noch keine Zeit den Source Code zu veröffentlichen.