
Corso di Java Spring Framework #8: modalità di Dependency Injection
La Dependency Injection rappresenta il meccanismo attraverso cui vengono fornite le dipendenze richieste da un oggetto.
Si tratta di una possibile implementazione dell'Inversion of Control (IoC) con cui si trasferisce al framework l'onere di creare e gestire il ciclo di vita delle dipendenze stesse.
L'implementazione può avvenire in diverse modalità:
- tramite attributo (field-based injection).
- tramite costruttore (constructor-based injection)
- tramite metodo setter (setter-based injection)
In tutti i casi si utilizza l'annotazione @Autowired
in corrispondenza del "punto" in cui si intende effettuare l'injection.
Field Injection
Si tratta della modalità più semplice in quanto la dipendenza viene iniettata direttamente in un attributo annotato con @Autowired
.
Tuttavia è ormai è deprecata in favore delle altre due soluzioni che vedremo di seguito, sebbene sia quella più semplice da implementare a prima vista e che non richiede del codice aggiuntivo.
@Component
public class Car {
@Autowired
private Engine engine;
public Car() {
}
public Engine getEngine() {
return engine;
}
public void start() {
engine.turnOn();
}
}
Vediamo quali sono le principali critiche sollevate:
- si crea un accoppiamento forte con il framework Spring perchè solo attraverso la reflection è possibile iniettare il valore nell'attributo quando questo è privato e non accessibile tramite setter. Questo aspetto diventa fondamentale durante gli unit test quando spesso si ricorre agli oggetti mock per sostituire dipendenze e servizi esterni, operando in isolamento
- è impossibile utilizzarla con attributi final (immutabili) che possono essere valorizzati solo in fase di inizializzazione dell'oggetto (quindi esclusivamente con la constructor injection) e non più modificabili. Tutto ciò impatta sulla sicurezza dello stato dell'oggetto stesso
- è molto semplice violare il principio della singola responsabilità (come accennato in precedenza) in quanto si può aggiungere un qualsiasi numero di dipendenze senza rendersi effettivamente conto di tale situazione, a differenza di quanto può accadere con un costruttore troppo "affollato".
Per ovviare a tutte queste criticità abbiamo a disposizione una differente soluzione.
Constructor Injection
In questo caso le dipendenze richieste dalla classe sono fornite come parametri del costruttore:
@Component
public class Car {
private Engine engine;
public Car() {
}
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public Engine getEngine() {
return engine;
}
public void start() {
engine.turnOn();
}
}
N.B. Prima della versione 4.3 di Spring era obbligatorio l'utilizzo dell'annotazione @Autowired. Ora è opzionale se la classe dispone di un unico costruttore. In caso contrario almeno uno di essi deve essere annotato.
In generale l'injection basata su costruttore è preferibile perchè rende disponibili tutte le dipendenze in fase di inizializzazione, ovvero quando si crea un oggetto attraverso l'invocazione del costruttore stesso.
Quindi per le dipendenze obbligatorie resta la soluzione più conveniente perchè si ha la garanzia che vengano correttamente predisposte evitando a runtime le pericolose NullPointerException
e senza dover scrivere ulteriore codice per effettuare questo genere di verifiche.
Questo discorso è applicabile anche al testing perchè consente di effettuare semplicemente l'injection di oggetti mock quando necessario.
Inoltre se il costruttore ha molti parametri è facile individuare possibili errori di progettazione: ad esempio una classe che svolge troppi compiti violando il principio della singola responsabilità.
Infine con la injection attraverso il costruttore si può ottenere una sorta di immutabilità in quanto l'unico punto di injection è in fase di creazione dell'oggetto e se si marcano come final
gli attributi inizializzati dal costruttore non sarà più possibile mdificarli in seguito.
Ma se alcune dipendenze fossero opzionali? C'è una soluzione anche per questo.
Setter Injection
In questo caso le dipendenze sono passate come parametri di un metodo setter a cui si aggiunge sempre l'annotazione @Autowired
.
@Component
public class Car {
private Engine engine;
public Car() {
}
public Engine getEngine() {
return engine;
}
@Autowired // @Autowired(required=false) se opzionale
public void setEngine(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
}
Per rendere le dipendenze opzionali si può aggiungere l'attributo required=false
.
Questa soluzione non è applicabile all'injection tramite costruttore perchè non consente di selezionare specifiche dipendenze.
Ricapitolando, con la dependency injection le classi dovrebbero dichiarare chiaramente le dipendenze di cui necessitano specificando quelle obbligatorie all'interno del costruttore e quelle opzionali attraverso i metodi setter. Al contrario la field injection andrebbe riservata solo a scopi didattici, per fare delle prove e mai in produzione.
[VIDEO]
[LINKS]
- Corso completo di Java Spring Framework - Indice argomenti
- Tutorial di Spring Boot - Indice argomenti
- Codice su GitHub
- Flashcards Anki per lo studio
- Libri consigliati
`