Une Architecture Plugin Avec Java
Aujourd’hui, nous allons parler d’une fonctionnalité présente dans Java depuis la version 6 et très peu utilisée, les
Service Provider Interface
(SPI). Nous verrons également comment créer une architecture utilisant les plugins avec
SpringBoot.
SPI, Késako ?
Derrière les SPI, il y a une réponse à un problème bien connu qui est “Comment découpler la déclaration de l’implémentation d’un service ?” Dans les langages objets, cela passe traditionnellement par une interface. Celle-ci fait office de contrat que doivent respecter les implémentations. Le code client fait appel à une interface et via un mécanisme d’injection de dépendance, l’implémentation correspondante est appelée. La beauté de la chose est que le client ne connaît que l’interface au moment de la compilation du code. Il n’y a donc pas de dépendance entre le client et l’implémentation. La conséquence est un code plus maintenable et la possibilité de remplacer une implémentation par une autre de manière transparente.
Cependant, un problème arrive rapidement lorsque l’on souhaite délivrer une application utilisant des implémentations que l’utilisateur peut créer lui-même. Dans ce cas les différentes implémentations ne peuvent pas être embarquées lors de la compilation. C’est ce problème que les SPI permettent de résoudre en Java. La possibilité d’ajouter des implémentations sous forme de Jar sans avoir à recompiler le code source de l’application.
Comment cela fonctionne ?
Pour qu’une SPI
puisse fonctionner, il faut 4 composantes :
- Une Interface déclarant le contrat à respecter.
- Un service permettant de découvrir les implémentations de cette interface
- Une ou plusieurs implémentations
- Un client utilisant le service pour accéder aux fonctionnalités des implémentations.
Exemple.
Prenons pour l’exemple, la création d’un système de plugin pour l’application. Nous allons pour cela créer 3 projets maven :
- services-spi (notre interface et le service)
- services (les implémentations)
- client (le consommateur de notre service)
Interface et Service
Nous allons donc commencer par créer une interface commune à tous nos plugins dans le module maven services-spi
:
Nos plugins auront juste un nom et une description. Ajoutons le service permettant d’accéder à cette interface.
|
|
Comme vous pouvez le voir, notre service est un singleton. De cette manière, on ne recrée pas une instance de ServiceLoader
à chaque appel. Ce ServiceLoader
est instancié via la méthode load(Class class)
et prends en paramètre notre interface.
Il fournit ensuite un stream permettant d’itérer sur la liste des implémentations trouvées.
Implémentations
Créons maintenant deux implémentations différentes dans le module services
:
|
|
et
|
|
Jusqu’ici rien de bien particulier. Nos deux plugins implémentent notre interface Plugin
. Afin de permettre à Java de découvrir ces implémentations,
nous allons maintenant devoir déclarer un fichier dans META-INF/services
.
Ce fichier doit se nommer d’après le nom de l’interface implémentée. Dans notre cas :
info.techland.myapp.services.spi.pluginProvider.Plugin
Il doit contenir la liste des implémentations disponibles. Dans notre cas :
Client
Il ne reste plus qu’à faire appel à notre service dans notre client. Nous allons utiliser un client SpringBoot pour cela. Pour pouvoir injecter notre service provider dans notre application, il suffit de définir une configuration :
Maintenant, nous pouvons l’utiliser dans notre application Spring. Par exemple dans un controller.
|
|
Une dernière configuration est nécessaire pour permettre à Spring de charger des Jar supplémentaires au démarrage. Il
suffit de créer un fichier loader.properties
à côté du fichier application.properties.
Mise en oeuvre.
Nous pouvons maintenant compiler notre code via la commande mvn clean package
depuis le projet parent.
Une fois cela fait, prendre le jar api/target/api-1.0.0-SNAPSHOT.jar
et le placer dans un dossier de son choix.
Il est ensuite possible de lancer l’application avec :
|
|
La classe PropertiesLauncher permet de dire à SpringBoot de prendre en compte notre fichier loader.properties
.
Si vous allez sur l’url http://localhost:8080/plugins, aucuns résultats ne devrait être affichés.
Ajoutons maintenant le jar ./services/services-1.0.0-SNAPSHOT.jar
dans un dossier lib
à côté du jar de notre application
et relançons la commande.
Résultat :
L’appel à notre endpoint nous retourne la liste des deux plugins. Nous venons d’ajouter deux implémentations à notre application sans avoir besoin de la recompiler.
Conclusion
Nous venons de voir comment créer une application extensible avec Java. Il s’agit d’un outil très puissant pour laisser à l’utilisateur le choix des implémentations à utiliser.
Le code source est disponible ici : https://github.com/scandinave/springboot-spi