39. Services dans JPMS (Le Modèle ServiceLoader)
Table des matières
- 39.1 Le Problème que les Services Résolvent
- 39.1.1 Rôles dans le Modèle de Service
- 39.1.2 Module Interface de Service
- 39.1.3 Module Fournisseur de Service
- 39.1.4 Module Consommateur de Service
- 39.1.5 Chargement des Services à lExécution
- 39.1.6 Règles de Résolution des Services
- 39.1.7 Couche Service Locator
- 39.1.8 Schéma Séquentiel dInvocation
- 39.1.9 Tableau Récapitulatif des Composants
JPMS inclut un mécanisme de service intégré qui permet aux modules de découvrir et dutiliser des implémentations à l’exécution
sans coder en dur des dépendances entre fournisseurs et consommateurs.
Ce mécanisme est basé sur l’API ServiceLoader existante, mais les modules le rendent fiable, explicite et sûr.
39.1 Le Problème que les Services Résolvent
Parfois un module doit utiliser une capacité, mais ne devrait pas dépendre dune implémentation spécifique.
Des exemples typiques incluent : - frameworks de journalisation - pilotes de base de données - systèmes de plugins - fournisseurs de service sélectionnés à l’exécution
Sans services, le consommateur devrait dépendre directement dune implémentation concrète.
Cela crée un couplage fort et réduit la flexibilité.
39.1.1 Rôles dans le Modèle de Service
Le modèle de service JPMS implique quatre rôles distincts.
| Rôle | Description |
|---|---|
Interface de service |
Définit le contrat |
Fournisseur de service |
Implémente le service |
Consommateur de service |
Utilise le service |
Service loader |
Découvre les implémentations à l’exécution |
39.1.2 Module Interface de Service
L’interface de service définit l’API dont les consommateurs dépendent.
Elle doit être exportée afin que les autres modules puissent la voir.
package com.example.service;
public interface GreetingService {
String greet(String name);
}
module com.example.service {
exports com.example.service;
}
Note
Le module d’interface de service ne devrait contenir aucune implémentation.
39.1.3 Module Fournisseur de Service
Un module fournisseur implémente l’interface de service et déclare quil fournit le service.
package com.example.service.impl;
import com.example.service.GreetingService;
public class EnglishGreeting implements GreetingService {
public String greet(String name) {
return "Hello " + name;
}
}
module com.example.provider.english {
requires com.example.service;
provides com.example.service.GreetingService with com.example.service.impl.EnglishGreeting;
}
Points clés :
- Le fournisseur dépend de l’interface de service
- La classe d’implémentation n’a pas besoin d’être exportée
- La directive provides enregistre l’implémentation
39.1.4 Module Consommateur de Service
Le module consommateur déclare quil utilise un service, mais ne nomme aucune implémentation.
module com.example.consumer {
requires com.example.service;
uses com.example.service.GreetingService;
}
Note
uses déclare l’intention de découvrir des implémentations à l’exécution.
Un module qui déclare uses mais ne possède aucun fournisseur correspondant sur le module path compile normalement,
mais ServiceLoader retourne un résultat vide à l’exécution.
39.1.5 Chargement des Services à lExécution
L’API ServiceLoader effectue la découverte de service.
Elle trouve tous les fournisseurs visibles pour le graphe des modules.
ServiceLoader<GreetingService> loader =
ServiceLoader.load(GreetingService.class);
for (GreetingService service : loader) {
System.out.println(service.greet("World"));
}
JPMS garantit que seuls les fournisseurs déclarés sont découverts.
La découverte “accidentelle” basée sur le classpath est empêchée.
39.1.6 Règles de Résolution des Services
Pour qu’un service soit découvrable par ServiceLoader, plusieurs conditions doivent être satisfaites :
| Règle | Signification |
|---|---|
| Le module fournisseur doit être lisible | Résolu par le graphe requires |
| L’interface de service doit être exportée | Les consommateurs doivent la voir |
Le consommateur doit déclarer uses |
Sinon ServiceLoader échoue |
Le fournisseur doit déclarer provides |
La découverte implicite est interdite |
39.1.7 Couche Service Locator
Il est possible dintroduire une couche supplémentaire appelée Service Locator.
Dans cette architecture :
- Le
consommateurn’utilise pas directementServiceLoader - Le
Service Locatorest le seul composant qui déclareuses - Le
consommateurdépend duService Locator
Structure architecturale :
Consommateur → Service Locator → ServiceLoader → Fournisseur
Module du Service Locator :
module com.example.locator {
requires com.example.service;
uses com.example.service.GreetingService;
}
Classe Service Locator :
package com.example.locator;
import java.util.ServiceLoader;
import com.example.service.GreetingService;
public class GreetingLocator {
public static GreetingService getService() {
return ServiceLoader
.load(GreetingService.class)
.findFirst()
.orElseThrow();
}
}
Module Consommateur :
module com.example.consumer {
requires com.example.locator;
}
Le consommateur ne déclare pas uses parce quil ninvoque pas directement ServiceLoader.
39.1.8 Schéma Séquentiel dInvocation
Séquence dexécution :
- Le
ConsommateurinvoqueGreetingLocator.getService() - Le
Service LocatorinvoqueServiceLoader.load(...) - Le
ServiceLoaderconsulte le graphe des modules - Le système identifie les modules qui déclarent
provides - L’implémentation du
Fournisseurest instanciée - L’instance est retournée au
Consommateur
Schéma séquentiel :
Consommateur
│
│ 1. getService()
▼
Service Locator
│
│ 2. ServiceLoader.load()
▼
ServiceLoader
│
│ 3. Résolution du fournisseur
▼
Implémentation Fournisseur
│
│ 4. Instance retournée
▼
Consommateur
39.1.9 Tableau Récapitulatif des Composants
| Composant | Rôle | exports | requires | uses | provides |
|---|---|---|---|---|---|
| SPI | Définit le contrat | ✅ | ❌ | ❌ | ❌ |
| Fournisseur | Implémente le service | ❌ | ✅ | ❌ | ✅ |
| Service Locator | Effectue la découverte | (optionnel) | ✅ | ✅ | ❌ |
| Consommateur | Utilise le service | ❌ | ✅ | ❌ | ❌ |