2. Introduzione al framework Spring
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 eventuali dipendenze che questi potrebbero avere: un singleton può infatti avere 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 facilita l'uso di architetture a livelli e la programmazione basata su interfacce. Nei casi più semplici, ogni livello è implementato da un singleton e implementa un'interfaccia. Se l'applicazione opera con le interfacce dei livelli anziché con le relative classi di implementazione, ne risulta un'architettura scalabile che consente di modificare l'implementazione di un livello senza influire sugli 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;
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;
- uso di JUnit per testare i progetti;
2.1. Configurazione dell'ambiente di sviluppo
È necessario disporre di:
- un JDK (Java Development Kit) installato (sezione 23.1);
- il gestore delle dipendenze Maven installato (sezione 23.2);
- l'IDE Spring Tool Suite (STS) installato (sezione 23.3);
- scaricato il codice dal documento [http://tahe.developpez.com/java/spring-database];
Importare le configurazioni di runtime dalla cartella [eclipse config] degli esempi in STS. Queste configurazioni sono particolarmente importanti. Alcuni progetti richiedono il passaggio di argomenti alla JVM per poter essere eseguiti, e questo tipo di configurazione è generalmente un grattacapo. Inoltre, questo documento utilizza progetti Maven. Quando si incontra l'avviso:
Nota: premere [Alt-F5] per rigenerare tutti i progetti Maven.
Si consiglia vivamente di seguire questo consiglio. Senza questa precauzione, i progetti potrebbero visualizzare errori incomprensibili semplicemente perché le dipendenze Maven tra i progetti non sono corrette.
![]() |
- In [1], fare clic con il tasto destro del mouse in [Package Explorer];
![]() |
- in [4a-4b-4c], selezionare la cartella [eclipse config / launch configurations] [4b] dagli esempi;
- In [5], selezionare le configurazioni disponibili. Selezionarle tutte;
- in [6], completare la procedura guidata;
- In [7-8], visualizza le configurazioni di esecuzione importate;
![]() |
- In [8-9], deselezionare [9] per visualizzare le configurazioni dei progetti non caricati in STS. Questo è attualmente il caso;
![]() |
- in [10], le applicazioni Java, e in [11], le configurazioni di runtime per i primi tre esempi che esamineremo;
- in [12], i test JUnit, e in [13], la configurazione di esecuzione per il quarto esempio in questa sezione;
Ora, crea una variabile Eclipse denominata [M2_REPO] che specificherà la cartella del repository Maven locale (vedi Sezione 23.2). Questa variabile viene utilizzata in diverse configurazioni di esecuzione:
![]() |
Abbiamo assegnato alla variabile [M2_REPO] il valore mostrato in [6] qui sotto:
![]() |
Ora, importate i quattro esempi dalla cartella [spring-core]:
![]() |
- In [1], fai clic con il pulsante destro del mouse in [Package Explorer];
![]() |
- in [4a-4b], selezionare la cartella [spring-core] contenente gli esempi;
- in [5], selezionare tutti i progetti nella cartella e fare clic su [Fine];
- in [6], i quattro progetti in [Package Explorer];
2.2. Esempio-01
2.2.1. Il progetto Eclipse
![]() |
2.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]:
![]() |
2.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:
2.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]:
![]() |
2.2.5. La classe eseguibile
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo01 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// 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;
2.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 directory che la JVM (Java Virtual Machine) che esegue il progetto cerca per trovare una classe a cui fa riferimento il progetto. Per un progetto Eclipse, il [ClassPath] è costituito da quanto segue:
- 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.
2.2.7. Risultati
2.3. Esempio-02
2.3.1. Il progetto Eclipse
![]() |
2.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: definiscono un singleton identificato da [person_01], ovvero il nome del metodo.
- riga 23: i parametri [person_01, person_02] riportano i nomi di singleton. Spring li inizializzerà automaticamente con i riferimenti a tali singleton. Questo processo è noto come iniezione di parametri;
Questo modo di configurare i singleton è più esplicito rispetto a quello che utilizza il file XML. Infatti, stiamo riproducendo ciò che Spring ha fatto implicitamente sulla base del file XML.
2.3.3. La classe eseguibile
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo02 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// 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;
2.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 l e necessaria. Questa verrà automaticamente ricercata nei repository Maven distribuiti su Internet. Se l e richiesta ha a sua volta delle dipendenze, anche queste verranno scaricate automaticamente. Le dipendenze scaricate vengono memorizzate in un repository locale sul computer. Se in seguito un'altra applicazione avrà bisogno 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 componente 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 presenta 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ò portare a problemi imprevisti.
2.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 è indicata 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.
2.4. Esempio-03
2.4.1. Il progetto Eclipse
Questa volta creeremo 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 prodotto:
- in [6]: la sua versione;
- in [7]: il tipo di pacchetto (altre opzioni 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 verrà collocato 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;
Completiamo il progetto come segue:
![]() |
2.4.2. La configurazione Maven
Per impostazione predefinita viene generato un file [pom.xml]. Lo modifichiamo come segue:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-core-03</name>
<description>Introduction à Spring</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<!-- 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;
2.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 precedentemente commentato con due nuove aggiunte:
2.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:
2.4.5. Esecuzione del progetto
L'esecuzione del progetto produce il seguente output 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 funziona, è possibile ridurre i 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]:
Ora c'è solo una riga di log.
2.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];
Una volta eseguita l'operazione, 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:
2.5. Esempio-04
2.5.1. Obiettivo
Questo esempio si basa su uno presentato nel documento [Introduzione a Spring IoC], che dimostra il contributo di Spring alla configurazione di architetture multistrato. Nel documento originale, l'esempio utilizza una configurazione Spring di tipo " " 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.
2.5.2. Il progetto Eclipse
2.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 il testing. 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.
2.5.2.2. La classe eseguibile
![]() |
La classe eseguibile [SpringCore04Application] [[2] è la seguente:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringCore04Application {
public static void main(String[] args) {
SpringApplication.run(SpringCore04Application.class, args);
}
}
- 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 configurazioni basate sulle 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:
- 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] invece che su [info], otteniamo il seguente risultato:
I log sono scomparsi. Appaiono solo messaggi di livello [warn], e qui non ce n'erano.
2.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;
}
}
2.5.4. Configurazione del progetto Spring
![]() |
La classe di configurazione [Config] è la seguente:
package spring.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;
@Configuration
public class Config {
// -------------- 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];
2.5.5. Test unitario [JUnitTest]
![]() |
La classe [JUnitTest] si trova nel ramo [src/test/java] del progetto Maven. Gli elementi presenti in questo ramo 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 [business2] dell'interfaccia [IBusiness];
- 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 con un margine di errore pari a delta, ovvero abs(real1-real2) <= delta. Ad esempio, si potrebbe scrivere assertEquals(real1, real2, 1E-6) per verificare che due valori siano uguali con un margine di errore pari a 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 Object non sia nullo,
- assertNull(Object) e assertNull(message, Object): verifica che il riferimento Object sia nullo,
- 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, 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.
2.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;




















































