Skip to content

5. [Corso]: Introduzione al framework Spring

Parole chiave: architettura multilivello, Spring, iniezione di dipendenze.

Spring è apparso per la prima volta nel 2004 come contenitore di oggetti. Da allora, si è evoluto in diversi rami: Spring MVC, Spring Data, Spring Batch, ... [http://spring.io]. In questo capitolo, ci concentreremo esclusivamente sul contenitore di oggetti. Ecco alcuni punti chiave:

  • un'applicazione ha 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 chiamata contesto;
  • le classi accedono ai singleton dell'applicazione richiedendoli a Spring tramite il loro nome, tipo o entrambi;
  • Spring crea i singleton e gestisce le eventuali dipendenze che essi possono avere: un singleton può infatti contenere riferimenti a uno o più altri singleton. Quando Spring crea un singleton, crea anche le sue dipendenze;
  • Quando un'applicazione basata su Spring si avvia, può chiedere a Spring di creare tutti i singleton dell'applicazione. Questi saranno quindi disponibili nel contesto Spring;
  • Spring semplifica l'uso di architetture a livelli e la programmazione basata su interfacce. In casi semplici, ogni livello è implementato come singleton e implementa un'interfaccia. Se l'applicazione lavora con le interfacce dei livelli piuttosto che con le loro classi di implementazione, il risultato è un'architettura scalabile che permette di cambiare l'implementazione di un livello senza influenzare gli altri, grazie alle seguenti due caratteristiche:
    • 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;

I singleton possono essere dichiarati in tre modi, che possono essere combinati:

  • all'interno di un file XML,
  • in una classe di configurazione speciale;
  • con qualsiasi classe utilizzando le annotazioni;

Di seguito presentiamo tre esempi di configurazione:

  • [esempio-01]: configurazione centralizzata in un unico file XML;
  • [esempio-02]: configurazione centralizzata in una singola classe Java;
  • [esempio-03]: configurazione distribuita su più classi Java;

L'ultimo esempio [esempio-04] si concentra sulla configurazione Spring di un'architettura a livelli. Questo è l'esempio più importante. Verrà utilizzato in modo coerente per configurare le architetture descritte in questo 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;

5.1. Supporto

 

La cartella [support / chap-05] contiene i progetti Eclipse relativi a questo capitolo.

5.2. Esempio-01

5.2.1. Il progetto Eclipse

 

5.2.2. La classe [Person]

 

package istia.st.spring.core;
 
public class Personne {
 
    // fields
    private String nom;
    private String prenom;
    private int age;
 
    // manufacturers
    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);
    }
 
    // getters and setters
 
    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 come segue [1-2]:

5.2.3. La classe [Apartment]

 

package istia.st.spring.core;
 
public class Appartement {
 
    // fields
    private Personne proprietaire;
    private int surface;
 
    // getters and setters
 
    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 il costruttore predefinito 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(){
}

5.2.4. Il file di configurazione 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/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
    <!-- Person 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>
    <!-- Person 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>
    <!-- a list of people -->
    <util:list id="club">
        <ref bean="personne_01" />
        <ref bean="personne_02" />
    </util:list>
    <!-- an apartment -->
    <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 [Person];
  • righe 12–16: la classe [Person] viene prima creata utilizzando il suo costruttore predefinito [new Person()]. Quindi, per ogni tag [property], viene utilizzato un metodo setter della classe. Ad esempio, alla riga 13, verrà eseguito il metodo [setName("martin")]. Il metodo [setName] deve quindi esistere. Questo è un punto importante da ricordare;
  • righe 18–21: il tag <util:list> viene utilizzato per definire un singleton che è una lista;
  • riga 19: fa riferimento al singleton [person_01] definito alla riga 6. Questo è ciò che viene chiamato iniezione di dipendenze. È possibile utilizzare due attributi per inizializzare il campo di un singleton:
    • [value]: per assegnare un valore primitivo (stringa, numero, data, ecc.) al campo,
    • [ref]: per assegnare al campo il riferimento di un oggetto Spring;

Nota: il file di configurazione Spring può essere generato come segue [1-4]:

5.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) {
        // spring context retrieval
        ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
        // beans are recovered
        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);
        // we display them
        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);
        // recovered beans are singletons
        // they can be requested several times, but the same bean is always retrieved
        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 [person_01] di tipo [Person]. Questo secondo parametro è facoltativo, ma se omesso viene restituito un riferimento di tipo [Object], che deve quindi essere convertito in tipo [Person];
  • Riga 19: Non usiamo il nome del bean, ma solo il suo tipo, poiché esiste un solo singleton di tipo [Apartment];
  • Riga 18: vengono utilizzati sia l'identificatore che il tipo del singleton desiderato. L'identificatore è ridondante poiché esiste un solo singleton di tipo [new ArrayList<Person>().getClass()];
  • Righe 32–33: mostrano che se richiediamo lo stesso singleton più volte, otteniamo sempre lo stesso riferimento, dimostrando così che abbiamo effettivamente a che fare con un singleton. È importante comprendere questo punto;

