La Dependency Injection in Spring

Forse avrete sentito parlare di Dependency Injection o Inversion of Control. Due concetti che in realtà non sono esattamente sinonimi, ma che spesso, erroneamente o per semplicità, vengono utilizzati come tali.

Spring framework è un esempio lampante di come questi paradigmi possano essere applicati in maniera estremamente efficace, favorendo flessibilità e riutilizzo del codice.

In questo articolo ne parliamo un po’…

DI o IoC?

Esiste una sottile differenza tra Dependency Injection e Inversion of Control, tanto sottile da generare spesso confusione.

Cerchiamo dunque di far luce sui due concetti; di seguito ne do una mia personalissima definizione.

Inversion of Control (o IoC), un concetto piuttosto ampio che descrive un approccio tipico dei moderni framework: non sono più i componenti custom del progetto a richiamare gli elementi del framework, ma, viceversa, è il framework a ricercare i componenti specifici dell’applicazione e integrarli.

In questo, dunque, consiste la famosa inversione di controllo di cui tanto si parla. Il framework non è più un monolite che espone delle funzionalità predefinite, ma è un nucleo estendibile tramite logiche personalizzate, in maniera modulare.

Dipendency Injection (o DI) è invece il nome che si dà ad una delle diverse implementazioni che la Inversion of Control può avere. Per intenderci, Spring implementa la IoC tramite Dependency Injection.

La DI prevede che tutti gli oggetti all’interno della nostra applicazione accettino le dipendenze, ovvero gli (altri) oggetti di cui hanno bisogno, tramite costruttore o metodi setter. Non sono quindi gli stessi oggetti a creare le proprie dipendenze, ma esse vengono iniettate dall’esterno.

Com’è facilmente intuibile, applicare la DI non richiede necessariamente l’utilizzo di un framework. Disaccoppiare la creazione delle dipendenze dagli oggetti che le richiedono è una buona pratica di sviluppo che, in generale, andrebbe applicata quanto più possibile.

Spring IoC container

Spring ci facilita ulteriormente la vita creando e iniettando (al posto nostro) le dipendenze necessarie alle classi della nostra applicazione. Questo avviene sia per i componenti del framework, sia per gli oggetti da noi definiti. Insomma, uno spettacolo! :-)

L’ecosistema all’interno del quale le applicazioni Spring vivono viene definito IoC container. Lo IoC container si occupa di istanziare gli oggetti (beans) dichiarati nel progetto e di reperire e iniettare tutte le dipendenze ad essi associate. Tali dipendenze possono essere componenti del framework o altri bean dichiarati nel contesto applicativo.

Dichiarazione dei componenti

La dichiarazione dei bean, come in generale la configurazione di un’applicazione Spring, può avvenire in tre modi differenti: tramite file XML, codice (configuration class), oppure Java annotations.

Personalmente trovo molto efficace l’utilizzo combinato di annotazioni e classi di configurazione, per cui nel resto dell’articolo mi limiterò a parlare solamente di queste due modalità di configurazione.

L’annotazione @Component

Prendiamo in esame un ipotetico progetto Spring Boot. Abbiamo già visto in un precedente post come l’annotazione @SpringBootApplication, applicata alla main class del nostro progetto, abiliti una serie di funzionalità e configurazioni di default.

Tra le funzioni principali c’è il component scanning, ovvero la scansione del root package del progetto alla ricerca di tutte le classi marcate come componenti.

Fin qui, tutto abbastanza chiaro. Ma come facciamo a definire quali classi sono dei componenti?

Facile! Tramite l’annotazione @Component:

@Component
public class ExampleComponent { ... }

All’avvio dell’applicazione, Spring eseguirà la scansione dei componenti. Tutte le classi annotate come @Component verranno istanziate e registrate nell’ApplicationContext (l’interfaccia che rappresenta lo IoC container). Le istanze create saranno dunque disponibili per essere iniettate in altri bean, in base alle dipendenze dichiarate.

