Dependency Injection
Inversion of Control
Nella programmazione classica in Java (e nei linguaggi ad oggetti) quando una classe A ha bisogno di un oggetto della classe B, crea una istanza della la classe B con la parola chiave new. Questo meccanismo è molto chiaro ma presenta alcune problematiche:
- anche l’oggetto della classe B potrebbe avere bisogno un oggetto della classe C, che a sua volta deve essere creato con new. E la dipendenza potrebbe continuare verso un oggetto della classe D, e così via.
- il consumatore (cioè l’oggetto della classe A) deve conoscere l’esistenza della classe B. Se per esigenze di progetto la classe B viene sostituita da un’altra classe simile, bisogna riscrivere anche la classe A.
Nei progetti più grandi questo meccanismo può diventare enormemente complicato, costringendo i programmatori a riscrivere interi pezzi di applicazione a seguito di una modifica.
Per questo nel corso degli anni sono state proposte diverse soluzioni (come le Factories e le Abstract Factories) che cercano di demandare il problema della creazione degli oggetti a classi indipendenti, ma questa strada porta comunque a creare progetti con ancora più classi e comunque una complessità non accettabile.
La soluzione migliore, ormai adottata oggi, invece segue un principio differente, chiamato “Inversion of Control”.
L’Inversion of Control è un principio di progettazione delle applicazioni che consiste nel lasciare il controllo della creazione degli oggetti ad un tool esterno, di solito presente in una libreria o un framework. Il programmatore quindi si concentra solo sulle classi e le loro gerarchie, così come le classi che le utilizzano, e delega a questo strumento il compito di creare gli oggetti effettivi e a risolvere le dipendenze.
Dependency Injection
La Dependency Injection è un tipo di Inversion of Control, che viene utilizzato dal framework Spring, tramite una classe speciale, che si chiama ApplicationContext.
Questa classe analizza automaticamente le classi del progetto, opportunamente annotate con appositi marcatori (vedi sotto) e crea una tabella per gestire tutte le dipendenze.
Quando la classe A richiede la classe B, l’ApplicationContext analizza le dipendenze e crea una istanza di B e la fornisce ad A, senza usare new e senza usare altri strumenti.
La forza dell’ApplicationContext è che può generare istanze di classi concrete anche a partire da dipendenze da interfacce.
Ad esempio se la classe Autista ha bisogno dell’interfaccia AutomobileInterface, e questa interfaccia è implementata dalla classe FiatPunto, non è necessario che Autista conosca questa classe. Pensa a tutto L’ApplicationContext, che individua da solo la classe FiatPunto, e ne fornisce una istanza ad Autista.
Vediamo ora come funziona.
Bean
Creiamo un nuovo progetto SpringBoot con Initializr, usiamo come package it.cipiaceinfo e come nome Artifact il nome “ditest”.
Creiamo ora questa interfaccia.
package it.cipiaceinfo.ditest;
public interface DispositivoLuminoso {
String illumina();
}
Per definire le classi che sono gestite dall’Application Context, queste sono annotate con la keyword @Bean o da sue specializzazioni, come @Component, che definisce un componente applicativo Springboot. Creiamo quindi una classe @Component che implementa DispositivoLuminoso:
package it.cipiaceinfo.ditest;
import org.springframework.stereotype.Component;
@Component
public class Lampadina implements DispositivoLuminoso {
@Override
public String illumina() {
return "Lampadina ad incandescenza";
}
}Un @Component è un @Bean, e con questa annotazione indichiamo a SpringBoot che dovrà essere usato dal Dependency Injector.
Creiamo ora la classe che lo usa:
package it.cipiaceinfo.ditest;
import org.springframework.stereotype.Component;
@Component
public class Interruttore {
private final DispositivoLuminoso dispositivoLuminoso;
public Interruttore(DispositivoLuminoso dispositivoLuminoso) {
// Spring ha iniettato la lampadina qui
this.dispositivoLuminoso = dispositivoLuminoso;
}
public void premi() {
System.out.println(this.dispositivoLuminoso.illumina());
}
}La classe Interruttore (anch’essa è un @Component) non crea nessun DispositivoLuminoso. Anzi, non sa nemmeno quale dispositivo luminoso le verrà passato. Sarà il Dependency Injector a scegliere l’istanza della classe adatta (che nel nostro caso è Lampadina).
A questo punto andiamo a vedere la classe DItestApplication, punto di ingresso del progetto:
package it.cipiaceinfo.ditest;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DitestApplication {
public static void main(String[] args) {
SpringApplication.run(DitestApplication.class, args);
}
@Bean
public CommandLineRunner esegui(Interruttore interruttore) {
return args -> {
System.out.println("--- L'applicazione è partita! ---");
interruttore.premi(); // Spring ha già iniettato la lampadina qui
};
}
}
Nota: SpringBoot è progettato per realizzare Web Application e Web Service, quindi di base non ha una interfaccia “a terminale”. Tuttavia siccome in questo esempio stiamo testando la Dependency Injection, e non facendo un vero Web Service, creiamo un metodo in Application che consente di mostrare l’output sul terminale, tramite una istanza dell’interfaccia CommandLineRunner:
@Bean
public CommandLineRunner esegui(Interruttore interruttore) {
return args -> {
// Spring ha già iniettato l'interruttore qui
interruttore.premi();
};
}Nel corpo della funzione (che marchiamo come @Bean per dire a Spring che stiamo usando il Dependency Injector) possiamo vedere che Spring inietta l’interruttore. Nel codice dell’interruttore (vedi sopra) che quando l’interruttore viene creato, Spring inietta la lampadina, che è la classe che trova che implementa DispositivoLuminoso.
Il sistema risolve automaticamente le dipendenze, senza chiederci di scrivere il codice per farlo.
Le annotazioni di Spring
Spring prevede questa gerarchia di annotazioni:
- @Bean: annotazione base, usata raramente (di solito nei metodi)
- @Component: indica un generico componente di un WebService
- @Service: indica un @Component che si occupa della business logic
- @Repository: indica un @Component che si occupa della persistenza su database
- @Controller: indica un @Component che definisce le chiamate al WebService (le singole URL sono associate ad uno specifico servizio).
- @Component: indica un generico componente di un WebService
Il sistema delle annotazioni è in realtà molto più vasto, e si applica in molti ambiti, per aggiungere funzionalità alle classi e ai metodi, e ridurre la quantità di codice da scrivere. Non è utilizzato solo da Spring ma anche da molte librerie della specifica Java EE, come JPA, che vedremo nella prossima lezione.
Esercizi
- Implementare il codice sopra indicato.
- Creare un nuovo progetto, e realizzare al suo interno:
- una interfaccia Saluto con metodo saluta() che restituisce una Stringa.
- una classe @Component SalutoItaliano che implementa l’interfaccia restituendo un saluto italiano (nel metodo saluta() ).
- una classe @Component Messaggio che ha una proprietà SalutoItaliano che ha un attrivuto di tipo Saluto ed il metodo inviaSaluto() che stampa il risultato di saluta().
- Completare la classe Application con il CommandLineRunner che esegue inviaSaluto() da una istanza di Messaggio.
- come si può vedere il comportamento è del tutto simile a quello dell’esempio.