Nota: una classe eseguibile può essere generata come segue [1-6]:

  • Il controllo [6] garantisce che la classe generata contenga un metodo statico [main], rendendola eseguibile;

5.2.6. Dipendenze del progetto

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

Le dipendenze vengono aggiunte al progetto come segue:

  • in [1]: clicca con il tasto destro del mouse sul progetto / [Build Path] / [Configure Build Path];
  • in [2]: [Add JARs] se i JAR da aggiungere si trovano in una cartella del progetto. Altrimenti, [Add External JARs];
  • in [3], selezionare i JAR da aggiungere al ClassPath del progetto (in questo caso, si trovano nella cartella [lib] all'interno del progetto);

Definizione: il [ClassPath] di un progetto è l'insieme delle cartelle in cui la JVM (Java Virtual Machine) che esegue il progetto effettua la ricerca 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] è quella generata dalla compilazione della cartella [src]. Pertanto, tutto ciò che si trova nella cartella [src] fa automaticamente parte del [ClassPath] (anche se non si tratta di un file .java). Quindi, nel progetto precedente, il file di configurazione Spring [config-01.xml], che si trova nella cartella [src], farà parte del [ClassPath] del progetto in fase di esecuzione.

5.2.7. 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

5.3. Esempio-02

5.3.1. Il progetto Eclipse

 

5.3.2. La classe di configurazione 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 i singleton. Questi vengono definiti utilizzando 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 [person_01], ovvero il nome del metodo.
  • riga 23: i parametri [person_01, person_02] recano i nomi dei singleton. Spring li inizializzerà automaticamente con i riferimenti di questi singleton. Questo processo è denominato iniezione di parametri;

Questo modo di configurare i singleton è più esplicito di quello che utilizza il file XML. Infatti, stiamo replicando ciò che Spring faceva implicitamente sulla base del file XML.

5.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) {
        // spring context retrieval
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        // beans are recovered
        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);
        // we display them
        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);
        // recovered beans are singletons
        // they can be requested several times, but the same bean is always retrieved
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • La riga 13 fa sì che tutti i bean definiti nella classe [Config] vengano istanziati;
  • il resto del codice rimane invariato;

5.3.4. 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>
    <!-- plugins -->
    <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 grattacapo quando si utilizzano librerie Java le cui dipendenze sono sconosciute. Ad esempio, il framework [Hibernate], che gestisce l'accesso al database, ha decine di dipendenze. Il progetto [Maven] risolve questo problema. È sufficiente specificare la dipendenza necessaria. Questa viene automaticamente cercata nei repository Maven distribuiti su Internet. 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 recuperata dal repository locale. Una dipendenza è caratterizzata dai seguenti elementi:

  • riga 17: un tag <dependency>;
  • riga 18: un attributo [groupId] che generalmente 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 artefatto Maven definito dalle righe 4–8:

  • righe 4–6: gli attributi [groupId, artifactId, version] appena descritti;
  • righe 7–8: sono attributi opzionali;

Torneremo sul ruolo delle righe 24–40 un po' più avanti. Per convertire un progetto Eclipse standard in un progetto Maven, è necessario fare due cose:

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

