Introduzione
Non molto tempo fa i parametri di configurazione di un software (java) erano rigidamente codificati (hard-coded) al suo interno, successivamente furono esternalizzati in file di properties (semplici file di testo), in modo da non dover ricompilare il codice a seguito di modifiche nella configurazione, ed infine, molto grazie a Spring, da semplici file di testo si è passato all’utilizzo di file XML. L’introduzione dell’annotazione @Configuration
in Spring 3 per la configurazione del container IoC sembra quindi un passo indietro all’approccio iniziale.
Il component scanning e l’autowiring con annotation hanno reso Spring molto simile al modo in cui la specifica J2EE gestisce la Dependency Injection, quindi l’esternalizzazione con l’uso dell’XML rimaneva forse la sola caratteristica distintiva, anche perché consentiva di mantenere la configurazione in pochi file piuttosto che diffonderla all’interno del codice. L’utilizzo dell’XML comporta però diversi svantaggi che spesso vengono sottovalutati:
- Non è type-safe, quindi un qualsiasi errore nel file (anche di semplice battitura) è rilevato esclusivamente allo startup dell’
ApplicationContext
di Spring. Questo comporta inutili ricicli che rallentano lo sviluppo, soprattutto quando si tratta di applicazioni web che devono essere rideployate. - L’XML è verboso al punto da divenire molto grandi in applicazioni mediamente complesse, per cui si è costretti a suddividere la configurazione in più file.
- Non è possibile navigare nei vari file di configurazione e se si desidera trovare dove un determinato bean viene definito è necessario eseguire una ricerca full-text.
Entrambi gli approcci quindi hanno vantaggi e svantaggi, però riportare la configurazione nel codice ha diverse implicazioni interessanti:
- Tutti gli IDE supportano il type-checking, il code completion, il refactoring e la ricerca delle referenze nel codice.
- Per uno sviluppatore java è molto più semplice codificare concetti come: creazione di bean, inizializzazione, etc. in java piuttosto che con inventare metodi complicati in XML.
- l’uso di design pattern specifici possono rendere il codice (di configurazione) chiaro e leggibile.
JavaConfig
si candida quindi come una valida alternativa sia all’uso dell’XML che all’autowiring. Esso infatti unisce il vantaggio del disaccoppiamento dell’XML con il compile-time check di Java. In un certo senso JavaConfig
può essere visto come l’equivalente di un file XML ma codificato in linguaggio Java.
Configurazione
L’annotazione principale di Spring JavaConfig è @Configuration
. Questa indica al container che la classe contiene elementi di configurazione, che sostanzialmente si concretizzano in metodi annotati con @Bean
, i quali definiscono la logica di creazione, configurazione e inizializzazione degli oggetti che verranno gestiti dal contenitore Spring IoC. Vediamo un primo semplice esempio di configurazione:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Configuration public class ApplicationConfig { @Primary @Bean public TestBean testBean() { return new TestBean( "Test Param" ); } @Bean( name = {"bean1","bean2"}, initMethod="init", destroyMethod="destroy" ) public TestBean otherBean() { return new TestBean( "Other bean" ); } } |
Quanto JavaConfig
si “imbatte” in questa classe, annotata con @Configuration
, ricerca tutti i metodi annotati con @Bean
, li esegue e registra i valori restituiti nel container. Di default il nome del bean generato coincide con quello del metodo che lo genera, come nel caso testBean
dell’esempio. Questo comportamento può essere ridefinito utilizzando il parametro name
dell’annotazione @Bean
, anche indicando diversi alias, come nel caso dei bean bean1
e bean2
dell’esempio.
L’annotazione @Primary
è utilizzata per risolvere l’ambiguità nel caso in cui dal contesto si tenti di recuperare un bean di tipo TestBean
che viene generato da entrambe i metodi della classe.
L’annotazione @Bean
corrisponde in tutto e per tutto al tag <bean>
dell’XML di Spring e di fatto supporta molti degli attributi offerti da tale tag come: init-method, destroy-method, autowiring, lazy-init, dependency-check, depends-on e scope. L’esempio mostra l’uso delle proprietà initMethod
e destroyMethod
, per la generazione dei bean 1 e 2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class TestBean { private String pram = null; public TestBean( String param ) { this.pram = param; } public String getParam() { return this.pram; } public void init() { System.out.println( "I'm initialized"); } public void destroy() { System.out.println( "I'm destroyed"); } } |
L’ispezione della classe di configurazione avviene utilizzando le classi di bootstrap AnnotationConfigApplicationContext
o AnnotationConfigWebApplicationContext
per applicazioni web. Il seguente codice una possibile classe di avvio del progetto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class MainApp { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( ApplicationConfig.class ); TestBean teatBean = context.getBean( TestBean.class ); System.out.println( "testBean: " + teatBean.getParam() ); TestBean bean1 = (TestBean) context.getBean( "bean1" ); System.out.println( "bean1: " + bean1.getParam() ); TestBean bean2 = (TestBean) context.getBean( "bean2" ); System.out.println( "bean2: " + bean2.getParam() ); try { TestBean otherBean = (TestBean) context.getBean( "otherBean" ); System.out.println( "otherBean: " + otherBean.getParam() ); } catch (BeansException e) { System.err.println( e.getMessage() ); } context.close(); } } |
1 2 3 4 5 6 7 8 9 10 |
set 15, 2017 12:42:42 PM org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh INFORMAZIONI: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@37bba400: startup date [Fri Sep 15 12:42:42 CEST 2017]; root of context hierarchy I'm initialized testBean: Test Param bean1: Other bean bean2: Other bean No bean named 'otherBean' is defined set 15, 2017 12:42:43 PM org.springframework.context.annotation.AnnotationConfigApplicationContext doClose INFORMAZIONI: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@37bba400: startup date [Fri Sep 15 12:42:42 CEST 2017]; root of context hierarchy I'm destroyed |
Ciclo di Vita
Nell’esempio precedente abbiamo visto come, attraverso gli attributi initMethod
e destroyMethod
sia possibile interagire con il ciclo di vita dei bean. JavaConfig, come il core di Framework, supporta inoltre l’utilizzo delle “Common Annotations” definite nella specificaJSR-250. Per esempio:
1 2 3 4 5 6 7 8 9 10 11 |
public class LifecycleBean { @PostConstruct public void init() { System.out.println( "PostConstruct executed" ); } @PreDestroy public void cleanapp() { System.out.println( "PreDestroy executed" ); } } |
JavaConfig
supporta inoltre l’uso delle interfacce di callback definite nel framework Spring quali InitializingBean
, DisposableBean
o Lifecycle
i cui rispettivi metodi sono invocati dal container in base al loro Javadoc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class LifecycleBean implements InitializingBean, DisposableBean, Lifecycle { // from InitializingBean public void afterPropertiesSet() throws Exception { System.out.println( "afterPropertiesSet executed" ); } // from DisposableBean public void destroy() throws Exception { System.out.println( "destroy executed" ); } // from Lifecycle public void start() { System.out.println( "start executed" ); } // from Lifecycle public void stop() { System.out.println( "stop executed" ); } // from Lifecycle public boolean isRunning() { System.out.println( "isRunning executed" ); return true; } } |
Bean Scope
Come nel caso dell’utilizzo del file XML, anche per i bean definiti attraverso l’annotazione @Bean
è possibile controllare lo scope. Allo scopo possiamo utilizzare l’annotazione @Scop
e specificando lo scope di interesse. Ovviamente sono gestiti dal framework gli scope standard del core di Spring: singleton
e prototype
, ma anche quelli legati alle request HTTP e gestiti in Spring MVC: request
e session
.
1 2 3 4 5 |
@Bean @Scope( scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE ) public ScopedBean scopedBean() { return new ScopedBean(); } |
Codice Sorgente
Il codice sorgente completo di tutti gli esempi presentati può essere scaricato qui javaconfig parte 1.