Skip to content

15. Spring IoC

15.1. Introduzione

Il nostro obiettivo è quello di esplorare le capacità di configurazione e integrazione del framework Spring (http://www.springframework.org), nonché di definire e utilizzare il concetto di IoC (Inversion of Control), noto anche come Dependency Injection

Consideriamo l'applicazione a tre livelli che abbiamo appena realizzato:

Per rispondere alle richieste degli utenti, il controller [Application] deve comunicare con il livello [service]. Nel nostro esempio, si trattava di un'istanza di tipo [DaoImpl]. Il controller [Application] ha ottenuto un riferimento al livello [service] nel suo metodo [init] (Sezione 14.8.3):

@SuppressWarnings("serial")
public class Application extends HttpServlet {
...

    // service
    ServiceImpl service=null;
...
    // init
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
        // dao] layer instantiation
        DaoImpl dao = new DaoImpl();
        dao.init();
        // instantiation of the [service] layer
        service = new ServiceImpl();
        service.setDao(dao);
    }
  • riga 6: il livello [dao] è stato istanziato creando esplicitamente un'istanza di [DaoImpl]
  • riga 9: il livello [service] è stato istanziato creando esplicitamente un'istanza di [ServiceImpl]

Ricordiamo che le classi [DaoImpl] e [ServiceImpl] implementano delle interfacce, in particolare le interfacce [IDao] e [IService], rispettivamente. Nelle versioni future, l’interfaccia [IDao] sarà implementata da una classe che gestisce un elenco di persone memorizzate in un database. Chiamiamo questa classe [DaoBD] ai fini di questo esempio. Sostituire l'implementazione [DaoImpl] del livello [dao] con l'implementazione [DaoBD] richiederà la ricompilazione del livello [web]. Infatti, la riga 6 sopra, che istanzia il livello [dao] con un tipo [DaoImpl], ora deve istanziarlo con un tipo [DaoBD]. Il nostro livello [web] dipende quindi dal livello [dao]. La riga 9 sopra mostra che dipende anche dal livello [service].

Spring IoC ci consentirà di creare un'applicazione a 3 livelli in cui i livelli sono indipendenti l'uno dall'altro, ovvero la modifica di uno non richiede la modifica degli altri. Ciò offre una grande flessibilità nell'evoluzione dell'applicazione.

L'architettura precedente si evolverà come segue:

Con [Spring IoC], il controller [Application] otterrà il riferimento di cui ha bisogno dal livello [service] come segue:

  1. nel suo metodo [init], chiederà al livello [Spring IoC] di fornire un riferimento al livello [service]
  2. [Spring IoC] utilizzerà quindi un file XML di configurazione che gli indica quale classe deve essere istanziata e come deve essere inizializzata.
  3. [Spring IoC] restituisce il riferimento al livello [service] creato al controller [Application].

Il vantaggio di questa soluzione è che i nomi delle classi che istanziano i vari livelli non sono più hard-coded nel metodo [init] del controller, ma sono semplicemente elencati in un file di configurazione. Modificare l’implementazione di un livello richiederà una modifica in questo file di configurazione, ma non nel controller.

Esploriamo ora le funzionalità di [Spring IoC] utilizzando alcuni esempi.

15.2. Spring IoC nella pratica

15.2.1. Spring

[Spring IoC] fa parte di un progetto più ampio disponibile all'indirizzo [http://www.springframework.org/] (maggio 2006):

  • [1]: Spring utilizza diverse tecnologie di terze parti, qui denominate "dipendenze". È necessario scaricare la versione con le dipendenze incluse per evitare di dover scaricare in un secondo momento le librerie degli strumenti di terze parti.
  • [2]: La struttura delle directory del file zip scaricato
  • [3]: La distribuzione [Spring], ovvero gli archivi .jar del progetto Spring stesso senza le sue dipendenze. L'aspetto [IoC] di Spring è fornito dagli archivi [spring-core.jar, spring-beans.jar].
  • [4,5]: gli archivi degli strumenti di terze parti

15.2.2. Progetti Eclipse per gli esempi

Realizzeremo tre esempi che illustrano l'uso di Spring IoC. Saranno tutti contenuti nel seguente progetto Eclipse:

Image

Il progetto [springioc-examples] è configurato in modo tale che i file sorgente e le classi compilate si trovino nella radice della cartella del progetto:

  • [1]: La struttura delle cartelle del progetto [Eclipse]
  • [2]: I file di configurazione Spring si trovano nella directory principale del progetto e quindi nel classpath dell'applicazione
  • [3]: le classi dell'Esempio 1
  • [4]: le classi dell'Esempio 2
  • [5]: le classi dell'Esempio 3
  • [6]: Le librerie del progetto [spring-core.jar, spring-beans.jar] si trovano nella cartella [dist] della distribuzione Spring, mentre [commons-logging.jar] si trova nella cartella [lib/jakarta-commons]. Questi tre archivi sono stati inclusi nel classpath dell'applicazione.

15.2.3. Esempio 1

Gli elementi dell'Esempio 1 sono stati inseriti nel pacchetto [springioc01] del progetto:

Image

La classe [Person] è la seguente:

package istia.st.springioc01;

public class Personne {

    // features
    private String nom;
    private int age;

    // person display
    public String toString() {
        return "nom=[" + this.nom + "], age=[" + this.age + "]";
    }

    // init-close
    public void init() {
        System.out.println("init personne [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy personne [" + this.toString() + "]");
    }

    // getters-setters
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

}

La classe contiene:

  • righe 6-7: due campi privati, name e age
  • righe 23–38: i metodi get e set per questi due campi
  • righe 10-12: un metodo toString per recuperare il valore dell'oggetto [Person] come stringa
  • righe 15-21: un metodo init che verrà chiamato da Spring quando l'oggetto viene creato e un metodo close che verrà chiamato quando l'oggetto viene distrutto

Per istanziare oggetti di tipo [Person] utilizzando Spring, useremo il seguente file [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
</beans>
  • Righe 3, 12: Il tag <beans> è il tag radice dei file di configurazione Spring. All'interno di questo tag, il tag <bean> viene utilizzato per definire i vari oggetti da creare.
  • righe 4–7: definizione di un bean
  • riga 4: il bean è denominato [person1] (attributo id) ed è un'istanza della classe [istia.st.springioc01.Person] (attributo class). Il metodo [init] dell'istanza verrà chiamato una volta creata (attributo init-method), mentre il metodo [close] dell'istanza verrà chiamato prima che venga distrutta (attributo destroy-method).
  • riga 5: definisce il valore da assegnare alla proprietà [name] (attributo name) dell'istanza [Person] creata. Per eseguire questa inizializzazione, Spring utilizzerà il metodo [setName]. Questo metodo deve quindi esistere. In questo caso è così.
  • Riga 6: come sopra per la proprietà [age].
  • Righe 8–11: definizione simile di un bean denominato [person2]

La classe di test [Main] è la seguente:

package istia.st.springioc01;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // operation spring configuration file
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
        // recovery of bean [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
        // recovery of the [personne2] bean
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // retrieve the [personne2] bean again
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // we delete all the beans
        bf.destroySingletons();
    }
}