L'icona di un progetto Maven è caratterizzata da una M [4]. La S indica che il progetto contiene componenti Spring. Non è consigliabile convertire (come abbiamo appena fatto) un progetto Eclipse in un progetto Maven, poiché il progetto risulterà privo della struttura prevista per un progetto Maven, il che a volte può causare problemi imprevisti.

5.3.5. Generazione dell'artefatto Maven del progetto

Ci riferiamo all'elemento definito dalle righe 4–6 del file [pom.xml] come all'artefatto Maven del progetto:


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

Per generare questo artefatto, nel file [pom.xml] devono essere presenti le seguenti righe 3–7:


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

Questi definiscono il plugin Maven in grado di generare l'artefatto del progetto. Procediamo quindi come segue:

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

 

È quindi possibile verificare che l'artefatto Maven sia stato installato correttamente:

 

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

5.4. Esempio-03

5.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'identificatore del gruppo Maven a cui apparterrà il progetto;
  • in [5]: il nome dell'artefatto Maven generato:
  • in [6]: la sua versione;
  • in [7]: il suo formato di pacchettizzazione (altri formati includono war, ear, apk, ecc.);
  • in [8]: il progetto così creato;

Per impostazione predefinita, un progetto Maven ha una struttura di directory specifica:

  • [src / main / java]: il codice sorgente del progetto. L'output compilato da questi sorgenti andrà nella cartella [target/classes] del progetto;
  • [src/main/resources]: risorse che devono trovarsi nel classpath del progetto ma che non sono file sorgente Java. Verranno copiate così come sono nella cartella [target/classes] del progetto;
  • [src/test/java]: il codice sorgente per i test del progetto. L'output compilato da queste sorgenti andrà nella cartella [target/test-classes] del progetto. Questi elementi non sono inclusi nell'archivio Maven del progetto;
  • [src / test / resources]: risorse che devono trovarsi nel classpath del progetto per i test ma che non sono file sorgente Java. Verranno copiate così come sono nella cartella [target/test-classes] del progetto;

Portiamo a termine il progetto come segue:

 

5.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>
 
    <!-- maven parent project -->
    <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>
        <!-- logs -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
 
    </dependencies>
 
    <!-- plugins -->
    <build>
        <plugins>
            <!-- to generate the project archive with its dependencies -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- to install the project artifact in the local Maven repository -->
            <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: per la compilazione del progetto viene utilizzato JDK 1.8;
  • righe 16–20: per i progetti che utilizzano le librerie Spring, è conveniente utilizzare un progetto Maven padre chiamato [spring-boot-starter-parent]. Questo definisce le versioni delle varie librerie Spring e delle loro dipendenze. Ciò elimina la necessità di definirle nella sezione delle dipendenze. Pertanto, nelle righe 24–27, non specifichiamo la versione desiderata di [spring-context]. Sarà quella definita dal progetto padre [spring-boot-starter-parent]. Questa tecnica elimina la necessità di preoccuparsi di potenziali incompatibilità di versione tra le dipendenze. Quelle definite dal progetto padre sono compatibili tra loro;
  • righe 29–32: Spring scrive una quantità significativa di informazioni sulla console tramite una libreria di logging. Questa libreria viene importata qui;
  • righe 40–47: un plugin Maven, di cui parleremo più avanti;
  • righe 50–52: il plugin per la generazione dell'artefatto Maven del progetto;

5.4.3. La classe di configurazione 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 vediamo il codice che è già stato commentato, con due nuove aggiunte:
    • riga 13: indica che ci sono altri bean da istanziare nel pacchetto [spring.core.entities],
    • righe 34–37: un bean [mySurface];

