Per comprendere a fondo le potenzialità di Spring bisogna prima introdurre i concetti di Inversion of Control (IoC) e Dependency Injection (DI), che pur significando due cose diverse, spesso vengono utilizzati come sinonimi provocando non poca confusione.
L'Inversion of Control è un principio architetturale nato alla fine degli anni ottanta, basato sul concetto di invertire il controllo del flusso di sistema (Control Flow) rispetto alla programmazione tradizionale. Questo principio è molto utilizzato nei framework e ne rappresenta una delle caratteristiche basilari che li distingue dalle API.
Nella programmazione tradizionale la logica di tale flusso è definita esplicitamente dallo sviluppatore, che si occupa tra le altre cose di tutte le operazioni di creazione, inizializzazione ed invocazione dei metodi degli oggetti. IoC invece inverte il control flow facendo in modo che non sia più lo sviluppatore a doversi preoccupare di questi aspetti, ma il framework, che reagendo a qualche "stimolo" se ne occuperà per suo conto. Questo principio è anche conosciuto come Hollywood Principle ("Non chiamarci, ti chiameremo noi").
Come illustrato da Martin Fowler in un suo famoso post del 2004, esistono diverse implementazioni di IoC e la domanda da porsi è quale aspetto del Control Flow stanno invertendo.
Sempre nello stesso post Fowler conia il termine Dependency Injection (DI) per riferirsi ad una specifica implementazione dello IoC rivolta ad invertire il processo di risoluzione delle dipendenze, facendo in modo che queste vengano iniettate dall'esterno. Banalmente, nel caso della programmazione Object Oriented, una classe A
si dice dipendente dalla classe B
se ne usa in qualche punto i servizi offerti.
Perché questo tipo di collaborazione abbia luogo la A
ha diverse alternative: istanziare e inizializzare (attraverso costruttore o metodi setter) la classe B, ottenere un'istanza di B
attraverso una factory oppure effettuare un lookup attraverso un servizio di naming (es JNDI). Ognuno di questi casi implica che nel codice di A sia "scolpita" la logica di risoluzione della dipendenza verso B
.
Per chiarire introduciamo un esempio. Si vuole creare un semplice generatore di report in grado di generare output in formato testuale.
public class TxtReport {
public void generate(String data) {
System.out.println("genera txt report");
}
}
public class ReportGenerator {
TxtReport report = null;
public Report generate(String data) {
report = new TxtReport(); // risoluzione della dipendenza
report.generate(data);
return report;
}
}
Nell'esempio sopra possiamo notare come la classe ReportGenerator
abbia una dipendenza verso TxtReport
e come questa sia risolta nel corpo del metodo generate()
. Questo modo di operare, oltre a vincolare la creazione della dipendenza nel codice limitandone il riuso (cosa succede se in futuro vogliamo generare report in formato HTML?), tende a generare un forte accoppiamento tra le classi.
Il problema risiede nel fatto che senza l'ausilio di un apposito sistema la risoluzione delle dipendenze è ad esclusivo appannaggio delle classi stesse, che dovranno preoccuparsi di creare gli oggetti da cui dipendono o di ottenerne delle reference attraverso operazioni di lookup.