Skip to content

15. Spring IoC

15.1. Introduction

Ci proponiamo di scoprire le possibilità 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 iniezione di dipendenze (Dependency Injection)

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

Per rispondere alle richieste dell’utente, il controller [Application] deve rivolgersi al livello [service]. Nel nostro esempio, quest’ultimo era un’istanza di tipo [DaoImpl]. Il controller [Application] ha ottenuto un riferimento al livello [service] nel proprio metodo [init] (paragrafo 14.8.3):

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

     // servizio
    ServiceImpl service=null;
...
     // inizializzazione
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
         // istanziazione del livello [dao]
        DaoImpl dao = new DaoImpl();
        dao.init();
         // istanziazione del livello [service]
        service = new ServiceImpl();
        service.setDao(dao);
    }
  • riga 6: il livello [dao] è stato istanziato tramite la creazione esplicita di un'istanza [DaoImpl]
  • riga 9: il livello [service] è stato istanziato tramite la creazione esplicita di un'istanza [ServiceImpl]

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

Spring IoC ci consentirà di creare un'applicazione a tre livelli in cui i livelli sono indipendenti gli uni dagli altri, c.a.d, per cui la modifica di uno non richiede la modifica degli altri. Ciò garantisce una grande flessibilità nell'evoluzione dell'applicazione.

L’architettura precedente si evolverà nel modo seguente:

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

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

Il vantaggio di questa soluzione è che ora i nomi delle classi che istanziano i diversi livelli non sono più hardcoded nel metodo [init] del controller, ma sono semplicemente presenti in un file di configurazione. Modificare l’implementazione di un livello comporterà una modifica in questo file di configurazione, ma non nel controller.

Presentiamo ora le possibilità offerte da [Spring IoC] con l’ausilio di alcuni esempi.

15.2. Spring IoC nella pratica

15.2.1. Spring

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

  • [1]: Spring utilizza diverse tecnologie di terze parti denominate qui dépendances. È necessario scaricare la versione con dépendances per evitare di dover scaricare successivamente le librerie degli strumenti di terze parti.
  • [2]: la struttura delle directory del file zip scaricato
  • [3]: la distribuzione [Spring], c.a.d. Gli archivi .jar del progetto Spring stesso senza le sue dipendenze. L'aspetto [IoC] di Spring è garantito dagli archivi [spring-core.jar, spring-beans.jar].
  • [4,5]: gli archivi degli strumenti di terze parti

15.2.2. Progetti Eclipse degli esempi

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

Image

Il progetto [springioc-exemples] è configurato in modo che i file sorgente e le classi compilate si trovino nella directory principale del progetto:

  • [1]: la struttura delle cartelle del progetto [Eclipse]
  • [2]: i file di configurazione di Spring si trovano nella directory principale del progetto, quindi nella cartella 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 di Spring e [commons-logging.jar] 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 [Personne] è la seguente:

package istia.st.springioc01;

public class Personne {

     // caratteristiche
    private String nom;
    private int age;

     // visualizzazione Persona
    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 presenta:

  • righe 6-7: due campi privati «nom» e «age»
  • righe 23-38: i metodi di lettura (get) e scrittura (set) di questi due campi
  • righe 10-12: un metodo toString per recuperare il valore dell'oggetto [Personne] sotto forma di stringa di caratteri
  • righe 15-21: un metodo `init` che verrà chiamato da Spring al momento della creazione dell'oggetto, un metodo `close` che verrà chiamato al momento della distruzione dell'oggetto

Per istanziare oggetti di tipo [Personne] 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> serve a definire i diversi oggetti da creare.
  • righe 4-7: definizione di un bean
  • riga 4: il bean si chiama [personne1] (attributo id) ed è un'istanza della classe [istia.st.springioc01.Personne] (attributo class). Il metodo [init] dell'istanza verrà chiamato una volta che questa sarà stata creata (attributo init-method) e il metodo [close] dell'istanza verrà chiamato prima della sua distruzione (attributo destroy-method).
  • riga 5: definiscono il valore da assegnare alla proprietà [nom] (attributo name) dell'istanza [Personne] creata. Per eseguire questa inizializzazione, Spring utilizzerà il metodo [setNom]. È quindi necessario che tale metodo esista. In questo caso è così.
  • riga 6: lo stesso vale per la proprietà [age].
  • righe 8-11: definizione analoga di un bean denominato [personne2]

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) {
         // utilizzo del file di configurazione Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
         // recupero del bean [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
         // recupero del bean [personne2]
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         // recupero del bean [personne2] ancora una volta
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         // si eliminano tutti i bean
        bf.destroySingletons();
    }
}