Commenti:

  • riga 10: per recuperare i bean definiti nel file [spring-config-01.xml], utilizziamo un oggetto [XmlBeanFactory], che ci permette di istanziare i bean definiti in un file XML. Il file [spring-config-01.xml] verrà inserito nel [ClassPath] dell'applicazione, ovvero in una delle directory cercate dalla Java Virtual Machine quando cerca una classe a cui fa riferimento l'applicazione. L'oggetto [ClassPathResource] viene utilizzato per cercare una risorsa nel [ClassPath] di un'applicazione, in questo caso il file [spring-config-01.xml]. L'oggetto [bf] (Bean Factory) risultante ci permette di ottenere un riferimento a un bean denominato "XX" utilizzando l'istruzione bf.getBean("XX").
  • Riga 12: Viene richiesto un riferimento per il bean denominato [person1] nel file [spring-config-01.xml].
  • Riga 13: Viene visualizzato il valore dell'oggetto [Person] corrispondente.
  • Righe 15–16: Facciamo lo stesso per il bean denominato [person2].
  • Righe 18–19: Richiediamo nuovamente il bean denominato [person2].
  • Riga 21: tutti i bean in [bf] vengono rimossi, ovvero quelli creati dal file [spring-config-01.xml].

L'esecuzione della classe [Main] produce i seguenti risultati:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Commenti:

  • La riga 1 è stata ottenuta eseguendo la riga 12 di [Main]. L'operazione