5.4.4. La classe [Apartment]

 

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 {
 
    // fields injected by Spring
    @Autowired
    @Qualifier("personne_01")
    private Personne propriétaire;
 
    @Autowired
    @Qualifier("mySurface")
    private int surface;
 
    // getters and setters
    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. Questo singleton verrà individuato perché abbiamo scritto [@ComponentScan({ "istia.st.spring.core.entities" })] nella classe [Config];
  • riga 11: chiede a Spring di iniettare il riferimento di uno dei singleton nel campo. Questo può essere definito in due modi:
    • tramite il suo identificatore (righe 12, 16),
    • in base al suo tipo, se esiste un solo singleton di quel tipo;

5.4.5. Esecuzione del progetto

L'esecuzione del progetto produce il seguente output 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 messaggi di log, pari a diverse decine di righe. Questi messaggi possono rivelarsi molto utili per il debug di un progetto che non funziona correttamente. Quando il progetto funziona, è possibile ridurre i messaggi di log come segue:
  

Nella cartella [src/main/resources], creare il seguente file [logback.xml]:


<configuration> 
 
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- encoders are  by default assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
 
  <!-- log level control -->
  <root level="info"> <!-- info, debug, warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • La riga 12 imposta il livello di 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

Ora c'è solo una riga di log.

5.4.6. Generazione dell'archivio del progetto con le sue dipendenze

L'archivio creato nel progetto precedente può essere utilizzato anche da un progetto Eclipse non Maven. Alcuni progetti utilizzano molte librerie e può essere complicato non dimenticarne nessuna. È qui che Maven fa miracoli, poiché è sufficiente specificare la dipendenza di livello superiore 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, le seguenti righe 3–10 devono essere 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>

Questi definiscono il plugin Maven in grado di generare l'artefatto del progetto insieme alle sue dipendenze. Successivamente, procedere come segue [1-6]:

  • [4-6] rappresentano una configurazione di build Maven;
  • in [4], inserisci un nome qualsiasi;
  • in [5], specificare la cartella del progetto;
  • in [6], inserisci gli obiettivi Maven:
    • [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 le 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ò essere aperto con un programma di decompressione. Una volta decompresso l'archivio precedente, si ottiene la seguente struttura di directory:

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

5.5. Esempio-04

5.5.1. Obiettivo

Questo esempio si basa su quello presentato nel documento [Introduzione a Spring IoC], che illustra il contributo di Spring alla configurazione di architetture multistrato. Nel documento originale, l'esempio utilizza una configurazione Spring definita in un file XML. Qui, implementiamo l'esempio utilizzando una configurazione basata su classi Java e annotazioni.

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

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

5.5.2. Il progetto Eclipse

5.5.2.1. Generazione

Creiamo un nuovo tipo di progetto:

  • in [4], inserisci il nome del progetto Eclipse;
  • in [5], selezionare un progetto Maven;
  • in [6], selezionare una versione Java >=1.7;
  • in [7], selezionare la versione di Spring Boot suggerita;
  • le informazioni in [8-11] sono informazioni relative a Maven;
  • in [12], è possibile selezionare una o più delle dipendenze suggerite. Ciò aggiungerà una serie di dipendenze al file Maven [pom.xml];
  • In [13], selezionare una cartella esistente e vuota in cui ospitare il progetto;
  • in [14], il progetto generato. Ne analizzeremo i componenti;

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/> <!-- lookup parent from 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: contengono le informazioni inserite nella procedura guidata di creazione del progetto;
  • righe 14–19: il progetto Maven padre, che definisce una serie di librerie insieme alle loro versioni. Se alcune di esse sono dipendenze del progetto, vengono elencate nel file [pom.xml] senza le loro versioni;
  • riga 23: questa riga viene utilizzata solo se si intende generare un archivio eseguibile del progetto. In caso contrario, rimane inutilizzata;
  • righe 28–31: le dipendenze minime per un progetto Spring Boot. Si noti che non abbiamo selezionato alcuna dipendenza dall'elenco delle caselle di controllo;
  • righe 33–37: la dipendenza necessaria per gestire i test unitari JUnit [http://junit.org/] integrati con Spring. La riga 36 indica che la dipendenza è richiesta solo per i test. Di conseguenza, non sarà inclusa nell'archivio del progetto;
  • righe 42–45: il plugin che genera l'artefatto Maven del progetto;

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

Vedremo che queste sono sufficienti per ciò che vogliamo fare qui.

5.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);
    }
}
  • Riga 6: L'annotazione [@SpringBootApplication] è una scorciatoia per le tre annotazioni [@Configuration, @EnableAutoConfiguration, @ComponentScan], il che significa:
    • che la classe [SpringCore04Application] è una classe di configurazione Spring;
    • che Spring Boot riceve l'istruzione di eseguire la configurazione in base alle classi che trova nel classpath del progetto, che in questo caso sono le dipendenze Maven;
    • di esaminare la directory corrente (quella della classe [SpringCore04Application]) per trovare 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 è l'elenco degli argomenti passati al metodo [main] (riga 9). Il metodo statico [SpringApplication.run] è responsabile della creazione del contesto Spring, ovvero della creazione dei vari bean presenti nelle classi di configurazione o nelle directory scansionate dall'annotazione [@ComponentScan]. Il metodo [main] qui non fa nient'altro. Per dargli un po' più di sostanza, lo modificheremo come segue:

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) {
        // spring context instantiation
        ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
        // context display
        System.out.println("---------------- Liste des beans Spring");
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
        // closing context
        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 presenti in questo contesto;

L'applicazione può essere eseguita come segue [1-3]. È valido anche il metodo standard [Esegui come applicazione Java].

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: bean provenienti dal contesto Spring. Non conosciamo il loro ruolo. Troviamo il bean [springCore04Application] alla riga 18, che, grazie alla sua annotazione [@SpringBootApplication], diventa automaticamente un bean Spring;
  • le altre righe sono log Spring a livello [INFO]. Come abbiamo già visto, questi log possono essere controllati dal file [logback.xml] posto nel classpath del progetto:
  

<configuration> 
 
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- encoders are  by default assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
 
  <!-- log level control -->
  <root level="warn"> <!-- info, debug, warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Se, alla riga 12 sopra, impostiamo il livello su [warn] anziché su [info], otteniamo 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 messaggi di livello [warn], e qui non ce n'erano.

5.5.3. Implementazione dei diversi livelli dell'architettura

Ora implementeremo i tre livelli dell'architettura sopra descritta:

  

Il livello [DAO] è implementato dal pacchetto [spring.core.dao]. Esso fornisce 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 [business] è implementato dal pacchetto [spring.core.business]. Esso fornisce la seguente interfaccia [IMetier]:


package spring.core.metier;
 
public interface IMetier {
 
    public int doSomethingInMetierLayer(int a, int b);
}

Questa interfaccia ha due implementazioni: [Business1] e [Business2]:


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]. Esso fornisce 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;
    }
 
}