Commenti:

  • riga 10: per ottenere i bean definiti nel file [spring-config-01.xml], utilizziamo un oggetto di tipo [XmlBeanFactory] che consente di istanziare i bean definiti in un file XML. Il file [spring-config-01.xml] verrà inserito nel [ClassPath] dell'applicazione, c.a.d, in una delle directory esplorate dalla macchina virtuale Java quando cerca una classe a cui fa riferimento l'applicazione. L'oggetto [ClassPathResource] serve a cercare una risorsa nel [ClassPath] di un'applicazione, in questo caso il file [spring-config-01.xml]. L'oggetto [bf] ottenuto (Bean Factory) consente di ottenere il riferimento a un bean denominato "XX" tramite l'istruzione bf.getBean("XX").
  • riga 12: si richiede un riferimento al bean denominato [personne1] nel file [spring-config-01.xml].
  • riga 13: viene visualizzato il valore dell'oggetto [Personne] corrispondente.
  • righe 15-16: si procede allo stesso modo per il bean denominato [personne2].
  • righe 18-19: si richiama il bean denominato [personne2].
  • riga 21: si eliminano tutti i bean [bf] e c.a.d, ovvero quelli creati a partire dal file [spring-config-01.xml].

L'esecuzione della classe [Main] fornisce 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 dall'esecuzione della riga 12 di [Main]. L'operazione
Personne personne1 = (Personne) bf.getBean("personne1");

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

  • riga 2: la riga 13 di [Main] ha visualizzato il valore dell’oggetto [Personne] creato.
  • righe 3-4: lo stesso fenomeno si ripete per il bean denominato [personne2].
  • riga 5: l'operazione delle righe 18-19 di [Main]
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

non ha provocato la creazione di un nuovo oggetto di tipo [Personne]. Se così fosse stato, sarebbe stato visualizzato il metodo [init]. È il principio del singleton. Spring, per impostazione predefinita, crea una sola istanza dei bean presenti nel proprio file di configurazione. Si tratta di un servizio di riferimento agli oggetti. Se gli viene richiesto il riferimento a un oggetto non ancora creato, lo crea e ne restituisce un riferimento. Se l’oggetto è già stato creato, Spring si limita a fornirne un riferimento. In questo caso, poiché [personne2] era già stato creato, Spring si limita a restituirne un riferimento.

  • Le visualizzazioni delle righe 6-7 sono state causate dalla riga 21 di [Main], che richiede la distruzione di tutti i bean referenziati dall’oggetto [XmlBeanFactory bf], quindi i bean [personne1, personne2]. Poiché questi due bean possiedono l’attributo [destroy-method="close"], viene eseguito il metodo [close] di entrambi i bean, provocando la visualizzazione delle righe 6-7.

Avendo ormai acquisito le basi della configurazione Spring, d’ora in poi saremo un po’ più rapidi nelle nostre spiegazioni.

15.2.4. Esempio 2

Gli elementi dell’esempio 2 sono inseriti nel pacchetto [springioc02] del progetto:

Image

Il pacchetto [springioc02] si ottiene innanzitutto copiando e incollando il pacchetto [springioc01], quindi vi si aggiunge la classe [Voiture] e si adatta la classe [Main] al nuovo esempio.

La classe [Voiture] è la seguente:

package istia.st.springioc02;

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

     // costruttori
    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 + "]";
    }

     // getter-setter
    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 presenta:

  • righe 5-7: tre campi privati di tipo, marca e proprietario. Questi campi possono essere inizializzati e letti tramite i metodi pubblici get e set dei bean alle righe 26-48. Possono essere inizializzati anche tramite il costruttore Auto(String, String, Persona) definito alle righe 13-17. La classe possiede inoltre un costruttore senza argomenti per conformarsi allo standard JavaBean.
  • righe 20-23: un metodo toString per recuperare il valore dell’oggetto [Voiture] sotto forma di stringa
  • righe 51-57: un metodo init che verrà chiamato da Spring subito dopo la creazione dell’oggetto, un metodo close che verrà chiamato al momento della distruzione dell’oggetto

Per creare oggetti di tipo [Voiture], utilizzeremo 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 ai bean definiti in [spring-config-01.xml] un bean con chiave "voiture1" di tipo [Voiture] (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é optare per il metodo già illustrato nell'esempio 1, in questa sede abbiamo scelto di utilizzare il costruttore Auto(String, String, Persona) 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 rimozione.
  • riga 13: valore del primo parametro del costruttore [Voiture(String, String, Personne)].
  • riga 14: valore del secondo parametro del costruttore [Voiture(String, String, Personne)].
  • righe 15-17: valore del terzo parametro del costruttore [Voiture(String, String, Personne)]. Questo parametro è di tipo [Personne]. Come valore gli viene fornito il riferimento (tag ref) del bean [personne2] definito nello stesso file (attributo local).

Per i nostri test, utilizzeremo 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) {
         // utilizzo del file di configurazione Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
         // recupero del bean [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
         // eliminazione dei bean
        bf.destroySingletons();
    }
}

