5. [Cours]: Introduzione al framework Spring
Parole chiave: architettura a più livelli, Spring, iniezione di dipendenze.
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 ci limiteremo a presentare 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 accedono 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;
5.1. Support
![]() | ![]() |
La cartella [support / chap-05] contiene i progetti Eclipse di questo capitolo.
5.2. Exemple-01
5.2.1. Il progetto Eclipse
![]() |
5.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]:
![]() |
5.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:
5.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 innanzitutto 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]:
![]() |
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) {
// 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]:
![]() |
![]() |
- è la selezione di [6] che farà sì che la classe generata contenga un metodo statico [main] che la renderà eseguibile;
5.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 file JARs da aggiungere al file 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] diventa 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.
5.2.7. I risultati
5.3. Exemple-02
5.3.1. Il progetto Eclipse
![]() |
5.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.
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) {
// 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;
5.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.
5.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 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.
5.4. Exemple-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'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 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>
<!-- 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 permette 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;
5.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];
5.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;
5.4.5. Esecuzione del progetto
L'esecuzione del progetto restituisce il seguente risultato nella console:
- 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, warn -->
<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]:
Rimane solo una riga di log.
5.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 tralasciarne 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;
5.5. Exemple-04
5.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 si desidera 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.
5.5.2. Il progetto Eclipse
5.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], scegliere 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 [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.
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);
}
}
- 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:
- 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:
I log sono scomparsi. Appaiono solo i messaggi di livello [warn] e in questo caso non ce n'erano.
5.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;
}
}
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 {
// -------------- 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];
5.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.
5.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;














































