Skip to content

2. Introduzione al framework Spring

Spring è apparso nel 2004 inizialmente come contenitore di oggetti. Da allora si è evoluto in molteplici ramificazioni: Spring MVC, Spring Data, Spring Batch, ... [http://spring.io]. In questo capitolo presentiamo solo il contenitore di oggetti. Ecco alcuni punti di riferimento:

  • un’applicazione è composta da più classi e alcune di esse condividono oggetti che devono essere unici (singleton). Spring crea e gestisce questi singleton;
  • Spring colloca questi singleton in una struttura denominata contesto;
  • le classi hanno accesso ai singleton dell’applicazione richiedendoli a Spring tramite il loro nome, il loro tipo o entrambi;
  • Spring crea i singleton e gestisce le loro eventuali dipendenze: un singleton può infatti avere riferimenti a uno o più altri singleton. Quando Spring crea un singleton, crea anche le relative dipendenze;
  • quando un'applicazione basata su Spring viene avviata, può richiedere a Spring di creare tutti i singleton dell'applicazione. Questi saranno poi disponibili nel contesto di Spring;
  • Spring facilita l’utilizzo delle architetture a livelli e la programmazione tramite interfacce. Nei casi più semplici, ogni livello è implementato da un singleton e implementa un’interfaccia. Se l’applicazione opera con le interfacce dei livelli e non con le relative classi di implementazione, si ottiene un’architettura scalabile che consente di modificare l’implementazione di un livello senza modificare gli altri grazie alle due caratteristiche seguenti:
    • l'applicazione ottiene un riferimento al livello tramite il suo nome. Spring le fornisce un riferimento alla classe che implementa il livello;
    • l’applicazione utilizza questo riferimento come quello dell’interfaccia del livello e non come quello di una classe;

La dichiarazione dei singleton può avvenire in tre modi, che possono essere combinati tra loro:

  • all’interno di un file XML,
  • in una classe di configurazione speciale;
  • con qualsiasi classe tramite annotazioni;

Di seguito presentiamo tre esempi di configurazione:

  • [exemple-01]: configurazione centralizzata in un unico file XML;
  • [exemple-02]: configurazione centralizzata in un'unica classe Java;
  • [exemple-03]: configurazione distribuita su più classi Java;

L’ultimo esempio, [exemple-04], riguarda la configurazione Spring di un’architettura a livelli. Si tratta dell’esempio più importante, che verrà costantemente ripreso per configurare le architetture descritte nel documento.

Questi quattro esempi gettano le basi per quanto segue:

  • configurazione Spring e iniezione delle dipendenze;
  • utilizzo di Maven per gestire le dipendenze di un progetto;
  • utilizzo di JUnit per testare i progetti;

2.1. Configurazione dell’ambiente di lavoro

È necessario disporre di:

  • aver installato JDK (Java Development Kit) (paragrafo 23.1);
  • il gestore di dipendenze Maven (paragrafo 23.2);
  • aver installato la Spring Tool Suite (IDE) (paragrafo 23.3);
  • scaricato i codici del documento [http://tahe.developpez.com/java/spring-database];

Importare in STS le configurazioni di esecuzione contenute nella cartella [eclipse config] degli esempi. Queste configurazioni sono particolarmente importanti. Alcuni progetti, per essere eseguiti, richiedono il passaggio di argomenti a JVM e questo tipo di configurazione è generalmente piuttosto complicato. Inoltre, questo documento utilizza progetti Maven. Quando viene visualizzato il messaggio di avviso:

Nota: eseguire [Alt-F5] per rigenerare tutti i progetti Maven.

Si consiglia vivamente di seguire questa indicazione. Senza questa precauzione, i progetti potrebbero presentare errori incomprensibili semplicemente perché le dipendenze Maven tra i progetti sono errate.

  • in [1], clicca con il tasto destro su [Package Explorer];
  • in [4a-4b-4c] selezionare la cartella [eclipse config / launch configurations] [4b] degli esempi;
  • in [5], le configurazioni disponibili. Selezionatele tutte;
  • in [6], completate la procedura guidata;
  • in [7-8], visualizzate le configurazioni di esecuzione importate;
  • in [8-9], deselezionare [9] per visualizzare le configurazioni dei progetti non caricati in STS. Questo è il caso attuale;
  • in [10], le applicazioni Java e in [11], le configurazioni di esecuzione dei primi tre esempi che andremo a studiare;
  • in [12], i test JUnit e in [13] la configurazione di esecuzione del quarto esempio di questo paragrafo;

Ora create una variabile Eclipse denominata [M2_REPO] che indicherà la cartella del repository locale Maven (cfr. paragrafo 23.2). Questa variabile viene utilizzata in diverse configurazioni di esecuzione:

Alla variabile [M2_REPO] è stato assegnato il valore visibile in [6] qui sotto:

Ora importate i quattro esempi dalla cartella [spring-core]:

  • in [1], clicca con il tasto destro su [Package Explorer];
  • in [4a-4b], selezionare la cartella [spring-core] degli esempi;
  • in [5], selezionare tutti i progetti della cartella e eseguire [Finish];
  • in [6], i quattro progetti in [Package Explorer];

2.2. Exemple-01

2.2.1. Il progetto Eclipse

 

2.2.2. La classe [Personne]

 

package istia.st.spring.core;

public class Personne {

    // campi
    private String nom;
    private String prenom;
    private int age;

    // costruttori
    public Personne() {

    }

    public Personne(String nom, String prénom, int âge) {
        this.nom = nom;
        this.prenom = prénom;
        this.age = âge;
    }

    // toString
    public String toString() {
        return String.format("Personne[%s, %s,%d]", prenom, nom, age);
    }

    // getter e setter

    public String getNom() {
        return nom;
    }

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

    public String getPrenom() {
        return prenom;
    }

    public void setPrenom(String prenom) {
        this.prenom = prenom;
    }

    public int getAge() {
        return age;
    }

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

}

Nota: i getter e i setter possono essere generati automaticamente nel modo seguente [1-2]:

2.2.3. La classe [Appartement]

 

package istia.st.spring.core;

public class Appartement {

    // campi
    private Personne proprietaire;
    private int surface;

    // getter e setter

    public Personne getProprietaire() {
        return proprietaire;
    }

    public void setProprietaire(Personne proprietaire) {
        this.proprietaire = proprietaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", proprietaire, surface);
    }

}

Nota: questa classe non ha un costruttore esplicito. In questo caso, esiste sempre per impostazione predefinita il costruttore senza parametri che non esegue alcuna operazione. Quando si creano dei costruttori, questo costruttore predefinito non esiste più implicitamente. È quindi necessario definirlo esplicitamente:

public Appartement(){
}

2.2.4. Il file di configurazione di Spring

 

<?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:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.0.xsd">
    <!-- Persona 01 -->
    <bean id="personne_01" class="istia.st.spring.core.Personne">
        <constructor-arg index="0" value="dubois" />
        <constructor-arg index="1" value="paul" />
        <constructor-arg index="2" value="34" />
    </bean>
    <!-- Persona 02 -->
    <bean id="personne_02" class="istia.st.spring.core.Personne">
        <property name="nom" value="martin" />
        <property name="prenom" value="micheline" />
        <property name="age" value="18" />
    </bean>
    <!-- un elenco di persone -->
    <util:list id="club">
        <ref bean="personne_01" />
        <ref bean="personne_02" />
    </util:list>
    <!-- un appartamento -->
    <bean id="appartement" class="istia.st.spring.core.Appartement">
        <property name="surface" value="100" />
        <property name="proprietaire" ref="personne_01" />
    </bean>
</beans>
  • righe 2, 27: i singleton sono definiti all'interno di un tag <beans>;
  • righe 6-10: ogni singleton è definito da un tag <bean>;
  • riga 6: [id] è l'identificatore del singleton. [class] è il nome completo della classe da istanziare;
  • righe 7-9: i tre valori da passare al costruttore della classe [Personne];
  • righe 12-16: la classe [Personne] viene prima creata con il suo costruttore predefinito [new Personne()]. Successivamente, per ogni tag [property], viene utilizzato un setter della classe. Ad esempio, per la riga 13, verrà eseguito il metodo [setNom("martin")]. È quindi necessario che il metodo [setNom] esista. È un punto importante da tenere a mente;
  • righe 18-21: il tag <util:list> consente di definire un singleton che è una lista;
  • riga 19: indica il singleton [personne_01] definito alla riga 6. Si tratta di ciò che viene definito «iniezione di dipendenze». Per inizializzare il campo di un singleton sono disponibili due attributi:
    • [value]: per assegnare al campo un valore primitivo (stringa, numero, data, ...),
    • [ref]: per assegnare al campo il riferimento a un oggetto Spring;

Nota: il file di configurazione Spring può essere generato nel modo seguente [1-4]:

2.2.5. La classe eseguibile

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo01 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // recupero del contesto Spring
        ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
        // si recuperano i bean
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // li si visualizza
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // i bean recuperati sono singleton
        // è possibile richiederli più volte, si ottiene sempre lo stesso bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • riga 14: crea il contesto Spring. Tutti i singleton definiti nel file [config-01.xml] vengono quindi istanziati;
  • riga 16: richiede un riferimento al singleton identificato da [personne_01] di tipo [Personne]. Questo secondo parametro è facoltativo, ma in tal caso si riceve un riferimento a un tipo [Object], riferimento che deve quindi essere convertito nel tipo [Personne];
  • riga 19: non si utilizza il nome del bean, ma solo il suo tipo, poiché esiste un solo singleton di tipo [Appartement];
  • riga 18: sono stati utilizzati sia l’identificatore che il tipo del singleton desiderato. L’identificatore è superfluo poiché esiste un solo singleton di tipo [new ArrayList<Personne>().getClass()];
  • righe 32-33: dimostrano che se si richiede più volte lo stesso singleton, si ottiene sempre lo stesso riferimento, dimostrando così che si tratta effettivamente di un singleton. È importante comprendere questo punto;

Nota: è possibile generare una classe eseguibile nel modo seguente [1-6]:

  • è proprio selezionando [6] che la classe generata conterrà un metodo statico [main] che la renderà eseguibile;

2.2.6. Le dipendenze del progetto

 
  • dipendenze Spring: [spring-core, spring-beans, spring-context, spring-expression, commons-logging];

Le dipendenze vengono aggiunte al progetto nel modo seguente:

  • in [1]: clic destro sul progetto / [Build Path] / [Configure Build Path];
  • in [2]: [Add JARs] se i file JARs da aggiungere si trovano in una cartella del progetto. Altrimenti [Add External JARs];
  • in [3], selezionare i JARs da aggiungere al ClassPath del progetto (qui si trovano nella cartella [lib] all'interno del progetto);

Definizione: il [ClassPath] di un progetto è l'insieme delle cartelle esplorate dal JVM (Java Virtual Machine) che esegue il progetto, per trovare una classe a cui esso fa riferimento. Per un progetto Eclipse, il [ClassPath] è costituito dai seguenti elementi:

  • la cartella [bin] del progetto;
  • gli elementi del [Build Path] del progetto;

La cartella [bin] è la cartella generata dalla compilazione della cartella [src]. Pertanto, tutto ciò che si trova nella cartella [src] fa automaticamente parte della cartella [ClassPath] (anche se non si tratta di un file .java). Pertanto, nel progetto precedente, il file di configurazione Spring [config-01.xml], che si trova nella cartella [src], farà parte della cartella [Classpath] del progetto al momento dell'esecuzione.

2.2.7. I risultati

févr. 21, 2014 1:16:23 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3ac67f69: startup date [Fri Feb 21 13:16:23 CET 2014]; root of context hierarchy
févr. 21, 2014 1:16:23 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
Infos: Loading XML bean definitions from class path resource [config-01.xml]
personnes--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
club--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
appartement--------
Appartement[Personne[paul, dubois,34], 100]
beans [p01,p01b] identiques ? true

2.3. Exemple-02

2.3.1. Il progetto Eclipse

 

2.3.2. La classe di configurazione di Spring


package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public Appartement appartement(Personne personne_01) {
        Appartement appartement = new Appartement();
        appartement.setSurface(200);
        appartement.setPropriétaire(personne_01);
        return appartement;
    }
}
  • riga 9: l'annotazione [@Configuration] è un'annotazione Spring. Indica che la classe annotata definisce dei singleton. Questi sono definiti tramite l'annotazione [@Bean]. Spring eseguirà tutti i metodi annotati con [@Bean]. Questi creano i singleton dell'applicazione;
  • righe 12-15: definisce un singleton identificato da [personne_01], ovvero il nome del metodo.
  • riga 23: i parametri [personne_01, personne_02] riportano i nomi dei singleton. Spring li inizializzerà automaticamente con i riferimenti a tali singleton. Si parla di iniezione di parametri;

Questo modo di configurare i singleton è più esplicito rispetto a quello che utilizza il file XML. In effetti, riproduciamo noi stessi ciò che Spring faceva implicitamente a partire dal file XML.

2.3.3. La classe eseguibile

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo02 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // recupero del contesto Spring
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        // si recuperano i bean
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // li si visualizza
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // i bean recuperati sono singleton
        // è possibile richiederli più volte, si recupera sempre lo stesso bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • la riga 13 provoca l'istanziazione di tutti i bean definiti nella classe [Config];
  • il resto del codice rimane invariato;

2.3.4. Le dipendenze del progetto

Le dipendenze sono definite dal seguente file [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-02</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
    </dependencies>
    <!-- plugin -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>

La gestione manuale delle dipendenze di un progetto diventa un vero rompicapo quando si utilizzano librerie Java di cui non si conoscono le dipendenze. Ad esempio, il framework [Hibernate], che gestisce l’accesso ai database, presenta decine di dipendenze. Il progetto [Maven] risolve questo problema. Si specifica il nome della dipendenza necessaria. Questa viene cercata automaticamente nei repository Maven distribuiti in rete. Se la dipendenza richiesta ha a sua volta delle dipendenze, anche queste vengono scaricate automaticamente. Le dipendenze scaricate vengono memorizzate in un repository locale sul computer. Se in seguito un’altra applicazione necessita della stessa dipendenza, questa non verrà scaricata ma cercata nel repository locale. Una dipendenza è caratterizzata dai seguenti elementi:

  • riga 17: un tag <dependency>;
  • riga 18: un attributo [groupId] che in genere identifica l’azienda che ha creato la dipendenza;
  • riga 19: un attributo [artifactId] che identifica la dipendenza;
  • riga 20: un attributo [version] che identifica la versione desiderata;

La generazione del progetto produrrà a sua volta un componente Maven definito dalle righe 4-8:

  • righe 4-6: gli attributi [ groupId, artifactId,version] che abbiamo appena descritto;
  • righe 7-8: sono attributi facoltativi;

Torneremo più avanti sul ruolo delle righe 24-40. Per trasformare un normale progetto Eclipse in un progetto Maven, occorre fare due cose:

  • creare il file [pom.xml] sopra indicato;
  • dichiarare che il progetto è ora un progetto Maven [1-4]:

L'icona di un progetto Maven presenta una M [4]. La S indica che il progetto contiene elementi Spring. Si sconsiglia di convertire (come abbiamo appena fatto) un progetto Eclipse in un progetto Maven, poiché in tal caso il progetto non presenta la struttura prevista per un progetto Maven, il che può talvolta causare problemi imprevisti.

2.3.5. Generazione dell’artifact Maven del progetto

Con il termine «artefatto Maven del progetto» si intende l’elemento definito dalle righe 4-6 del file [pom.xml]:


    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>

Per generare questo artefatto, è necessario che nel file [pom.xml] siano presenti le seguenti righe da 3 a 7:


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

Queste definiscono il plugin di Maven in grado di generare l’artefatto del progetto. Si procede quindi come segue:

L'artefatto così generato viene inserito nel repository Maven locale. La sua posizione è reperibile nella configurazione di Eclipse:

 

È quindi possibile verificare la corretta installazione dell’artefatto Maven:

 

D'ora in poi, un altro progetto Maven locale potrà utilizzare questo archivio.

2.4. Exemple-03

2.4.1. Il progetto Eclipse

Questa volta creiamo un progetto Maven [1-8]:

  • in [3b]: specificare una cartella vuota in cui verrà generato il progetto;
  • in [4]: l'identificativo del gruppo Maven a cui apparterrà il progetto;
  • in [5]: il nome dell'artefatto Maven generato;
  • in [6]: la sua versione;
  • in [7]: la sua modalità di packaging (esistono anche war, ear, apk, ...);
  • in [8]: il progetto così creato;

Un progetto Maven presenta, per impostazione predefinita, una struttura ben definita:

  • [src / main / java]: i codici sorgente del progetto. I prodotti compilati da questi sorgenti andranno nella cartella [target/classes] del progetto;
  • [src / main / resources]: le risorse che devono trovarsi nel Classpath del progetto pur non essendo codice sorgente Java. Verranno copiate così come sono nella cartella [target/classes] del progetto;
  • [src / test / java]: i codici sorgente dei test del progetto. I file compilati da questi sorgenti verranno inseriti nella cartella [target/test-classes] del progetto. Questi elementi non sono inclusi nell’archivio Maven del progetto;
  • [src / test / resources]: le risorse che devono trovarsi nel Classpath del progetto per i test, pur non essendo codici sorgente Java. Verranno copiate così come sono nella cartella [target/test-classes] del progetto;

Completiamo il progetto nel modo seguente:

 

2.4.2. La configurazione Maven

Per impostazione predefinita viene generato un file [pom.xml]. Lo modifichiamo come segue:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-03</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-03</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!-- progetto Maven padre -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Spring Context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <!-- log -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

    </dependencies>

    <!-- plugin -->
    <build>
        <plugins>
            <!-- per la generazione dell'archivio del progetto con le relative dipendenze -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- per l'installazione dell'artefatto del progetto nel repository locale di Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • riga 11: il progetto è codificato in UTF-8;
  • riga 12: si utilizza un JDK 1.8 per compilare il progetto;
  • righe 16-20: per i progetti che utilizzano le librerie Spring, è utile utilizzare un progetto Maven padre denominato [spring-boot-starter-parent]. Questo definisce le versioni delle diverse librerie Spring e delle relative dipendenze. Ciò consente di non doverle più specificare nella definizione delle dipendenze. Pertanto, alle righe 24-27 non si specifica la versione desiderata di [spring-context]. Sarà quella definita dal progetto padre [spring-boot-starter-parent]. Questa tecnica consente di non doversi preoccupare di eventuali incompatibilità di versione tra le dipendenze. Quelle definite dal progetto padre sono compatibili tra loro;
  • righe 29-32: Spring scrive una notevole quantità di informazioni sulla console tramite una libreria di log. Questa viene importata qui;
  • righe 40-47: un plugin Maven su cui torneremo;
  • righe 50-52: il plugin per la generazione dell’artefatto Maven del progetto;

2.4.3. La classe di configurazione di Spring

  

La classe [Config] è la seguente:


package istia.st.spring.core.config;

import istia.st.spring.core.entities.Personne;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public int mySurface() {
        return 200;
    }
}
  • qui si trova un codice già commentato con due novità:
    • riga 13: indica che ci sono altri bean da istanziare nel pacchetto [spring.core.entities],
    • righe 34-37: un bean [mySurface];

2.4.4. La classe [Appartement]

 

package spring.core.entities;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Appartement {

    // campi iniettati da Spring
    @Autowired
    @Qualifier("personne_01")
    private Personne propriétaire;

    @Autowired
    @Qualifier("mySurface")
    private int surface;

    // getter e setter
    public Personne getPropriétaire() {
        return propriétaire;
    }

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

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", propriétaire, surface);
    }

}
  • riga 7: l'annotazione [@Component] indica a Spring che la classe è un singleton che il framework deve istanziare e gestire. È perché nella classe [Config] abbiamo scritto [@ComponentScan({ "istia.st.spring.core.entities" })] che questo singleton verrà individuato;
  • riga 11: richiede a Spring di iniettare nel campo il riferimento di uno dei singleton. Questo può essere definito in due modi:
    • tramite il suo identificatore (righe 12, 16),
    • tramite il suo tipo, se esiste un solo singleton di quel tipo;

2.4.5. Esecuzione del progetto

L'esecuzione del progetto restituisce il seguente risultato nella console:

17:32:39.797 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
....
17:32:40.134 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appartement'
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
17:32:40.135 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'personne_01'
beans [p01,p01b] identiques ? true
  • righe 1-3: Spring genera un numero molto elevato di log, diverse decine di righe. Questi log possono essere molto utili per il debug di un progetto che non funziona. Quando il progetto funziona, è possibile ridurre i log nel modo seguente:
  

Nella cartella [src / main / resources] si crea il seguente file [logback.xml]:


<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- agli encoder viene assegnato per impostazione predefinita il tipo
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- controllo del livello dei log -->
  <root level="info"> <!-- info, debug, avviso -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • alla riga 12 si imposta il livello dei log. [debug] è un livello molto dettagliato, [info] molto meno;

Ecco i risultati con [level=info]:

17:39:58.580 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7cf10a6f: startup date [Tue Apr 07 17:39:58 CEST 2015]; root of context hierarchy
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
beans [p01,p01b] identiques ? true

Rimane solo una riga di log.

2.4.6. Generazione dell’archivio del progetto con le relative dipendenze

L'archivio creato nel progetto precedente può essere utilizzato anche da un progetto Eclipse non Maven. Alcuni progetti utilizzano numerose librerie e può essere complicato non dimenticarne nessuna. È qui che Maven fa miracoli, poiché basta specificare la dipendenza di livello più alto affinché quelle di livello inferiore vengano automaticamente aggiunte al Classpath del progetto. Quando un progetto Eclipse non Maven deve utilizzare gli archivi di un progetto Maven, è possibile generare l’artefatto di quest’ultimo con tutte le sue dipendenze (cosa che non era possibile nel progetto precedente). Per questa generazione, è necessario che le seguenti righe 3-10 siano presenti nel file [pom.xml]:


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

Queste definiscono il plugin di Maven in grado di generare l’artefatto del progetto con le relative dipendenze. Successivamente si procede come segue [1-6]:

  • [4-6] rappresentano una configurazione di esecuzione di Maven;
  • in [4], inserire un nome a scelta;
  • in [5], specificare la cartella del progetto;
  • in [6], inserire gli obiettivi Maven (goals):
    • [clean]: la cartella [target] del progetto viene eliminata;
    • [compile]: il progetto viene compilato. I risultati della compilazione vengono inseriti in una cartella [target] rigenerata;
    • [assembly:single]: le classi del progetto e delle sue dipendenze vengono inserite in un unico archivio jar nella cartella [target];

Dopo l'esecuzione, si ottiene il seguente risultato:

Un archivio jar è un file compresso che può quindi essere aperto con un programma di decompressione. Una volta decompresso l'archivio precedente, si ottiene la struttura delle directory seguente:

  • in [8], le classi delle dipendenze del progetto;
  • in [9], le classi del progetto stesso;

2.5. Exemple-04

2.5.1. Obiettivo

Questo esempio riprende uno di quelli presentati nel documento [Introduction à Spring IoC], in cui viene illustrato il contributo di Spring alla configurazione di architetture multistrato. Nel documento originale, l’esempio viene trattato con una configurazione Spring realizzata tramite un file XML. Qui trattiamo l’esempio con una configurazione basata su classi Java e annotazioni.

In questo caso vogliamo configurare un progetto Spring per la seguente architettura:

Ogni livello presenta un'interfaccia implementata con due classi. Vogliamo dimostrare che, grazie a Spring, è possibile modificare l'implementazione di un livello senza alcun impatto sul codice degli altri livelli.

2.5.2. Il progetto Eclipse

2.5.2.1. Génération

Creiamo un nuovo tipo di progetto:

  • in [4], inserire il nome del progetto Eclipse;
  • in [5], selezionare un progetto Maven;
  • in [6], selezionare una versione di Java >=1.7;
  • in [7], selezionare la versione di Spring Boot proposta;
  • le informazioni in [8-11] sono informazioni relative a Maven;
  • in [12], è possibile selezionare una o più delle dipendenze proposte. Ciò comporterà l’integrazione di un certo numero di dipendenze nel file [pom.xml] di Maven;
  • in [13], si indica una cartella esistente e vuota in cui ospitare il progetto;
  • in [14], il progetto generato. Analizziamo ora i vari elementi;

Il progetto è un progetto Maven configurato dal seguente file [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-04</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-core-04</name>
    <description>Programmation par interfaces</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath/> <!-- ricerca del genitore dal repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.SpringCore04Application</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • righe 6-12: riportano le informazioni inserite nella procedura guidata di creazione del progetto;
  • righe 14-19: il progetto Maven padre che definisce una serie di librerie con le relative versioni. Se una di esse è una dipendenza del progetto, viene menzionata nel file [pom.xml] senza indicarne la versione;
  • riga 23: questa riga serve solo se si intende generare un archivio eseguibile del progetto. In caso contrario, rimane inutilizzata;
  • righe 28-31: le dipendenze minime di un progetto Spring Boot. Ricordiamo che non abbiamo selezionato alcuna dipendenza dall’elenco a scelta multipla;
  • righe 33-37: la dipendenza necessaria per gestire i test unitari JUnit e [http://junit.org/] integrati con Spring. La riga 36 indica che la dipendenza è necessaria solo per i test. Di conseguenza, non verrà inclusa nell’archivio del progetto;
  • righe 42-45: il plugin che consente di generare l’artefatto Maven del progetto;

L'elenco delle dipendenze fornite da questo file è il seguente: [1]:

Vedremo che sono sufficienti per ciò che vogliamo fare in questo contesto.

2.5.2.2. La classe eseguibile

  

La classe eseguibile [SpringCore04Application] [[2] è la seguente:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringCore04Application.class, args);
    }
}
  • alla riga 6, l'annotazione [@SpringBootApplication] è una scorciatoia per le tre annotazioni [@Configuration, @EnableAutoConfiguration, @ComponentScan], il che significa che:
    • che la classe [SpringCore04Application] è una classe di configurazione Spring;
    • che a Spring Boot viene richiesto di effettuare configurazioni a partire dalle classi che troverà nel Classpath del progetto, quindi in questo caso nelle dipendenze Maven;
    • di esaminare la cartella corrente (quella della classe [SpringCore04Application]) per individuare eventuali altri componenti Spring;
  • riga 10: viene eseguito il metodo statico [SpringApplication.run]. Il suo primo parametro è una classe di configurazione Spring, in questo caso la classe [SpringCore04Application]. Il suo secondo parametro è, in questo caso, l'elenco degli argomenti passati al metodo [main] (riga 9). Il metodo statico [SpringApplication.run] ha il compito di creare il contesto Spring, ovvero di creare i vari bean presenti sia nelle classi di configurazione sia nelle cartelle esplorate dall’annotazione [@ComponentScan]. Il metodo [main] qui non fa nient’altro. Per dargli un po’ più di sostanza, lo modificheremo nel modo seguente:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        // istanziazione del contesto Spring
        ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
        // visualizzazione del contesto
        System.out.println("---------------- Liste des beans Spring");
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
        // chiusura del contesto
        context.close();
    }
}
  • riga 12: il metodo statico [SpringApplication.run] restituisce il contesto Spring che ha costruito;
  • righe 15-17: vengono visualizzati i nomi di tutti i bean di questo contesto;