In realtà Spring prevede diverse annotazioni utili a definire i componenti di un progetto, di cui la succitata @Component rappresenta la più generica e versatile. Classicamente però, un’applicazione web basata su Spring (e su Spring MVC), prevede l’utilizzo di tre categorie principali di componenti: Controllers, Services e Repositories. Esaminiamoli brevemente:

  • Controller: si occupano di gestire le interazioni tra utenti e logica di business. Ricevono comandi ed espongono le informazioni in modo da essere comprensibili per l’utente o utilizzabili da altri sistemi. L’annotazione che identifica questo tipo di componente è @Controller.

  • Service: in un progetto Spring, la logica di business è gestita tramite oggetti di tipo Service. Questa tipologia di componenti è identificata dall’annotazione @Service ed ha la funzione di elaborare i dati e di fornirli ai Controller per essere esposti verso il client. Allo stesso tempo, le informazioni da salvare vengono inviate allo strato di accesso ai dati.

  • Repository: è l’ultima classe di componenti che possiamo trovare in un progetto Spring. Si tratta degli oggetti dedicati al recupero dei dati da una generica sorgente (DB, file, REST service). Si tratta di un componente fondamentale a cui Spring ha dedicato un intero progetto spin-off: Spring Data. Le classi che si occupano dell’accesso ai dati sono identificate dall’annotazione @Repository.

L’annotazione @Bean

Esiste un altro modo in cui possiamo dichiarare componenti per la nostra applicazione, utile quando non possiamo avvalerci del component scan.

In tal caso dovremo dichiarare esplicitamente i nostri oggetti tramite l’annotazione @Bean:

@Configuration
public class ExampleBeanConfig {

  @Bean
  public ExampleComponent myComponent {
    return new ExampleComponent();
  }

  // ...

}

L’annotazione @Bean può essere utilizzata all’interno di una classe di configurazione per annotare un metodo che funge da factory per il nostro bean. L’oggetto restituito dal metodo verrà quindi preso in carico dallo IoC container.

Di default, tutti gli oggetti istanziati dal container sono disponibili come singleton; all’interno del nostro progetto avremo quindi una sola istanza di ogni bean. Questo comportamento, ovviamente, può essere configurato diversamente in base alle esigenze.

La parola magica: Autowired

Una volta stabilito come dichiarare i nostri bean all’interno del progetto, è necessario comprendere come i singoli pezzi possono essere “incastrati” nel modo giusto.

Come facciamo, dunque, a dire a Spring che una certa dipendenza dev’essere recuperata dal container ed assegnata ad uno specifico oggetto?

Lo facciamo tramite un’altra magica annotazione: @Autowired.

L’annotazione @Autowired può essere applicata a diversi elementi della nostra classe: ad un campo, un metodo o un costruttore. In ogni caso il suo compito è quello di indicare a Spring quali sono le dipendenze richieste da un determinato oggetto. Queste vengono ricercate tra le istanze presenti nello IoC container e, se presenti, vengono iniettate mediante il costruttore o il metodo annotato.

Ecco un esempio:

@Component
public class MyClass {

  private MyDependency myDependency;

  @Autowired
  public MyClass(MyDependency myDependency) {
    this.myDependency = myDependency;
  }

  // ...

}

Il bean di tipo MyClass necessita di un oggetto di tipo MyDependency per essere “costruito”. Tale oggetto verrà iniettato tramite il costruttore. A sua volta anche l’istanza di MyClass, una volta creata, sarà disponibile per soddisfare ulteriori dipendenze.

Piccola nota L’autowiring delle dipendenze funziona solamente per i componenti istanziati dal framework. Questo vuol dire che se creiamo un oggetto tramite operatore new, anche se la sua classe è annotata come @Component o simili, l’autowiring non verrà scatenato e le sue dipendenze non verranno popolate.

In conclusione

Se siete stati abbastanza coraggiosi da leggere fin qui, vi meritate innanzitutto un caloroso ringraziamento :-)

Spero di aver reso un quadro piuttosto chiaro di come la Dependency Injection possa essere sfruttata efficacemente all’interno di un progetto Spring e di come trarre vantaggio dall’utilizzo delle Java annotation per la dichiarazione dei bean.

Per una descrizione dettagliata dello IoC container di Spring, vi rimando alla documentazione ufficiale.

Buon lavoro e alla prossima,

David