
Corso di Java Spring Framework #13 | Scope dei bean (prototype)
Proseguiamo l'analisi dello scope dei bean occupandoci del prototype che prevede la creazione di una nuova istanza ogni volta che un bean viene referenziato.
In questo modo si evitano tutti quei problemi che abbiamo visto con lo scope singleton e di fatto possiamo lavorare con bean mutabili, ovvero il cui stato può cambiare nel tempo, senza avere tutti i grattacapi legati alla gestione della concorrenza.
Quindi se due o più thread accedono ad uno stesso bean non si verifica più la race condition.
L'utilizzo di questo scope va indicato esplicitamente in corrispondenza della definizione del bean mediante l'annotazione @Scope(BeanDefinitions.SCOPE_PROTOTYPE)
che utilizza una enumerazione specifica.
Vediamo quindi un esempio pratico tratto dal volume "Spring Start Here" di cui trovate la video recensione.
L'autore ipotizza uno scenario in cui c'è una classe CommentProcessor
che si occupa appunto di processare dei commenti, ad esempio quelli di un classico blog.
In pratica si tratta di una classe dotata dei soliti metodi getter e setter a cui si affiancano process()
e validate()
per effettuare l'elaborazione e la validazione dei commenti.
Inoltre abbiamo una classe di supporto annotata con @Service
con un metodo sendComment(Comment c)
all'interno del quale viene istanziato il CommentProcessor
e vengono effettuate le elaborazioni a livello di questo commento, per poi visualizzarlo.
La prima osservazione da fare è che la classe CommentProcessor
non è gestita direttamente da Spring e questo potrebbe apparire strano nel contesto del framework.
Tuttavia bisogna precisare che non sempre le classi devono "produrre" dei bean, nel senso che questa gestione da parte di Spring ha senso nel momento in cui ci consente di sfruttare le potenzialità del framework e quindi di arricchire le classi con ulteriori funzionalità.
Questo perchè oltre ai vantaggi ci sono anche degli svantaggi dovuti alla gestione dei bean.
All'avvio dell'applicazione Spring deve effettuare la scansione dei package, individuare i potenziali bean (sfruttando la reflection) e crearli, tenendo presente le dipendenze e quindi l'ordine stesso di creazione.
Tutto ciò determina un incremento dei tempi di startup, senza dimenticare che ogni bean occupa parte della memoria anche se poi non viene realmente utilizzato nel contesto di esecuzione.
In un precedente articolo abbiamo anche parlato dell'annotazione @Lazy
utilizzata per rimandare la creazione di un bean al momento in cui viene effettivamente referenziato.
Nel nostro caso, dal momento che il commento viene processato e visualizzato semplicemente sullo schermo potrebbe non avere molto senso mettere in campo la potenza del framework e dei bean.
Se invece volessimo salvare il commento all'interno di un database potremmo creare una classe CommentRepository
e sfruttare le funzionalità di Spring Data.
A questo punto la classe CommentProcessor
potrà essere annotata con @Component
andando a specificare anche il relativo scope e potrà essere iniettata come dipendenza dove necessario.
Ed ecco che dobbiamo porci una domanda importante: conviene utilizzare lo scope di default singleton (senza dover aggiungere nessuna annotazione) oppure ricorrere al prototype?
Evidentemente bisogna capire esattamente in quale contesto ci troviamo, ovvero se processiamo un singolo commento per volta, prelevato da una coda.
In questa situazione ha senso utilizzare il singleton perché non abbiamo nè elaborazioni nè accessi contemporanei, quindi non c'è nessun vantaggio nell'avere più istanze.
Al contrario, in un contesto reale, l'elaborazione avviene in parallelo lavorando su più commenti e quindi per evitare tutte le problematiche viste in precedenza relative alla race condition conviene utilizzare lo scope prototype.
Tuttavia però dobbiamo fare una modifica a livello della classe CommandService
, ovvero dobbiamo iniettare l'Application Context attraverso la field injection o ancora meglio attraverso il costruttore.
Quindi dobbiamo invocare il bean CommentProcessor
attraverso il metodo getBean()
del context e poi proseguire normalmente con l'elaborazione del commento.
Ed eccoci ad una nuova domanda: ma non è più semplice iniettare direttamente il CommentProcessor
all'interno di CommentService
senza fare tutti questi ulteriori passaggi?
Non ci stiamo complicando la vita in questo modo?
In realtà no e vediamo di capire il perchè. La classe CommentService
, in quanto annotata con @Service
, utilizza lo scope singleton
per cui all'avvio dell'applicazione Spring ne crea un'istanza e inietta tutte le dipendenze richieste.
In particolare inietta un'istanza di CommentProcess
e lo fa solo in quel preciso momento.
A questo punto non chiameremo più direttamente il CommentProcessor
ma lo faremo tramite il metodo sendComment()
di CommentService
per cui il bean sarà sempre quello istanziato all'avvio dell'applicazione.
Se più thread invocano CommentService
per elaborare in parallelo più commenti, la stessa classe, non essendo istanziata ad ogni invocazione perchè singleton, lavorerà sempre con la stessa istanza di CommentProcess
.
Questo nonostante il bean abbia alla radice lo scope prototype, semplicemente perchè è stato iniettato all'interno di una classe singleton che di fatto annulla tutti i vantaggi di questa tipologie di scope.
Praticamente ci troviamo di fronte ai problemi di race condtion.
Per questo motivo non possiamo ricorrere all'injection diretta e passare attraverso la chiamata all'Application Context.
Il metodo getBean()
contatta in quel momento l'Application Context e chiede di restituirgli un bean CommentProcess
per cui, essendo lo scope impostato a prototype, Spring crea una nuova istanza e la restituisce al CommentService
.
Anche se lavoriamo in parallelo sull'unica istanza della classe CommentService
avremo sempre nuove istanze di CommentProcessor
create al momento dell'invocazione del metodo getBean()
.
Ma allora quando conviene usare lo scope singleton?
In effetti è molto diffuso e non a caso è lo scope di default.
Se l'applicazione viene realizzata ex novo si può studiare a livello di progetto quando e come conviene ricorrere allo scope prototype.
Ovviamente bisogna tenere presente se il bean abbia o meno uno stato mutabile nel tempo.
Se in fase di progetto possiamo introdurre delle classi immutabili allora conviene usare il singleton, così da liberarci di tutta una serie di problemi.
In caso contrario dobbiamo optare per il prototype per i motivi che abbiamo illustrato in precedenza.
Se invece stiamo lavorando su un'applicazione legacy, che stiamo cercando di migrare a Spring e ci troviamo di fronte ad una serie di classi mutabili che non possiamo modificare rapidamente (ad esempio per vincoli di consegna), dobbiamo per forza utilizzare lo scope prototype così da evitare tutte le problematiche di cui abbiamo discusso.
Come al solito occorre fare una valutazione dettagliata della situazione avendo chiaro il funzionamento dei vari scope.
In seguito ci occuperemo anche di quelli relativi prettamente alle applicazioni web ovvero di request, session e application.
[VIDEO YOUTUBE]
[LINKS]