5.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 {
 
    // -------------- implementation [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;
    }
 
    // -------------- implementation [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), che è considerata un'implementazione dell'interfaccia [IDao] (riga 21). Il bean [dao1] è quindi considerato un'istanza di un'interfaccia (la terminologia è scorretta ma comprensibile) e non un'istanza di una classe. Questo è 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, Business, DAO] con le istanze [Ui1, Metier1, Dao1];
  • righe 40-57: implementare i livelli [UI, Business, DAO] con le istanze [Ui2, Business2, Dao2];

5.5.5. Test unitario [JUnitTest]

  

La classe [JUnitTest] si trova nella directory [src/test/java] del progetto Maven. I file presenti in questa directory non vengono inclusi nell'archivio finale del progetto. Il 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] fa parte del progetto Spring Boot Test (riga 8). Viene introdotta dalla seguente dipendenza nel file [pom.xml]:

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

Questa annotazione accetta come parametro l'elenco delle classi di configurazione da utilizzare per costruire il contesto Spring richiesto per il test. Qui utilizziamo la classe di configurazione [Config] già presentata;

  • riga 17: l'annotazione [@RunWith] è un'annotazione JUnit (riga 5). Il suo parametro è la classe responsabile dell'esecuzione dei 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 {
 
    // layer [UI]
    @Autowired
    @Qualifier("ui1")
    private IUi ui1;
 
    @Autowired
    @Qualifier("ui2")
    private IUi ui2;
 
    // business] layer
    @Autowired
    @Qualifier("metier1")
    private IMetier metier1;
 
    @Autowired
    @Qualifier("metier2")
    private IMetier metier2;
 
    // layer [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: iniettiamo (riga 8) il bean denominato (riga 9) [ui1]. Si noti che alla riga 10 iniettiamo un'istanza di interfaccia anziché un'istanza di classe;
  • righe 21–32: gli altri bean definiti nella classe [Config] vengono iniettati allo stesso modo;
  • riga 34: l'annotazione [@Test] designa un metodo da eseguire durante il test. Altre possibili annotazioni sono le seguenti:
    • [@BeforeClass]: metodo da eseguire prima dell'avvio dei 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: verifichiamo che la chiamata [dao1.doSomethingInDaoLayer(10, 20)] restituisca 30. Per convenzione, il primo parametro è il valore atteso e il secondo è il valore effettivo;
  • riga 36: testa l'istanza [dao1] dell'interfaccia [IDao];
  • riga 37: testa l'istanza [dao2] dell'interfaccia [IDao];
  • riga 42: testa l'istanza [metier1] dell'interfaccia [IMetier];
  • Riga 43: verifica l'istanza [metier2] dell'interfaccia [IMetier];
  • riga 48: verifica l'istanza [ui1] dell'interfaccia [IUi];
  • riga 36: verifica l'istanza [ui2] dell'interfaccia [IUi];