Il metodo [main] richiede il riferimento del bean [voiture1] (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 [voiture1] (riga 12). Spring avvia la creazione del bean [voiture1] poiché tale bean non è ancora stato creato (singleton). Poiché il bean [voiture1] fa riferimento al bean [personne2], quest'ultimo viene a sua volta creato. Il bean [personne2] è stato creato. Viene quindi eseguito il suo metodo [init] (riga 1) dei risultati. Successivamente viene istanziato il bean [voiture1]. 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 [voiture1].
  3. La riga 15 di [main] richiede la distruzione di tutti i bean esistenti, il che provoca la visualizzazione delle righe 4 e 5 dei risultati.

15.2.5. Esempio 3

Gli elementi dell’esempio 3 sono inseriti nel pacchetto [springioc03] del progetto:

Image

Il pacchetto [springioc03] viene ottenuto innanzitutto copiando e incollando il pacchetto [springioc01], quindi vi si aggiunge la classe [GroupePersonnes], si elimina la classe [Voiture] e si adatta la classe [Main] al nuovo esempio.

La classe [GroupePersonnes] è la seguente:

package istia.st.springioc03;

import java.util.Map;

public class GroupePersonnes {

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

     // getter - setter
    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;
    }

     // visualizzazione
    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: membri: un array di persone appartenenti al gruppo

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

Si noti qui che la classe [GroupePersonnes] non definisce un costruttore senza argomenti. Ricordiamo che, in assenza di qualsiasi costruttore, esiste un costruttore "predefinito" che è il costruttore senza argomenti e che non esegue alcuna operazione.

In questa sede si intende mostrare come Spring consenta di inizializzare oggetti complessi, quali quelli dotati di campi di tipo array o dizionario. Il file dei bean [spring-config-03.xml] dell’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, utilizzeremo 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) {
         // utilizzo del file di configurazione Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
         // recupero del bean [groupe1]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
         // eliminazione dei bean
        bf.destroySingletons();
    }
}
  • righe 12-13: si richiede a Spring un riferimento al bean [groupe1] e se ne visualizza il valore.

I risultati ottenuti 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 di [Main], si richiede un riferimento al bean [groupe1]. Spring avvia la creazione di questo bean. Poiché il bean [groupe1] fa riferimento ai bean [personne1] e [personne2], questi due bean vengono creati (righe 1 e 2 dei risultati). Il bean [groupe1] 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

Consideriamo un'applicazione a 3 livelli con la seguente struttura:

utente

Dati

Livello business [metier]

Livello di accesso ai dati [dao]

Livello interfaccia utente [ui]

In questa sede intendiamo illustrare l’utilità di Spring nella realizzazione di tale architettura.

  • I tre livelli saranno resi indipendenti grazie all’utilizzo di interfacce Java
  • L’integrazione dei tre livelli sarà realizzata 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 [metier]:
    • [IMetier]: l'interfaccia del livello
    • [Metier1, Metier2]: 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 necessarie all'applicazione. Sono quelle utilizzate negli esempi precedenti.
  • [6]: il pacchetto dei test. [Main1] utilizzerà la configurazione [spring-config-01.xml] e [Main2] la configurazione [spring-config-02.xml].

Lo scopo di questo esempio è dimostrare che è possibile modificare l’implementazione di uno o più livelli dell’applicazione senza alcun impatto sugli altri livelli. Tutto avviene nel file di configurazione di 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 [métier]


Il livello [métier] 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 [Metier1] sarà la seguente:

package istia.st.springioc.troistier.metier;

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

public class Metier1 implements IMetier {

     // livello [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 [Metier2] sarà la seguente:

package istia.st.springioc.troistier.metier;

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

public class Metier2 implements IMetier {

     // livello [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 {

     // strato [business]
    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 {

     // livello [business]
    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);
    }

}

I 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>
    <!-- classe DAO -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
    <!-- la classe business -->
    <bean id="metier"     class="istia.st.springioc.troistier.metier.Metier1">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- la classe UI -->
    <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>
    <!-- la classe DAO -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
    <!-- la classe di business -->
    <bean id="metier"
        class="istia.st.springioc.troistier.metier.Metier2">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- la classe UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

I 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) {
         // si recupera un'implementazione dell'interfaccia IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
         // si utilizza la classe
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // si visualizza il risultato
        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 ottenuti sulla console 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) {
         // si recupera un'implementazione dell'interfaccia IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
         // si utilizza la classe
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // si visualizza il risultato
        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 ottenuti sulla console di Eclipse:

ui(10,20)=-10

15.4. Conclusion

L'applicazione che abbiamo realizzato offre una grande flessibilità di evoluzione. È possibile modificare l'implementazione di un livello tramite una semplice configurazione. Il codice degli altri livelli rimane invariato. Ciò è reso possibile dal concetto IoC, che costituisce uno dei due pilastri di Spring. L’altro pilastro è AOP (Aspect Oriented Programming), che non abbiamo presentato. Consente di aggiungere, sempre tramite configurazione, un “comportamento” a un metodo di classe senza modificarne il codice.