Corso di Java Spring Framework #10 | Dipendenze circolari
Questa volta ci occupiamo delle dipendenze circolari che possono presentarsi durante la fase di creazione dei bean in Spring.
Il framework cerca di crearli secondo un ordine che consenta a tutti di essere immediatamente operativi.
Per esempio supponiamo di avere 3 bean con le seguenti dipendenze
BeanA → BeanB → BeanC
In questo caso la creazione non può partire nè da BeanA nè da BeanB, perchè ciascuno di essi necessita di un ulteriore bean per il suo funzionamento.
L'unico libero da vincoli è BeanC e quindi sarà quest'ultimo ad essere creato per primo, seguito da BeanB e infine da BeanA.
Se però BeanC dovesse dipendere a sua volta da BeanA si avrebbe dipendenza circolare e, non potendo più stabilire un ordine corretto di creazione, Spring solleverebbe a runtime l'eccezione BeanCurrentlyInCreationException
.
Il problema delle dipendenze circolari è molto importante perchè a partire dalla versione 2.6.7 di Spring non sono più ammesse di default, sebbene ci sia la possibilità di specificare un'opportuna proprietà all'interno di application.yaml (o nell'omologo file delle properties)
spring:
main:
allow-circular-references: true
così da forzarne il loro utilizzo, anche se sarebbe meglio evitarlo.
Ovviamente questa situazione si pone con la constructor injection in quanto le dipendenze devono essere iniettate nel momento della creazione dei bean.
Negli altri casi tutto fila liscio, ma come abbiamo visto in un precedente articolo questa modalità è caldamente consigliata in presenza di dipendenze obbligatorie.
Vediamo allora quali sono le possibili soluzioni.
Eliminazione delle dipendenze circolari
La presenza di queste dipendenze è di solito il sintomo di una cattiva progettazione delle classi, nel senso che con molta probabilità sono stati violati i principi SOLID ed in particolare quello della singola responsabilità.
In sostanza qualche classe svolge più di un compito e ciò non andrebbe fatto.
Tuttavia non sempre è possibile modificare il codice per vari motivi, ad esempio perchè si tratta di codice legacy su cui non possiamo mettere le mani. Quindi occorre trovare delle alternative.
Utilizzo dell'annotazione @Lazy
Un modo per "rompere" la dipendenza circolare consiste nell'uso dell'annotazione @Lazy
in corrispondenza di una delle due classi coinvolte, fermo restando l'injection by constructor.
@Component
public class BeanA {
private BeanB beanB;
public BeanA() {}
@Autowired
public BeanA(@Lazy BeanB beanB) {
this.beanB = beanB;
}
}
In questo modo non viene iniettato il bean richiesto ma un proxy opportunamente creato, mentre il bean "completo" sarà iniettato solo quando effettivamente richiesto.
E in genere ciò non avviene al momento della creazione dei bean, risolvendo il problema.
Utilizzo della setter injection
In questo caso la dipendenza non sarà iniettata al momento della creazione del bean, ma in un secondo momento attraverso il setter annotato con @Autowired
.
@Component
public class BeanA {
private BeanB beanB;
public BeanA() {
}
public BeanB getBeanB() {
return beanB;
}
@Autowired
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
Tuttavia questa soluzione implica una modifica dal punto di vista logico.
Infatti l'injection attraverso il costruttore si utilizza per le dipendenze obbligatorie, riservando il setter per quelle opzionali. Quindi dovremo verificare attentamente la corretta inizializzazione ed iniezione delle stesse.
Utilizzo dell'annotazione @PostConstruct
In questo caso utilizziamo l'injection by field della dipendenza e creiamo il metodo init()
per settarla correttamente dopo aver creato il bean.
@Component
public class BeanA {
@Autowired
private BeanB beanB;
@PostConstruct
public init() {
beanB.setBeanB(this);
}
}
Di fatto init()
richiama il setter di BeanB (iniettato nel field privato) e gli assegna come dipendenza l'istanza che sta effettuando la chiamata tramite il riferimento this
.
Quindi occorre valutare tutti i fattori in gioco per fare la scelta più adeguata, preferibilmente a livello di design (se possibile) per rimuovere il problema alla radice.
[VIDEO YOUTUBE]
[LINKS]