Le asserzioni che possono essere utilizzate in un metodo di test sono le seguenti:

  • assertEquals(espressione1, espressione2): verifica che i valori delle due espressioni siano uguali. Sono accettati molti tipi di espressioni (int, String, float, double, boolean, char, short). Se le due espressioni non sono uguali, viene generata un'eccezione di tipo [AssertionFailedError],
  • assertEquals(real1, real2, delta): verifica che due numeri reali siano uguali entro delta, ovvero abs(real1-real2) <= delta. Ad esempio, si potrebbe scrivere assertEquals(real1, real2, 1E-6) per verificare che due valori siano uguali entro 10⁻⁶,
  • assertEquals(message, expression1, expression2) e assertEquals(message, real1, real2, delta) sono varianti che consentono di specificare il messaggio di errore da associare all'eccezione [AssertionFailedError] generata quando il metodo [assertEquals] fallisce,
  • assertNotNull(Object) e assertNotNull(message, Object): verifica che il riferimento a Object non sia nullo,
  • assertNull(Object) e assertNull(message, Object): verifica che il riferimento a Object sia nullo,
  • assertSame(Object1, Object2) e assertSame(message, Object1, Object2): verifica che i riferimenti a 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, procedere come segue:

Si ottiene il seguente risultato:

 

In questo caso, tutti i test sono stati superati. Cosa dimostra questo esempio? Dimostra la flessibilità offerta dal framework Spring nella configurazione di un'architettura a livelli. È possibile scegliere di utilizzare l'implementazione [Ui1, Metier1, Dao1] o [Ui2, Metier2, Dao2] semplicemente configurandola. Pertanto, nel precedente test JUnit, se si mantiene solo l'iniezione dei bean [ui1, metier1, dao1], si sta lavorando con la prima architettura. Per cambiare l'architettura, è sufficiente cambiare i bean iniettati. Ciò avviene senza modificare il codice dei livelli che implementano le interfacce. Questo tipo di programmazione è chiamato programmazione basata su interfaccia perché non si utilizzano le istanze delle classi che implementano i livelli, ma piuttosto le istanze delle loro interfacce.

5.6. Conclusione

  • Spring gestisce oggetti che sono singleton (una singola istanza). Spring gestisce anche oggetti che vengono istanziati ogni volta che viene richiesta un'istanza a Spring. Anche questo caso verrà presentato in questo documento;
  • questi oggetti possono essere dichiarati in vari modi che possono essere combinati:
    • 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 utilizzando l'annotazione [@Autowired]. Questo processo è noto come iniezione di dipendenze (DI);
  • Spring è molto utile per configurare architetture a livelli quando viene utilizzato in combinazione con il paradigma di programmazione basato su interfacce;