Java 6 ServiceLoader

In deze post wil ik graag de ServiceLoader klasse bespreken. Deze klasse maakt het mogelijk om applicaties te configureren. Dit gebeurt door de klasse die een interface implementeert, op te geven in een bestand. In Effective Java spreekt Joshua Bloch zich uit voor het gebruik van interfaces. Interfaces zorgen voor een kleinere koppeling tussen klassen en objecten. Door een implementatie van een interface in de code los te koppelen, wordt nog een stap gezet in de richting van Dijkstra’s spreuk: “Maximale cohesie, minimale koppeling”.

Het is verstandig om interfaces te gebruiken bij het definiëren van objecten. Hiervoor zijn verschillende redenen. Eén van die redenen is dat de ontwikkelaar geen zicht heeft op implementatie details. Hij kan daar dan ook geen misbruik van maken. Een andere reden is dat de implementatie veranderd kan worden zonder een applicatie te breken. In beide gevallen is het duidelijk dat er een zachtere koppeling ontstaat wanneer een interface wordt gebruikt.

Vaak komt het voor dat in de code al een keuze is gemaakt voor de te gebruiken implementatie. Bijvoorbeeld:

List lijst = new ArrayList();

Het noemen van ArrayList is een harde koppeling. Na het kiezen van een andere implementatie is het noodzakelijk de code opnieuw te compileren. Dit is niet wenselijk. Het zou fijn zijn om deze koppeling te vermijden.

Na gesprekken met collega Ivo Limmen over deze kwestie liet hij mij zijn oplossing zien. Hij had met eigen handen een framework gebouwd wat het mogelijk maakt om de keuze van implementerende klassen te configureren. Nadat Ivo mij had uitgelegd hoe zijn oplossing in elkaar stak, zei hij dat het Java platform deze functionaliteit al in zich heeft. Deze opmerking heb ik opgevat als een persoonlijke uitdaging die kwestie eens uit te zoeken.

API
De API van ServiceLoader, de klasse die bovengenoemde problemen oplost, is vriendelijk kort. Er wordt gesproken over services en providers. Een service is een (verzameling van) interface(s) en een provider is een specifieke implementatie van een service. De API bestaat uit zes methoden. Drie ervan zijn statische methoden die een ServiceLoader object terug geven. Deze zijn afhankelijk van een ClassLoader die als argument meegegeven kan worden.

Van de drie overige methoden is toString() er één van. Aanroepen van deze methode levert een string op die als volgt is opgebouwd. De volledig gespecificeerde naam van ServiceLoader gevolgd door de volledig gespecificeerde naam van de service tussen blokhaken. Hieronder geef ik een voorbeeld voor de Animal service uit de meegeleverde jar file.

java.util.ServiceLoader[nl.sogyo.blog.serviceloader.interfaces.Animal]

De belangrijkste methode is iterator() dat een Iterator object terug geeft. Deze Iterator zal één voor één de providers instantiëren. Hiervoor is het noodzakelijk dat de provider een argumentloze constructor heeft. De ServiceLoader zoekt deze providers in de META-INF/services directory. Hierin hoort een file met als naam de volledig gespecificeerde naam van de service. De inhoud van deze file is dan weer de volledig gespecificeerde naam van de provider.

Hieronder werk ik een voorbeeld uit wat gebruik maakt van bovenstaande techniek.

Voorbeeld
Voor dit voorbeeld heb ik een Animal interface gedefinieerd. De enige methode van deze interface is makeCall(). Deze methode schrijft het geluid wat het dier maakt naar de standaard uitvoer. Ik heb ook voor enkele implementaties gezorgd. Zo miauwt een kat, keft een hond en loeit een koe. Verder heb ik een AnimalPound klasse gemaakt. Deze klasse heeft een methode releaseAnimal(). Deze methode ziet er als volgt uit.

public Animal releaseAnimal() {

  ServiceLoader animalLoader = ServiceLoader.load(Animal.class);

  for(Animal beast: animalLoader) {

    /* We are expecting only one beast. */

    return beast;

  }

  throw new RuntimeException("No Animal registered.");

}

Hierin zie je de ServiceLoader gebruikt worden. Het enige wat nu nog van belang is het creëren van een services directory in de META-INF directory met bijbehorende inhoud. Bijvoorbeeld bevat de file META-INF/services/Animal de regel SmallCat.

Wanneer nu aan het AnimalPound gevraagd wordt een Animal vrij te geven en dit beest wordt gevraagd een geluid te maken dan zal het “miauw miauw” naar standaard uitvoer schrijven.

Bovenstaande heb ik gepackaged en gebundeld in deze file.