È possibile eseguire l’applicazione nel modo seguente: [1-3]. È valido anche il metodo standard [Run As Java Application].

Si ottiene il seguente risultato:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

2015-04-08 11:18:38.254  INFO 4796 --- [           main] demo.SpringCore04Application             : Starting SpringCore04Application on Gportpers3 with PID 4796 (D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04\target\classes started by ST in D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04)
2015-04-08 11:18:38.295  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.776  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-04-08 11:18:38.788  INFO 4796 --- [           main] demo.SpringCore04Application             : Started SpringCore04Application in 0.773 seconds (JVM running for 1.335)
---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
2015-04-08 11:18:38.789  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.790  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
  • righe 14-28: i bean del contesto Spring. Non ne conosciamo la funzione. Troviamo il bean [springCore04Application] alla riga 18 che, grazie alla sua annotazione [@SpringBootApplication], diventa automaticamente un bean Spring;
  • le altre righe sono log di Spring di livello [INFO]. Come abbiamo già visto, questi log possono essere controllati tramite il file [logback.xml] inserito nel Classpath del progetto:
  

<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- agli encoder viene assegnato per impostazione predefinita il tipo
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- controllo del livello dei log -->
  <root level="warn"> <!-- info, debug, warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Se alla riga 12 sopra riportata si sostituisce il livello [info] con [warn], si ottiene il seguente risultato:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer

I log sono scomparsi. Appaiono solo i messaggi di livello [warn] e in questo caso non ce n'erano.

2.5.3. Implementazione dei diversi livelli dell’architettura

Ora implementeremo i tre livelli dell’architettura sopra riportata:

  

Il livello [DAO] è implementato dal pacchetto [spring.core.dao]. Presenta la seguente interfaccia [IDao]:


package spring.core.dao;

public interface IDao {

    public int doSomethingInDaoLayer(int a, int b);
}

Questa interfaccia ha due implementazioni: [Dao1] e [Dao2]:


package spring.core.dao;

public class Dao1 implements IDao {

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

}

package spring.core.dao;

public class Dao2 implements IDao {

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

}

Il livello [métier] è implementato dal pacchetto [spring.core.metier]. Presenta la seguente interfaccia [IMetier]:


package spring.core.metier;

public interface IMetier {

    public int doSomethingInMetierLayer(int a, int b);
}

Questa interfaccia ha due implementazioni: [Metier1] e [Metier2]:


package spring.core.metier;

import spring.core.dao.IDao;

public class Metier1 implements IMetier {

    private IDao dao;

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

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

package spring.core.metier;

import spring.core.dao.IDao;

public class Metier2 implements IMetier {

    private IDao dao;

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

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


}

Il livello [UI] è implementato dal pacchetto [spring.core.ui]. Presenta la seguente interfaccia [IUi]:


package spring.core.ui;

public interface IUi {