Personne personne1 = (Personne) bf.getBean("personne1");

ha forzato la creazione del bean [person1]. Poiché [init-method="init"] era scritto nella definizione del bean [person1], è stato eseguito il metodo [init] dell'oggetto [Person] creato. Viene visualizzato il messaggio corrispondente.

  • Riga 2: La riga 13 di [Main] ha visualizzato il valore dell'oggetto [Person] creato.
  • Righe 3–4: Lo stesso processo si ripete per il bean denominato [person2].
  • Riga 5: L'operazione nelle righe 18–19 di [Main]
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

non ha causato la creazione di un nuovo oggetto di tipo [Person]. Se così fosse stato, sarebbe stato visualizzato il metodo [init]. Questo è il principio del singleton. Per impostazione predefinita, Spring crea una sola istanza dei bean nel proprio file di configurazione. Si tratta di un servizio di riferimento agli oggetti. Se gli viene richiesto il riferimento a un oggetto che non è ancora stato creato, lo crea e restituisce un riferimento. Se l'oggetto è già stato creato, Spring restituisce semplicemente un riferimento ad esso. In questo caso, poiché [person2] è già stato creato, Spring restituisce semplicemente un riferimento ad esso.

  • L'output alle righe 6-7 è stato attivato dalla riga 21 di [Main], che richiede la distruzione di tutti i bean a cui fa riferimento l'oggetto [XmlBeanFactory bf], ovvero i bean [person1] e [person2]. Poiché questi due bean hanno l'attributo [destroy-method="close"], viene eseguito il metodo [close] di entrambi i bean, causando la visualizzazione delle righe 6-7.

Ora che abbiamo trattato le basi della configurazione di Spring, potremo procedere con le nostre spiegazioni un po' più rapidamente.

15.2.4. Esempio 2

Gli elementi dell'Esempio 2 sono collocati nel pacchetto [springioc02] del progetto:

Image

Il pacchetto [springioc02] viene creato innanzitutto copiando e incollando il pacchetto [springioc01], quindi vi viene aggiunta la classe [Car] e la classe [Main] viene adattata al nuovo esempio.

La classe [Car] è la seguente:

package istia.st.springioc02;

public class Voiture {
    // features
    private String marque;
    private String type;
    private Personne propriétaire;

    // manufacturers
    public Voiture() {
    }

    public Voiture(String marque, String type, Personne propriétaire) {
        setMarque(marque);
        setType(type);
        setPropriétaire(propriétaire);
    }

    // toString
    public String toString() {
        return "Voiture : marque=[" + this.marque + "] type=[" + this.type
                + "] propriétaire=[" + this.propriétaire + "]";
    }

    // getters-setters
    public String getMarque() {
        return marque;
    }

    public void setMarque(String marque) {
        this.marque = marque;
    }

    public Personne getPropriétaire() {
        return propriétaire;
    }

    public void setPropriétaire(Personne propriétaire) {
        this.propriétaire = propriétaire;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    // init-close
    public void init() {
        System.out.println("init voiture [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy voiture [" + this.toString() + "]");
    }
}

La classe contiene:

  • righe 5–7: tre campi privati: type, make e owner. Questi campi possono essere inizializzati e letti utilizzando i metodi pubblici get e set nelle righe 26–48. Possono anche essere inizializzati utilizzando il costruttore Car(String, String, Person) definito nelle righe 13–17. La classe dispone anche di un costruttore senza argomenti per conformarsi allo standard JavaBean.
  • righe 20–23: un metodo toString per recuperare il valore dell'oggetto [Car] come stringa
  • Righe 51–57: un metodo `init` che verrà chiamato da Spring immediatamente dopo la creazione dell'oggetto e un metodo `close` che verrà chiamato quando l'oggetto viene distrutto

Per creare oggetti di tipo [Car], useremo il seguente file Spring [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="voiture1" class="istia.st.springioc02.Voiture" init-method="init" destroy-method="close">
        <constructor-arg index="0" value="Peugeot" />
        <constructor-arg index="1" value="307" />
        <constructor-arg index="2">
            <ref local="personne2" />
        </constructor-arg>
    </bean>
</beans>

Questo file aggiunge un bean con la chiave "car1" di tipo [Car] ai bean definiti in [spring-config-01.xml] (righe 12–17). Per inizializzare questo bean, avremmo potuto scrivere:

1
2
3
4
5
6
7
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" init-method="init" destroy-method="close">
        <property name="marque" value="Peugeot"/>
        <property name="type" value="307"/>
        <property name="propriétaire">
            <ref local="personne2"/>
        </property>
</bean>

Anziché scegliere il metodo già presentato nell'Esempio 1, abbiamo deciso di utilizzare qui il costruttore Car(String, String, Person) della classe.

  • riga 12: definizione del nome del bean, della sua classe, del metodo da eseguire dopo la sua istanziazione e del metodo da eseguire dopo la sua distruzione.
  • riga 13: valore del primo parametro del costruttore [Car(String, String, Person)].
  • Riga 14: valore del secondo parametro del costruttore [Car(String, String, Person)].
  • Righe 15–17: valore del terzo parametro del costruttore [Car(String, String, Person)]. Questo parametro è di tipo [Person]. Gli forniamo il riferimento (tag ref) del bean [person2] definito nello stesso file (attributo locale).

Per i nostri test, useremo la seguente classe [Main]:

package istia.st.springioc02;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // operation spring configuration file
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
        // recovery of bean [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
        // delete the beans
        bf.destroySingletons();
    }
}

Il metodo [main] recupera il riferimento al bean [car1] (riga 12) e lo visualizza (riga 13). I risultati sono i seguenti:

1
2
3
4
5
init personne [nom=[Brigitte], age=[20]]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]
destroy voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
destroy personne [nom=[Brigitte], age=[20]]

Commenti:

  1. Il metodo [main] richiede un riferimento al bean [car1] (riga 12). Spring inizia a creare il bean [car1] poiché questo bean non è ancora stato creato (singleton). Poiché il bean [car1] fa riferimento al bean [person2], quest'ultimo viene a sua volta costruito. Il bean [person2] è stato creato. Viene quindi eseguito il suo metodo [init] (riga 1) dei risultati. Il bean [car1] viene quindi istanziato. Viene quindi eseguito il suo metodo [init] (riga 2) dei risultati.
  2. La riga 3 dei risultati proviene dalla riga 13 di [main]: viene visualizzato il valore del bean [car1].
  3. La riga 15 di [main] richiede la distruzione di tutti i bean esistenti, il che fa sì che vengano visualizzate le righe 4 e 5 dei risultati.

15.2.5. Esempio 3

Gli elementi dell'Esempio 3 sono collocati nel pacchetto [springioc03] del progetto:

Image

Il pacchetto [springioc03] viene creato innanzitutto copiando e incollando il pacchetto [springioc01], quindi viene aggiunta la classe [GroupePersonnes], viene rimossa la classe [Voiture] e la classe [Main] viene adattata al nuovo esempio.

