Java 6 ServiceLoader

 
30 december 2008

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.


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development, Java (EE)

Tags: , , ,


Reacties (2)

  • Daan Wanrooy schreef:

    @Ralf
    Ik betrap mij zelf erop te voldoen aan jouw praktijkvoorbeeld. Hoewel het zeker niet in elke situatie het beste is, vind ik het in veel gevallen verdedigbaar.

    Zo weet ik bijvoorbeeld niet hoe mijn code in de toekomst gebruikt gaat worden. Door een interface aan te bieden ligt de implementatie niet vast. Een betere implementatie kan nog geschreven en gebruikt worden, zonder dat er code hoeft te breken. Iets wat zonder interfaces niet mogelijk lijkt.
    Daarbij helpt het schrijven van een interface mij te concentreren op wat belangrijk is: het ontwerp in plaats van de implementatie.

    Het is zo dat interfaces de complexiteit van een applicatie verhoogt. Die complexiteit vind ik echter te overzien. Daarnaast levert die extra complexiteit veel flexibiliteit op. Ik neem de complexiteit voor lief, omdat het mij veel oplevert.

    Geplaatst op 08 januari 2009 om 13:22 Permalink

  • Ralf Wolter schreef:

    Het klopt dat een koppeling losser wordt indien je een interface gebruikt. Dit wil volgens mij niet zeggen dat het goed is om op elk object een interface te leggen.

    In de praktijk kom je nog wel eens tegen dat elke class een een interface en een impl class heeft. Het meest extreme geval dat ik ooit ben tegen gekomen was een interface ‘AbstractRootObject’ met een implementatie ‘AbstractRootObjectimpl’.

    Behalve dat een interface de koppeling lager legt, voegt het echter ook complexiteit toe. We hebben immers twee classes waar we er eest een enkele hadden. Erger nog om de losse koppeling te behouden moeten we ook andere regels hanteren voor het instatieren van het object zoals bijvoorbeeld een Factory.

    Hetgeen wat je hier beschrijft is een vorm van zo’n factory. Het kan erg handig zijn, maar veel objecten hebben al een dusdanige semantische koppeling dat het realiseren van het hardere technische koppeling niet noodzakelijk een slechte zaak is, en veel minder complex is.

    Geplaatst op 07 januari 2009 om 13:35 Permalink