Corso di Java Spring Framework #5: Bean (seconda parte) - Esempi pratici
Proseguiamo il discorso intrapreso nello scorso articolo relativo ai bean, analizzando alcuni esempi pratici.
Ricordiamo che i bean sono degli oggetti che vengono istanziati automaticamente da Spring e il cui ciclo di vita è gestito direttamente dal framework.
Per la loro creazione sono disponibili due modalità
-
la prima consiste nel creare una classe annotata con
@Component
o con una delle altre annotazioni stereotipo (@Service
,@Controller
,@Repository
); -
l'altra nel creare una classe di configurazione (usando l'annotazione
@Configuration
) all'interno della quale inserire una serie di metodi che istanziano le classi che vogliamo siano gestite come bean.
Tali metodi sono annotati con @Bean
così da essere gestiti all'interno dell'IoC container.
Il progetto di esempio è basato su Maven e include tra le dipendenze spring-context così da vedere direttamente come funziona il meccanismo di creazione dei bean.
Infatti Spring Boot (di cui parleremo in dettaglio nei prossimi articoli) se da un lato ci semplifica la vita, dall'altro nasconde molti dettagli che sarebbe opportuno conoscere per sfruttare al meglio la logica e le funzionalità del framework.
Mettiamoci quindi all'opera.
Per prima cosa occore creare la classe a partire dalla quale verranno istanziati i bean.
Nel nostro esempio si tratta della classe Student
, una banalissima classe Pojo dotata di costruttori, getter e setter.
public class Student {
private String firstName;
private String lastName;
public Student() {
super();
}
public Student(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
L'uso di queste classi è uno dei punti di forza di Spring rispetto al passato e ai ben noti EJB (Enterprise Java Beans) che obbligavano all'implementazione di specifiche interfacce.
Inoltre il codice di queste classi può essere generato automaticamente dagli IDE o da opportune librerie come Lombok grazie ad una manciata di annotazioni.
Vediamo la prima modalità di creazione dei bean.
Dobbiamo creare una classe da annotare con @Configuration
e che in questo caso si chiama BeanConfig
.
@Configuration
public class BeanConfig {
@Bean(name = "student1")
public Student getStudent1() {
return new Student("pinco", "pallino");
}
}
La presenza di tale annotazione è fondamentale per indicare a Spring che al suo interno saranno presenti le definizioni dei bean.
In particolare notiamo il metodo getStudent1()
, anche se il nome di un metodo può essere scelto arbitrariamente.
Il suo compito è semplicemente di restituire un'istanza della classe Student
ed è necessario perchè in sua assenza Spring non andrà a creare il bean anche se la classe è annotata con @Configuration
.
Di default il bean assumerà il nome del metodo che lo istanzia a meno che non sia specificata l'opzione name
(come nel precedente esempio).
Passiamo ora alla classe principale contenente il metodo main()
che rappresenta l'entrypoint di una qualsiasi classe Java.
public class SpringBeansExample {
public static void main(String[] args) {
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfig.class);
}
Per accedere ai bean abbiamo bisogno di interagire con l'Application Context e più precisamente utilizziamo una delle implementazioni di questa interfaccia, AnnotationConfigApplicationContext
, a cui passare la classe BeanConfig.class
.
Esiste anche un'altra modalità di definizione dei bean basata su file XML ma non verrà trattata in questo articolo.
L'uso delle annotazioni rende tutto molto più semplice perchè si lavora direttamente sul codice e non necessita di file di configurazione esterni.
L'istanza dell'Application Context, ctx
, ci serve per poter accedere ai bean creati direttamente da Spring, così da effettuare delle opportune operazioni.
Ma vediamo in dettaglio cosa possiamo fare.
Sono disponibili vari metodi associati al context come quello per richiamare uno specifico bean a partire dal suo nome.
Student s1 = (Student) ctx.getBean("student");
In questo caso dobbiamo effettuare un opportuno casting del tipo restituito (Student
) memorizzando il tutto in una variabile per successive elaborazioni.
In alternativa si può evitare il casting specificando direttamente la classe del bean restituito all'interno del metodo getBean()
Student s1 = ctx.getBean("student", Student.class);
Questo è un tipico esempio di overloading di un metodo.
Un altro metodo molto utile è getBeanDefinitionNames()
con cui possiamo elencare tutti i bean creati dall'applicazione.
for (String beanName : ctx.getBeanDefinitionNames()) {
System.out.println("Bean --->" + beanName);
}
In questo modo possiamo visualizzare anche tutti i bean creati automaticamente dal framework senza alcun nostro intervento diretto e che sono necessari per il suo corretto funzionamento.
Al termine delle operazioni andiamo a chiudere il context con il metodo close()
così da evitare possibili segnalazioni di errore.
Per visualizzare in dettaglio la lista dei bean creati si rimanda al video associato all'articolo.
Nella seconda modalità andiamo ad annotare la classe Student
con @Component
in modo che Spring possa creare un bean all'avvio dell'applicazione, ovvero un'istanza della classe di cui dovrà gestire l'intero ciclo di vita.
Ma questo non è sufficiente affinchè tutto funzioni correttamente.
Infatti dobbiamo annotare la classe principale contenente il metodo main()
con
@Configuration
@ComponentScan
Per quale motivo esattamente?
In primo luogo dobbiamo associare il context ad una classe di configurazione che nel precedente esempio era rappresentata da BeanConfig
.
Ora invece questo compito sarà svolto dalla classe principale grazie alla suddetta annotazione.
Naturalmente occorre aggiornare anche il riferimento a tale classe in
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(SpringBeansExample.class);
In questo modo non sarà più creato il bean student1
definito nella classe BeanConfig
perchè il context è "sganciato" da quest'ultima, ma allo stesso tempo non verrà creato il bean Student
nonostante la classe sia annotata con @Component
.
Questo perchè dobbiamo abilitare il "component scan", ovvero dire a Spring di ricercare tutte le classi componenti a partire da una certa posizione che in questo caso (e in generale di default) corrisponde al package contenente la classe con l'annotazione omonima @ComponentScan
.
Come vedremo Spring Boot semplifica tutto questo perchè aggiunge in automatico l'annotazione @SpringBootApplication
che ingloba altre annotazioni tra cui le precedenti due.
Per ora ci siamo ci siamo fermati al fatto che di questi bean esiste una sola istanza perché le impostazioni di default prevedeno il cosiddetto scope singleton, ma in seguito parleremo in dettaglio di questo argomento e vedremo come disporre di una o più istanze a seconda delle nostre esigenze.