La classe [GroupePersonnes] è la seguente:

package istia.st.springioc03;

import java.util.Map;

public class GroupePersonnes {

    // features
    private Personne[] membres;
    private Map groupesDeTravail;

    // getters - setters
    public Personne[] getMembres() {
        return membres;
    }

    public void setMembres(Personne[] membres) {
        this.membres = membres;
    }

    public Map getGroupesDeTravail() {
        return groupesDeTravail;
    }

    public void setGroupesDeTravail(Map groupesDeTravail) {
        this.groupesDeTravail = groupesDeTravail;
    }

    // display
    public String toString() {
        String liste = "membres : ";
        for (int i = 0; i < this.membres.length; i++) {
            liste += "[" + this.membres[i].toString() + "]";
        }
        return liste + ", groupes de travail = "
                + this.groupesDeTravail.toString();
    }

    // init-close
    public void init() {
        System.out.println("init GroupePersonnes [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
    }
}

I suoi due membri privati sono:

riga 8: members: un array di persone che sono membri del gruppo

riga 9: workingGroups: un dizionario che associa una persona a un gruppo di lavoro

Si noti qui che la classe [PersonGroup] non definisce un costruttore senza argomenti. Ricordiamo che in assenza di qualsiasi costruttore, esiste un costruttore "predefinito", ovvero il costruttore senza argomenti che non fa nulla.

L'obiettivo qui è dimostrare come Spring consenta di inizializzare oggetti complessi, come quelli con campi array o dizionario. Il file bean [spring-config-03.xml] per l'Esempio 3 è il seguente:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="groupe1" class="istia.st.springioc03.GroupePersonnes" init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref local="personne1" />
                <ref local="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte" value="Marketing" />
                <entry key="Simon" value="Ressources humaines" />
            </map>
        </property>
    </bean>
</beans>
  • righe 14–17: il tag <list> consente di inizializzare un campo di tipo array o che implementa l'interfaccia List con valori diversi.
  • righe 20-23: il tag <map> consente di fare la stessa cosa con un campo che implementa l'interfaccia Map.

Per i nostri test, useremo la seguente classe [Main]:

package istia.st.springioc03;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // operation spring configuration file
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
        // bean retrieval [group1]]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
        // delete the beans
        bf.destroySingletons();
    }
}
  • Righe 12-13: Chiediamo a Spring un riferimento al bean [group1] e visualizziamo il suo valore.

I risultati sono i seguenti:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}
destroy GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Commenti:

  • Alla riga 12 del file [Main] viene richiesto un riferimento al bean [group1]. Spring avvia la creazione di questo bean. Poiché il bean [group1] fa riferimento ai bean [person1] e [person2], vengono creati questi due bean (righe 1 e 2 dei risultati). Il bean [group1] viene quindi istanziato e viene eseguito il suo metodo [init] (riga 3 dei risultati).
  • La riga 13 di [Main] visualizza la riga 4 dei risultati.
  • La riga 15 di [Main] visualizza le righe 5-7 dei risultati.

15.3. Configurazione di un'applicazione n-tier con Spring

Si consideri un'applicazione a 3 livelli con la seguente struttura:

UserDataBusinessLayer [business]DataAccessLayer [dao]UserInterfaceLayer [ui]

In questa sede, intendiamo illustrare i vantaggi dell'utilizzo di Spring per la realizzazione di tale architettura.

  • I tre livelli saranno resi indipendenti tramite l'uso di interfacce Java
  • L'integrazione dei tre livelli sarà gestita da Spring

