Implementacja mechanizmu wtyczek za pomocą ServiceLoader

Java w wersji 6 dostarcza klasę java.util.ServiceLoader, z użyciem której można zaimplementować mechanizm wtyczek. Za przykład posłuży nam aplikacja generująca raporty wspierająca różne formaty danych wyjściowych.

Każdy generator raportów musi implementować odpowiedni interfejs:

1
2
3
4
5
6
package com.mbialon.rg;

public interface ReportGenerator {
  String format();
  void generate();
}

W podstawowej wersji aplikacji możemy dostarczyć tylko raport generujący wyjście w formacie HTML.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.mbialon.rg;

public class HtmlReportGenerator implements ReportGenerator {
  public String format() {
    return "html";
  }

  public void generate() {
    // Html report generation
  }
}

Aby skorzystać z możliwości dostarczanych przez java.util.ServiceLoader musimy w katalogu META-INF/services archiwum jar umieścić plik o nazwie odpowiadającej ładowanej usłudze (w moim przypadku to pełna ścieżka do interfejsu generatora raportów), który zawierał będzie ścieżki do klas implementujących ładowane usługi (w moim przypadku będzie to ścieżka do generatora HTML). Treść pliku META-INF/services/com.mbialon.rg.ReportGenerator to tylko jedna linia:

1
com.mbialon.rg.HtmlReportGenerator

Z przygotowanej zgodnie z powyższym opisem implementacji możemy skorzystać w aplikacji klienckiej.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.mbialon.rg;

import java.util.*;

public class Client {
  public static void main(String[] args) {
    ServiceLoader<ReportGenerator> providers = ServiceLoader.load(ReportGenerator.class);

    List<String> availableFormats = new ArrayList<String>();
    for (ReportGenerator generator : providers) {
      availableFormats.add(generator.format());
    }

    // user can select format from available options
    String selectedFormat;

    ReportGenerator selectedGenerator;
    for (ReportGenerator generator : providers) {
      if (selectedFormat.equals(generator.format()))
        selectedGenerator = generator;
    }
  }
}

Po załadowaniu dostępnych generatorów raportów w aplikacji klienckiej możemy odczytać listę dostępnych formatów oraz możemy wybrać odpowiedni generator zgodnie z wybranym przez użytkownika formatem raportu. Stworzoną w ten sposób aplikację kliencką możemy rozszerzać o nowe formaty raportów dostarczając kolejne biblioteki bez konieczności rekompilacji aplikacji klienckiej. Każda z tych bibliotek musi zawierać implementacje generatorów oraz plik META-INF/services/com.mbialon.rg.ReportGenerator zawierający ścieżki do tych klas.