    public int doSomethingInUiLayer(int a, int b);
}

Questa interfaccia ha due implementazioni: [Ui1] e [Ui2]:


package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui1 implements IUi {

    private IMetier metier;
    
    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

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

}

package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui2 implements IUi {

    private IMetier metier;

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

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

}

2.5.4. Configurazione del progetto Spring

  

La classe di configurazione [Config] è la seguente:


package spring.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;

@Configuration
public class Config {

    // -------------- implementazione [Ui1, Metier1, Dao1]
    @Bean
    public IDao dao1() {
        return new Dao1();
    }

    @Bean
    public IMetier metier1(IDao dao1) {
        Metier1 metier = new Metier1();
        metier.setDao(dao1);
        return metier;
    }

    @Bean
    public IUi ui1(IMetier metier1) {
        Ui1 ui = new Ui1();
        ui.setMetier(metier1);
        return ui;
    }

    // -------------- implementazione [Ui2, Metier2, Dao2]
    @Bean
    public IDao dao2() {
        return new Dao2();
    }

    @Bean
    public IMetier metier2(IDao dao2) {
        Metier2 metier = new Metier2();
        metier.setDao(dao2);
        return metier;
    }

    @Bean
    public IUi ui2(IMetier metier2) {
        Ui2 ui = new Ui2();
        ui.setMetier(metier2);
        return ui;
    }
}
  • righe 20-23: il bean denominato [dao1] (nome del metodo) è un'istanza della classe [Dao1] (riga 22) considerata come un'implementazione dell'interfaccia [IDao] (riga 21). Il bean [dao1] viene quindi considerato un’istanza dell’interfaccia (la terminologia non è corretta, ma il concetto è comprensibile) e non un’istanza della classe. Si tratta di un punto importante da comprendere. Anche tutti gli altri bean saranno istanze di interfacce;
  • righe 25-30: un'istanza dell'interfaccia [IMetier] implementata dalla classe [Metier1];
  • righe 32-37: un'istanza dell'interfaccia [IUi] implementata dalla classe [Ui1];
  • righe 20-37: implementano i livelli [UI, Metier, DAO] con istanze [Ui1, Metier1, Dao1];
  • righe 40-57: implementano i livelli [UI, Metier, DAO] con istanze [Ui2, Metier2, Dao2];

2.5.5. Test unitario [JUnitTest]

  

La classe [JUnitTest] è inserita nel ramo [src / test / java] del progetto Maven. Gli elementi di questo ramo non vengono inseriti nell'archivio finale del progetto. Il suo codice è il seguente:


package spring.core.tests;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
  • riga 16: l'annotazione [@SpringApplicationConfiguration] è un'annotazione del progetto Spring Boot Test (riga 8). È introdotta dalla seguente dipendenza del file [pom.xml]:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
</dependency>

Questa annotazione ha come parametro l'elenco delle classi di configurazione da utilizzare per costruire il contesto Spring necessario al test. In questo caso utilizziamo la classe di configurazione [Config] già presentata;

