Introduzione
In questo terzo articolo continuiamo la scoperta del framework Spring. In particolare verranno mostrate quelle che sono le modalità e le possibilità che Spring offre per definire bean all’interno del container IoC.
Il container IoC di Spring è disegnato per essere facilmente personalizzabile ed estensibile e permette allo sviluppatore di personalizzane il comportamento attraverso configurazione ed estendendo le sue caratteristiche.
Alla fine di questo articolo ti saranno familiari la maggior parte delle caratteristiche del container IoC di Spring.
Iniziamo con lo scrivere alcune classi che useremo negli esempi che seguiranno. Si tratta di una classe astratta Shape
, di 3 classi concrete, che useremo come “model”, e una classe ShapeService
che farà da “controller”.
package it.italiancoders.demospring3.types; public abstract class Shape { private String color; private int border; public Shape() { this.color = "White"; this.border = 0; } public Shape(String color) { this.color = color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getBorder() { return border; } public void setBorder(int border) { this.border = border; } public abstract void draw(); }
package it.italiancoders.demospring3.types; public class Circle extends Shape { public Circle() {} public Circle(String color) { super(color); } @Override public void draw() { System.out.println("Drow a Circle of color " + getColor() + " and border " + getBorder()); } }
package it.italiancoders.demospring3.types; public class Rectangle extends Shape { public Rectangle() {} public Rectangle(String color) { super(color); } @Override public void draw() { System.out.println("Drow a Rectangle of color " + getColor() + " and border " + getBorder()); } }
package it.italiancoders.demospring3.types; public class Square extends Shape { public Square() {} public Square(String color) { super(color); } @Override public void draw() { System.out.println("Drow a Square of color " + getColor() + " and border " + getBorder()); } }
package it.italiancoders.demospring3.components; import java.util.List; import it.italiancoders.demospring3.types.Shape; public class ShapeService { private List<Shape> shapes; public ShapeService(List<Shape> shapes) { this.shapes = shapes; } public void execute() { shapes.forEach(shape -> shape.draw()); } }
Creazione file di configurazione
Per dichiarare bean all’interno del container IoC Spring è necessario creare un file di configurazione XML che chiameremo app-config.xml. Per
Di seguito un esempio di un file di configurazione.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> ... </beans>
Dichiarazione dei bean
Vediamo i possibili modi che Spring mette a disposizione per dichiarare bean tramite configurazione XML. Ogni definizione di bean deve avere un id
o nome univoco e il nome completo della classe per permettere al container di istanziarlo.
constructor-arg
Dichiariamo quindi il primo bean della classe Rectangle
, assegnando il colore tramite costruttore usando l’elemento constructor-arg
:
<bean name="square" class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg> <value>Blue</value> </constructor-arg> </bean>
L’elemento constructor-arg
può essere utilizzato anche nella forma breve come mostrato di seguito:
<bean name="rectangle" class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg value="Blue" /> </bean>
property
Sempre utilizzando la classe Rectangle
, vediamo un esempio di dichiarazione con assegnazione del colore tramite il metodo setter setColor
utilizzando l’elemento property
.
<bean name="circle" class="it.italiancoders.demospring3.types.Circle"> <property name="color"> <value>Yellow</value> </property> </bean>
Da notare che il valore dell’attributo name
di property
deve corrispondere al nome del metodo setXyz
. Per esempio avendo un metodo setXyz
il valore dell’attributo name
di property
sarà xyz
.
Come per constructor-arg
anche property
può essere utilizzato anche nella forma breve come mostrato di seguito:
<bean name="circle" class="it.italiancoders.demospring3.types.Circle"> <property name="color" value="Yellow" /> </bean>
C’è un’ulteriore modalità di definire proprietà utilizzando lo schema p
come attributo dell’elemento <bean>
.
La configurazione diventa come segue:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="it.italiancoders.demospring3.types.Circle" p:color="Yellow" /> </beans>
Da notare che nel file app-config.xml
è stato aggiunto il namespace xmlns:p="http://www.springframework.org/schema/p
rispetto alla definizione iniziale. Questa modalità può essere utile per ridurre al minimo la verbosità dell’XML da scrivere nella configurazione, ma potrebbe risultare poco leggibile quando le proprietà da settare ad un bean sono parecchie.
Cosa ovvia è che dichiarando un bean senza utilizzare constructor-arg
l’oggetto relativo verrà istanziato utilizzando il costruttore di default, ovvero quello senza argomenti.
E’ possibile utilizzare entrambi i due elementi nella dichiarazione. Di seguito vediamo un esempio in cui viene instanziato un oggetto passando il colore al costruttore e la dimensione del bordo tramite metodo setter.
<bean name="rectangle" class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg value="Blue" /> <property name="border" value="1" /> </bean>
Guardiamo ora il file app-config.xml
con una configurazione completa in cui inseriamo anche la dichiarazione di un altro bean shapeService
a cui verrà passata una lista di tutti i riferimenti ai bean di tipo Shape dichiarati:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="rectangle" class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg value="Blue" /> </bean> <bean name="square" class="it.italiancoders.demospring3.types.Square"> <constructor-arg value="Red" /> </bean> <bean name="circle" class="it.italiancoders.demospring3.types.Circle"> <constructor-arg value="Yellow" /> </bean> <bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService"> <constructor-arg> <list> <ref bean="rectangle" /> <ref bean="square" /> <ref bean="circle" /> </list> </constructor-arg> </bean> </beans>
Vediamo infine la classe DemoSpring3Application
che contiene il metodo main. In questa classe sono presenti le annotation @SpringBootApplication
che inizializza il container Spring, @ImportResource
usata per importare il file di configurazione xml e @Autowired
per dare la dependancy injection del bean shapeService
(questa parte è stata trattata più nel dettaglio in questo articolo).
@SpringBootApplication @ImportResource("classpath:app-config.xml") public class DemoSpring3Application implements ApplicationListener<ApplicationReadyEvent> { @Autowired private ShapeService shapeService; public static void main(String[] args) { SpringApplication.run(DemoSpring3Application.class, args); } @Override public void onApplicationEvent(ApplicationReadyEvent arg0) { shapeService.execute(); } }
L’output ottenuto dall’esecuzione del progetto sarà il seguente:
Drow a Rectangle of color Blue and border 0 Drow a Square of color Red and border 0 Drow a Circle of color Yellow and border 0
Anziché dichiarare il bean shapeService
passandogli una lista di riferimenti ad altri bean avremmo potuto dichiarare i bean direttamente nella lista come inner bean, definendo il file app-config.xml
come segue:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService"> <constructor-arg> <list> <bean class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg value="Blue" /> </bean> <bean class="it.italiancoders.demospring3.types.Square"> <constructor-arg value="Red" /> </bean> <bean class="it.italiancoders.demospring3.types.Circle"> <constructor-arg value="Yellow" /> </bean> </list> </constructor-arg> </bean> </beans>
Volendo è possibile usare le due modalità in modo misto. Questo a sottolineare la flessibilità e le possibilità che Spring offre nella modalità di configurazione.
Dichiarare bean con Collections
La nostra classe ShapeService
ha come attributo una Lista di tipo java.util.List
di oggetti Shape. Oltre ad una List potrebbe essere necessario dichiarare un bean che ha un parametro del costruttore o di un metodo setter di tipo Set, Map o Properties.
Con Spring queste collections possono essere configurate con i tag XML <list>
, <set>
, <map>
e props
.
Set
Ad esempio modificando la nostra classe ShapeService
sostituendo l’attributo List<Shape> shapes
con Set<Shape> shapes
(e di conseguenza il costruttore) in questo modo:
public class ShapeService { private Set<Shape> shapes; public ShapeService(Set<Shape> shapes) { this.shapes = shapes; } ... }
la dichiarazione del bean shapeService
diventa come segue:
<bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService"> <constructor-arg> <set> <bean class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg value="Blue" /> </bean> <bean class="it.italiancoders.demospring3.types.Square"> <constructor-arg value="Red" /> </bean> <bean class="it.italiancoders.demospring3.types.Circle"> <constructor-arg value="Yellow" /> </bean> </set> </constructor-arg> </bean>
o cosi nel caso in cui c’è il desiderio o necessità di fare riferimento a bean già dichiarati:
<bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService"> <constructor-arg> <set> <ref bean="rectangle" /> <ref bean="square" /> <ref bean="circle" /> </set> </constructor-arg> </bean>
Map
L’interfaccia Map è sostanzialmente una tabella che contiene coppie chiave/valore.
Modificando invece la classe ShapeService
sostituendo l’attributo List<Shape> shapes
con Map<String, Shape> shapes
(e anche il costruttore) in questo modo:
public class ShapeService { private Map<String, Shape> shapes; public ShapeService(Map<String, Shape> shapes) { this.shapes = shapes; } ... }
la dichiarazione del bean shapeService
diventa come segue:
<bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService"> <constructor-arg> <map> <entry> <key> <value>Rectangle</value> </key> <bean class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg value="Blue" /> </bean> </entry> <entry> <key> <value>Square</value> </key> <bean class="it.italiancoders.demospring3.types.Square"> <constructor-arg value="Red" /> </bean> </entry> <entry> <key> <value>Circle</value> </key> <bean class="it.italiancoders.demospring3.types.Circle"> <constructor-arg value="Yellow" /> </bean> </entry> </map> </constructor-arg> </bean>
Come avrete già intuito anche in questo caso è utilizzabile l’elemento <ref bean="..." />
o contrazioni utilizzando attributi anziché elementi XML.
Properties
Spring supporta anche la collection java.util.Properties
, che è molto simile a map.
Modificando quindi la classe ShapeService
sostituendo l’attributo List<Shape> shapes
con Properties shapes
(e anche il costruttore) in questo modo:
public class ShapeService { private Properties shapes; public ShapeService(Properties shapes) { this.shapes = shapes; } ... }
la dichiarazione del bean shapeService
diventa come segue:
<bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService"> <constructor-arg> <props> <prop key="rectangle"> <bean class="it.italiancoders.demospring3.types.Rectangle"> <constructor-arg value="Blue" /> </bean> </prop> <prop key="square"> <bean class="it.italiancoders.demospring3.types.Square"> <constructor-arg value="Red" /> </bean> </prop> <prop key="circle"> <bean class="it.italiancoders.demospring3.types.Circle"> <constructor-arg value="Yellow" /> </bean> </prop> </props> </constructor-arg> </bean>
Riferimenti a bean
Alcune volte può tornare utile passare ad un bean un altro bean già dichiarato.
Oltre all’elemento <ref>
utilizzato negli esempi sopra relativi alle collections, è possibile passare riferimenti a bean ad un costruttore o ad un metodo setter.
Riferimento PER Metodo setter
Ipotizziamo di avere la seguente classe:
public class ShapeDrawer { private Shape shape; public ShapeDrawer() {} public ShapeDrawer(Shape shape) { this.shape = shape; } public setShape(Shape shape) { this.shape = shape; } }
potremmo dichiare il bean shapeDrawer
passando tramite metodo setShape
il riferimento al bean rectangle
, definito negli esempi precedenti, in questo modo:
<bean name="shapeDrawer" class="it.italiancoders.demospring3.components.ShapeDrawer"> <property name="shape"> <ref bean="rectangle" /> </property> </bean>
o nella forma breve
<bean name="shapeDrawer" class="it.italiancoders.demospring3.components.ShapeDrawer"> <property name="shape" ref="rectangle" /> </bean>
oppure come già visto utilizzando l’attributo p
<bean name="shapeDrawer" class="it.italiancoders.demospring3.components.ShapeDrawer" p:shape-ref="rectangle" />
Riferimento per argomento del costruttore
Volendo invece dichiarare il bean shapeDrawer
passando come argomento del costruttore il riferimento al bean rectangle
potremmo scrivere la configurazione in questo modo:
<bean name="shapeDrawer" class="it.italiancoders.demospring3.components.ShapeDrawer"> <constructor-arg> <ref bean="rectangle"/> </constructor-arg> </bean>
o nella forma contratta:
<bean name="shapeDrawer" class="it.italiancoders.demospring3.components.ShapeDrawer"> <constructor-arg ref="rectangle" /> </bean>
Conclusioni
Con questa introduzione abbiamo visto come il container IoC Spring ha una grande semplicità e estrema flessibilità nel definire configurazioni e dichiarare bean.
Nei prossimi articoli vedremo più nel dettaglio altre potenzialità di Spring ed un uso più avanzato del container IoC.