39. Servizi in JPMS (Il Modello ServiceLoader)
Indice
- 39.1 Il Problema che i Servizi Risolvono
- 39.1.1 Ruoli nel Modello dei Servizi
- 39.1.2 Modulo Interfaccia del Servizio
- 39.1.3 Modulo Provider del Servizio
- 39.1.4 Modulo Consumer del Servizio
- 39.1.5 Caricamento dei Servizi a Runtime
- 39.1.6 Regole di Risoluzione dei Servizi
- 39.1.7 Livello Service Locator
- 39.1.8 Schema Sequenziale di Invocazione
- 39.1.9 Tabella Riassuntiva dei Componenti
JPMS include un meccanismo di servizio integrato che permette ai moduli di scoprire e utilizzare implementazioni a runtime
senza codificare rigidamente dipendenze tra provider e consumer.
Questo meccanismo è basato sulla ServiceLoader API esistente, ma i moduli lo rendono affidabile, esplicito e sicuro.
39.1 Il Problema che i Servizi Risolvono
Talvolta un modulo necessita di utilizzare una capacità, ma non dovrebbe dipendere da una implementazione specifica.
Esempi tipici includono: - framework di logging - driver di database - sistemi plugin - provider di servizi selezionati a runtime
Senza i servizi, il consumer dovrebbe dipendere direttamente da una implementazione concreta.
Questo crea accoppiamento stretto e riduce la flessibilità.
39.1.1 Ruoli nel Modello dei Servizi
Il modello dei servizi JPMS coinvolge quattro ruoli distinti.
| Ruolo | Descrizione |
|---|---|
Interfaccia del servizio |
Definisce il contratto |
Provider del servizio |
Implementa il servizio |
Consumer del servizio |
Utilizza il servizio |
Service loader |
Scopre le implementazioni a runtime |
39.1.2 Modulo Interfaccia del Servizio
L’interfaccia del servizio definisce l’API da cui i consumer dipendono.
Deve essere esportata affinché altri moduli possano vederla.
package com.example.service;
public interface GreetingService {
String greet(String name);
}
module com.example.service {
exports com.example.service;
}
Note
Il modulo dell’interfaccia del servizio non dovrebbe contenere implementazioni.
39.1.3 Modulo Provider del Servizio
Un modulo provider implementa l’interfaccia del servizio e dichiara di fornire il servizio.
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;
}
Punti chiave:
- Il provider dipende dall’interfaccia del servizio
- La classe di implementazione non necessita di essere esportata
- La direttiva provides with registra l’implementazione
39.1.4 Modulo Consumer del Servizio
Il modulo consumer dichiara di utilizzare un servizio, ma non nomina alcuna implementazione.
module com.example.consumer {
requires com.example.service;
uses com.example.service.GreetingService;
}
Note
uses dichiara l’intenzione di scoprire implementazioni a runtime.
Un modulo che dichiara uses ma non ha provider corrispondenti nel module path compila normalmente,
ma ServiceLoader restituisce un risultato vuoto a runtime.
39.1.5 Caricamento dei Servizi a Runtime
La ServiceLoader API esegue la scoperta del servizio.
Trova tutti i provider visibili al grafo dei moduli.
ServiceLoader<GreetingService> loader =
ServiceLoader.load(GreetingService.class);
for (GreetingService service : loader) {
System.out.println(service.greet("World"));
}
JPMS garantisce che solo i provider dichiarati siano scoperti.
La scoperta “accidentale” basata su classpath è prevenuta.
39.1.6 Regole di Risoluzione dei Servizi
Affinché un servizio sia individuabile da ServiceLoader, devono essere soddisfatte diverse condizioni:
| Regola | Significato |
|---|---|
| Il modulo provider deve essere leggibile | Risolto dal grafo requires |
| L’interfaccia del servizio deve essere esportata | I consumer devono vederla |
Il consumer (o il Service Locator) deve dichiarare uses |
Altrimenti ServiceLoader fallisce |
Il provider deve dichiarare provides |
La scoperta implicita è vietata |
39.1.7 Livello Service Locator
È possibile introdurre un livello aggiuntivo denominato Service Locator.
In questa architettura:
- Il
consumernon utilizza direttamenteServiceLoader - Il
Service Locatorè l’unico componente che dichiarauses - Il
consumerdipende dalService Locator
Struttura architetturale:
Consumer → Service Locator → ServiceLoader → Provider
Modulo del 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();
}
}
Modulo Consumer:
module com.example.consumer {
requires com.example.locator;
}
Il consumer non dichiara uses perché non invoca direttamente ServiceLoader.
39.1.8 Schema Sequenziale di Invocazione
Sequenza di esecuzione:
- Il
ConsumerinvocaGreetingLocator.getService() - Il
Service LocatorinvocaServiceLoader.load(...) - Il
ServiceLoaderconsulta il grafo dei moduli - Il sistema individua i moduli che dichiarano
provides - Viene istanziata l’implementazione del
Provider - L’istanza viene restituita al
Consumer
Schema sequenziale:
Consumer
│
│ 1. getService()
▼
Service Locator
│
│ 2. ServiceLoader.load()
▼
ServiceLoader
│
│ 3. Risoluzione provider
▼
Provider Implementation
│
│ 4. Istanza restituita
▼
Consumer
39.1.9 Tabella Riassuntiva dei Componenti
| Componente | Ruolo | exports | requires | uses | provides |
|---|---|---|---|---|---|
| SPI | Definisce contratto | ✅ | ❌ | ❌ | ❌ |
| Provider | Implementa servizio | ❌ | ✅ | ❌ | ✅ |
| Service Locator | Esegue discovery | (opzionale) | ✅ | ✅ | ❌ |
| Consumer | Usa il servizio | ❌ | ✅ | ❌ | ❌ |