La struttura dell'applicazione in Eclipse potrebbe essere la seguente:

  • [1]: il livello [DAO]:
    • [IDao]: l'interfaccia del livello
    • [Dao1, Dao2]: due implementazioni di questa interfaccia
  • [2]: il livello [business]:
    • [IMetier]: l'interfaccia del livello
    • [Business1, Business2]: due implementazioni di questa interfaccia
  • [3]: il livello [ui]:
    • [IUi]: l'interfaccia del livello
    • [Ui1, Ui2]: due implementazioni di questa interfaccia
  • [4]: i file di configurazione Spring dell'applicazione. Configureremo l'applicazione in due modi.
  • [5]: le librerie richieste dall'applicazione. Queste sono quelle utilizzate negli esempi precedenti.
  • [6]: il pacchetto di test. [Main1] utilizzerà la configurazione [spring-config-01.xml] e [Main2] la configurazione [spring-config-02.xml].

Lo scopo di questo esempio è mostrare che possiamo modificare l'implementazione di uno o più livelli dell'applicazione senza alcun impatto sugli altri livelli. Tutto avviene nel file di configurazione Spring.


Il livello [dao]


Il livello [dao] implementa la seguente interfaccia [IDao]:

1
2
3
4
5
package istia.st.springioc.troistier.dao;

public interface IDao {
    public int doSomethingInDaoLayer(int a, int b);
}

L'implementazione [Dao1] sarà la seguente:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }
}

L'implementazione [Dao2] sarà la seguente:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }
}

Il livello [aziendale]


Il livello [business] implementa la seguente interfaccia [IMetier]:

1
2
3
4
5
package istia.st.springioc.troistier.metier;

public interface IMetier {
      public int doSomethingInBusinessLayer(int a, int b);
}

L'implementazione di [Business1] sarà la seguente:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier1 implements IMetier {

    // layer [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

L'implementazione di [Metier2] sarà la seguente:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier2 implements IMetier {

    // layer [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a--;
        b--;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

Il livello [ui]


Il livello [ui] implementa la seguente interfaccia [IUi]:

1
2
3
4
5
package istia.st.springioc.troistier.ui;

public interface IUi {
    public int doSomethingInUiLayer(int a, int b);
}

L'implementazione [Ui1] sarà la seguente:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui1 implements IUi {

    // business] layer
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

L'implementazione [Ui2] sarà la seguente:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui2 implements IUi {

    // business] layer
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b--;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

File di configurazione Spring


Il primo [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- the dao class -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
    <!-- the business class -->
    <bean id="metier"     class="istia.st.springioc.troistier.metier.Metier1">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- the UI class -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui1">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

Il secondo [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- the dao class -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
    <!-- the business class -->
    <bean id="metier"
        class="istia.st.springioc.troistier.metier.Metier2">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- the UI class -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

Programmi di test


Il programma [Main1] è il seguente:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main1 {

    public static void main(String[] args) {
        // we retrieve an implementation of the IUi interface
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
        // we use the
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // the result is displayed
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

Il programma [Main1] utilizza il file di configurazione [spring-config-01.xml] e quindi le implementazioni [Ui1, Metier1, Dao1] dei livelli. I risultati visualizzati nella console di Eclipse:

ui(10,20)=34

Il programma [Main2] è il seguente:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main2 {

    public static void main(String[] args) {
        // we retrieve an implementation of the IUi interface
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
        // we use the
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // the result is displayed
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

Il programma [Main2] utilizza il file di configurazione [spring-config-02.xml] e quindi le implementazioni [Ui2, Metier2, Dao2] dei livelli. I risultati visualizzati nella console di Eclipse:

ui(10,20)=-10

15.4. Conclusione

L'applicazione che abbiamo realizzato è altamente scalabile. Possiamo modificare l'implementazione di un livello semplicemente configurandolo. Il codice degli altri livelli rimane invariato. Ciò è possibile grazie al concetto di IoC, che è uno dei due pilastri di Spring. L'altro pilastro è l'AOP (Aspect-Oriented Programming), che non abbiamo trattato. Esso consente di aggiungere un "comportamento" a un metodo di classe — sempre tramite configurazione — senza modificare il codice del metodo stesso.