  • riga 17: l’annotazione [@RunWith] è un’annotazione JUnit (riga 5). Il suo parametro è la classe incaricata di eseguire i test al posto della classe predefinita del framework JUnit. Questa classe è una classe Spring (riga 9). Utilizzerà le annotazioni Spring presenti nella classe di test;

La classe completa è la seguente


...

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {

    // livello [UI]
    @Autowired
    @Qualifier("ui1")
    private IUi ui1;

    @Autowired
    @Qualifier("ui2")
    private IUi ui2;

    // livello [métier]
    @Autowired
    @Qualifier("metier1")
    private IMetier metier1;

    @Autowired
    @Qualifier("metier2")
    private IMetier metier2;

    // livello [dao]
    @Autowired
    @Qualifier("dao1")
    private IDao dao1;

    @Autowired
    @Qualifier("dao2")
    private IDao dao2;

    @Test
    public void testDao() {
        Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
        Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
    }

    @Test
    public void testMetier() {
        Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
        Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
    }

    @Test
    public void testUI() {
        Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
        Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
    }

}
  • righe 8-10: si inietta (riga 8) il bean denominato (riga 9) [ui1]. Si noti alla riga 10 che si inietta un’istanza di interfaccia e non un’istanza di classe;
  • righe 21-32: si iniettano allo stesso modo gli altri bean definiti nella classe [Config];
  • riga 34: l’annotazione [@Test] indica un metodo da eseguire durante i test. Le altre annotazioni possibili sono le seguenti:
    • [@BeforeClass]: metodo da eseguire prima di avviare i test;
    • [@AfterClass]: metodo da eseguire una volta completati tutti i test;
    • [@Before]: metodo da eseguire prima di ogni test;
    • [@After]: metodo da eseguire dopo ogni test;
  • riga 36: si verifica che la chiamata a [dao1.doSomethingInDaoLayer(10, 20)] restituisca effettivamente 30. Per convenzione, il primo parametro è il valore atteso e il secondo il valore effettivo;
  • riga 36: verifica l'istanza [dao1] dell'interfaccia [IDao];
  • riga 37: verifica l'istanza [dao2] dell'interfaccia [IDao];
  • riga 42: verifica l'istanza [metier1] dell'interfaccia [IMetier];
  • riga 43: esegue il test dell'istanza [metier2] dell'interfaccia [IMetier];
  • riga 48: esegue il test dell'istanza [ui1] dell'interfaccia [IUi];
  • riga 36: esegue il test dell'istanza [ui2] dell'interfaccia [IUi];

Le asserzioni utilizzabili in un metodo di test sono le seguenti:

