Spring-03: Introduzione al Container IoC di Spring

S

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.

A proposito di me

Alessio Fiore

Grande appassionato di sviluppo software e linguaggi di programmazione ha una grande conoscenza nel mondo Java e framework come Spring e Hibernate.
Laureato magistrale in Ingegneria Informatica lavora come sviluppatore software in ambito Telco principalmente lato backend, ma coltivando sempre molto interesse su tecnologie frontend.
Ha una grande esperienza su system integration, architetture software e sistemi distribuiti.

Di Alessio Fiore

Gli articoli più letti

Articoli recenti

Commenti recenti