  • assertEquals(espressione1, espressione2): verifica che i valori delle due espressioni siano uguali. Sono accettati numerosi tipi di espressione (int, String, float, double, boolean, char, short). Se le due espressioni non sono uguali, viene generata un’eccezione di tipo [AssertionFailedError ],
  • assertEquals(reale1, reale2, delta): verifica che due valori reali siano uguali con un margine di delta, ovvero c.a.d abs(reale1-reale2)<=delta. Si potrà scrivere, ad esempio, assertEquals(reale1, reale2, 1E-6) per verificare che due valori siano uguali con una tolleranza di 10⁻⁶,
  • assertEquals(messaggio, espressione1, espressione2) e assertEquals(messaggio, reale1, reale2, delta) sono varianti che consentono di specificare il messaggio di errore da associare all’eccezione di tipo [AssertionFailedError] generata quando il metodo [assertEquals] fallisce,
  • assertNotNull(Object) e assertNotNull(message, Object): verifica che il riferimento Object non sia uguale a null,
  • assertNull(Object) e assertNull(message, Object): verifica che il riferimento Object sia uguale a null,
  • assertSame(Object1, Object2) e assertSame(message, Object1, Object2): verifica che i riferimenti Object1 e Object2 puntino allo stesso oggetto,
  • assertNotSame(Object1, Object2) e assertNotSame(message, Object1, Object2): verifica che i riferimenti Object1 e Object2 non puntino allo stesso oggetto;

Per eseguire il test, è possibile procedere come segue:

Si ottiene il seguente risultato:

 

In questo caso tutti i test hanno avuto esito positivo. Cosa dimostra questo esempio? Dimostra la flessibilità offerta dal framework Spring nella configurazione di un'architettura a livelli. È possibile decidere di utilizzare l'implementazione [Ui1, Metier1, Dao1] o [Ui2, Metier2, Dao2] semplicemente tramite configurazione. Pertanto, nel precedente test JUnit, se si mantiene solo l’iniezione dei bean [ui1, metier1, dao1], si lavora con la prima architettura. Per cambiare architettura, è sufficiente modificare i bean iniettati. Ciò avviene senza modificare il codice dei livelli che implementano le interfacce. Questo tipo di programmazione viene definito «programmazione tramite interfacce», poiché non si utilizzano le istanze delle classi che implementano i livelli, ma le istanze delle loro interfacce.

2.6. Conclusion

  • Spring gestisce oggetti che sono singleton (un unico esemplare). Spring gestisce anche oggetti che vengono istanziati ogni volta che se ne richiede un’istanza a Spring. Anche questo caso verrà presentato in questo documento;
  • questi oggetti possono essere dichiarati in vari modi, che possono essere combinati tra loro:
    • in un file XML,
    • in una classe Java annotata con [@Configuration],
    • con qualsiasi classe Java annotata con [@Component, @Service, ...];
  • un oggetto Spring può essere iniettato in un altro oggetto Spring con l’annotazione [@Autowired]. Si parla in questo caso di iniezione di dipendenze (DI: Dependency Injection);
  • Spring si rivela molto pratico per configurare architetture a livelli con l’uso combinato del paradigma della programmazione tramite interfacce;