Sei sulla pagina 1di 96

Dispense Contrader

Luca Sau

16 dicembre 2022
Indice

I Back-End 5
1 Java 6
1.1 JRE vs JDK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2 Principi dell’ O.O.P. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.1 Astrazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.2 Ereditarietà . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.3 Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.4 Incapsulamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Classi astratte e Interfacce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.1 Classi Astratte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.2 Interfacce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4 Eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.4.1 Gerarchia delle eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.4.2 Gestione delle eccezioni da parte della JVM . . . . . . . . . . . . . . . . . . 18
1.4.3 User Defined Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5 Java Collections e Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5.1 Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.5.2 Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.6 Stream e Programamzione Funzionale . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.6.1 Lambda Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.6.2 Method References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.6.3 Java Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

2 Database 29
2.1 DB Relazionali o SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.2 DB Non Relazionali o NO-SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.3 SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.1 Join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.2 Relazioni tra tabelle: Foreign Key . . . . . . . . . . . . . . . . . . . . . . . 32
2.3.3 Tipi di relazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

3 Spring 34
3.1 Inversion Of Control e Dependency Injection . . . . . . . . . . . . . . . . . . . . . 34
3.1.1 IoC Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.2 Crezione dei bean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.3 Dependency Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.4 Moduli di Spring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.5 Spring Boot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.6 Annotazioni Utili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.6.1 Specializzazioni di @Component . . . . . . . . . . . . . . . . . . . . . . . . 38
3.6.2 Annotazioni di Spring Web . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.6.3 Annotazioni per le entità della JPA . . . . . . . . . . . . . . . . . . . . . . 41
3.6.4 @Transactional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.6.5 Annotazioni di Lombok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.6.6 @Mapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.7 Hibernate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

1
4 Rest API 46
4.1 REST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.2 Metodi HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.3 Standard d’Industria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.4 REST VS SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

5 Microservizi 48
5.1 Architettura monolitica vs. architettura di microservizi . . . . . . . . . . . . . . . 48
5.2 Caractteristiche dei Microservizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.3 Vantaggi dei Microservizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.4 Microservizi in Spring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

6 JSON Web Token 51


6.1 Struttura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.2 Funzionamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.3 Implementazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.3.1 Front End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.3.2 Back End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

II Front-End 60
7 TypeScript 61
7.1 Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
7.1.1 JS Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
7.1.2 Ereditarietà in JS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.1.3 Costruttori e funzioni costruttore . . . . . . . . . . . . . . . . . . . . . . . . 63
7.1.4 Closure e Lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
7.1.5 Scope delle variabili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.1.6 Classi in JS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.2 TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.2.1 Tipi in TS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.2.2 Tipi comuni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

8 HTML e CSS 71
8.1 HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
8.1.1 Struttura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
8.1.2 Elementi HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
8.1.3 Attributi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
8.1.4 Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
8.1.5 Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
8.2 CSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
8.2.1 Selettori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
8.2.2 Dichiarazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.2.3 Position . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.2.4 FlexBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

9 Angular 81
9.1 Moduli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
9.2 Componenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.2.1 Comunicazione tra componenti . . . . . . . . . . . . . . . . . . . . . . . . . 84
9.3 Template e Data Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.3.1 Interpolazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.3.2 Data Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.4 Direttive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9.5 Dependency Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
9.6 Collegamento col Back-End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
9.6.1 Observable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
9.6.2 Utilizzo delle chiamate HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . 91
9.7 Argomenti aggiuntivi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
9.7.1 Sincronia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
9.7.2 Caricamento dei dati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

2
9.7.3 File di configurazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

3
Introduzione

Blablabla
Per segnalazioni su imprecisioni/suggerimenti scrivete pure a l.sau@contrader.it o su discord Ral6h#7092.

4
Parte I

Back-End

5
Capitolo 1

Java

Java è un linguaggio di programmazione orientato a oggetti (object oriented language) e a


tipizzazione statica, sviluppato nel 1995 da Sun Microsystems.
Java è un linguaggio pseudo-compilato, ciò ne garantisce la portabilità, in quanto l’output restituito
dal compilatore Java è in formato bytecode, il quale può essere poi convertito in assembly da una
qualunque Java Virtual Machine (JVM), indipendentemente dalla macchina su cui il codice è
stato scritto inizialmente.

1.1 JRE vs JDK


JRE sta per Java Runtime Environment. Come dice il nome, esso è un ambiente di runtime,
include quindi tutto il necessario per l’esecuzione di un programma Java già compilato, come la
JVM, la Java Class Library e altre infrastrutture. Non può essere usato per la creazione di nuovi
programmi!
JDK sta per Java Development Kit. È quindi un kit di sviluppo sowftware completo di tutte le
funzioni: include di suo tutte le funzionalità della JRE, aggiungendo ad essa il compilatore (javac
da linea di comando), il debugger e altri tools, come javadoc e jdb.

NOTA BENE: la JDK è necessaria non solamente quando si sviluppa; ad esempio, se si de-
ploya una web-app che utilizza le JSP, in quanto l’ application server deve convertire le JSP in
Java Servlets e ha bisogno del compilatore per farlo!

6
1.2 Principi dell’ O.O.P.
I quattro principi fondamentali della programmazione orientata a oggetti sono:
1. Astrazione
2. Ereditarietà

3. Polimorfismo
4. Incapsulamento
Seguire questi principi garantisce la stesura di un codice flessibile, facilmente testabile e coerente
con gli standard. Vediamo ora tali principi nel dettaglio e come rispettarli quando scriviamo in
Java.

1.2.1 Astrazione
L’astrazione è un principio cardine del design di un applicazione: date le dimensioni spesso molto
grandi di un programma orientato a oggetti, è impensabile aspettarsi che gli utilizzatori diretti
debbano essere a conoscenza del funzionamento interno di ogni classe. Di conseguenza, è una prio-
rità fornire un livello di astrazione, in modo che il programma sia semplice sia da utilizzare che da
comprendere.
In Java ciò viene realizzato tramite l’utilizzo di interfacce e classi astratte, le quali ci permet-
tono di dichiarare quali sono le funzionalità offerte da un oggetto che implementi/concretizzi un
tale contratto, senza tuttavia svelare come tali funzionalità vengano di fatto realizzate.

public abstract class Animale {


public String verso(); //metodo astratto, sappiamo che un
//animale ha un suo verso, ma non sappiamo quale sia
}

public class Cane extends Animale { //concretizzo Animale


public String verso() { //esplicito il metodo
return "WOF";
}
}

Vedremo nel dettaglio le differenze tra iùun’interfaccia e una classe astratta nella prossima
sezione.

1.2.2 Ereditarietà
L’ereditarietà ci consente di strutturare una gerarchia tra le classi. Una classe figlia eredita tutte le
funzionalità della classe madre, ciò consente di strutturare gerarchicamente il nostro programma,
evitando ripetizioni di codice, consentendo maggior chiarezza e specializzazione.
In java, per creare una classe figlia di un’altra si utilizza la keyword extends nella dichiarazione.
Diamo innanzitutto un’occhiata al codice:

7
import java.util.Date;

public class Persona {


private String nome;
private Date dataNascita;
protected final String CONST = "di Persona";

public Persona(String nome, String dataNascita) {


this.nome = nome;
this.dataNascita = dataNascita;
}

public void greetings() {


System.out.println("Ciao!"):
}

public class Impiegato extends Persona {


private Long id;
private final String CONST = "di Impiegato";

public Impiegato(String nome, Date dataNascita, Long id) {


super(nome, dataNascita);
this.id = id;
}

public void demoSuper() {


System.out.println(this.CONST + " / " + super.CONST);
}
}

public class App() {


public static void main(String[] args) {
Impiegato myImp = new Impiegato("Luca", new Date(1999,09,03), 1254563);

myImp.greetings();

myImp.demoSuper();
}
}

//output: "Ciao!"
// "di Impiegato / di Persona"

Andiamo con ordine:

• Persona è la classe madre, essa ha come valori un nome e una data di nascita. Il costruttore
assegna questi valori. Oss: abbiamo volutamente chiamato i parametri del costruttore con lo
stesso nome delle variabili locali; questo non è un problema in quanto grazie alla keyword this
siamo in grado di specificare quando ci stiamo riferendo alle variabili e quando ai parametri.
• Impiegato è la classe figlia: eredita tutti gli attributi e i metodi di Persona, senza alcun
bisogno di dichiararli nuovamente, e introduce l’attributo id, specializzandosi in questo ri-
spetto alla classe madre. Osserviamo ora bene il costruttore: la keyword super invoca il
costruttore della classe madre, inizializzando quindi le variabili ereditate. Oss: super ha
anche un altro utilizzo, simile a quello di this: serve a riferirsi a una variabile della classe
madre qualora essa venga nascosta nella classe figlia, come nel caso di CONST nell’esempio
di qui sopra.
È importante ricordare che una classe può estendere una e una sola classe madre. Inoltre è
possibile limitare la disponibilita all’ereditarietà tramite la keyword final: una classe o un metodo
dichiarato come final non può essere estesa/o (mentre una variabile, al solito, non può essere
modificata).

8
Classi astratte e Interfacce
Vediamo ora come sono legate classi astratte e interfacce col concetto di ereditarietà.
Una classe astratta si comporta per lo più come una classe concreta, la principale differenza è che
una classe figlia deve (per forza di cose) implementare tutti i metodi dichiarati astrattamente nella
classe madre. Anche in questo caso una classe può estendere una e una sola classe astratta.
DOMANDA Ha senso dichiarare una classe astratta come final?
Per quanto riguarda le interfacce, esse non vengono estese bensı̀ implementate, in quanto esse
sono concettualmente un contratto che la classe implementante deve rispettare. Le interfacce
stanno quindi a metà tra il principio di ereditarietà e quello di polimorfismo. A differenza di una
classe astratta, nelle interfacce tutte le variabili sono (implicitamente) dichiarate come static e
final, di conseguenza una classe che implementa un’interfaccia può accedere a tali variabili ma non
modificarle. Per quanto riguarda i metodi, essi sono implicitamente public e astratti (solamente
dichiarati) e devono essere implementati dalla classe implementante. Ci sono tuttavia casi in cui
uno stesso metodo sia pensato per svolgere sempre la stessa funzione indipendentemente dalla
classe che implementa l’interfaccia; per questo motivo, da Java 8 in poi, nelle interfacce è possibile
scrivere dei metodi espliciti tramite la keyword default. Infine, bisogna ricordare che una classe
può implementare più di una interfaccia.

1.2.3 Polimorfismo
Il principio del polimorfismo può essere brevemente riassunto con la frase ”stesso nome, più forme”.
Messa semplicemente, il polimorfismo consente di svolgere una stessa azione in più modi differenti.
In java questo si esplicita principalmente in tre concetti:
1. Overload di Metodo
2. Override di Metodo

3. Java Generics
Vediamoli nel dettaglio:

Overload
L’overload consiste nel sovracaricare un metodo, ovvero nel dichiarare più metodi che abbiano lo
stesso nome ma type-signature differente. Java distingue questi metodi in base agli argomenti a
compile time. Vediamo un esempio:

import java.util.Date;

public class Persona {


private String nome;
private Date dataNascita;
protected final String CONST = "di Persona";

public Persona(String nome, String dataNascita) {


this.nome = nome;
this.dataNascita = dataNascita;
}

public Persona(String nome) {


this.nome = nome;
};

public void greetings() {


System.out.println("Ciao!"):
}

public String greetings(String nome) {


return "Ciao, " + nome;
}

9
In questo caso sia il costruttore che il metodo greetings() sono overloaded. NOTA BENE: affinchè
Java possa capire a quale dei metodi fare riferimento, è necessario che questi abbiano argomenti
di tipi diversi, altrimenti si avrà un errore di compilazione.

Override
L’override consiste nel sovrascrivere un metodo già implementato in una classe madre, mantenendo
la stessa type signature. A differenza dell’overload, in questo caso Java capisce a runtime quale
metodo chiamare in base alla dichiarazione dell’oggetto. Veidamo un esempio:

import java.util.Date;

public class Persona {

public void print() {


System.out.println("Persona");
}

public class Impiegato extends Persona {

@Override
public void print() {
System.out.println("Impiegato");
}
}

public class Test {


public static void main(String[] args) {

Impiegato ob1 = new Impiegato();


Persona ob2 = new Impiegato();
Persona ob1 = new Persona();

ob1.print(); // -> Impiegato


ob2.print(); // -> Impiegato
ob3.print(); // -> Persona

}
}

Generics
I generics permettono di passare il tipo di dati come argomento a metodi, classi e interfacce.
Usando i generici, è possibile creare classi che svolgano le stesse operazioni con diversi tipi di dati
senza dover ripetere codice. Una tale classe (/interfaccia/metodo) viene detta generica. Vediamo
nel dettaglio di cosa si tratta:

// Crea un’istanza di una classe generica


BaseType <Type> obj = new BaseType <Type>();

// Usiamo le parentesi uncinate <> per specificare un tipo generico


class Test<T> {
// Dichiariamo un oggetto di tipo T
T obj;
Test(T obj) { // costruttore
this.obj = obj;
}
public T getObject() { //getter
return this.obj;

10
}
}

// Utilizziamola
class Main {
public static void main(String[] args)
{
// Creiamo un’instanza di Test con tipo Integer
Test<Integer> iObj = new Test<Integer>(15);
System.out.println(iObj.getObject());

// Creiamo un’instanza di Test con tipo Stringa


Test<String> sObj
= new Test<String>("Contrader");
System.out.println(sObj.getObject());
}
}

//output: 15
// Contrader

La classe Test ha le stesse funzionalità sia che obj sia una string o un intero (o di qualsiasi altro
tipo), se non avessimo utilizzato i generici avremmo dovuto scrivere due (o più) classi diverse a
seconda del tipo desiderato!

Possiamo inoltre passare più di un tipo generico, ad esempio:

class Test<T, U>


{
T obj1;
U obj2;

Test(T obj1, U obj2)


{
this.obj1 = obj1;
this.obj2 = obj2;
}

public void print()


{
System.out.println(obj1);
System.out.println(obj2);
}
}

class Main
{
public static void main (String[] args)
{
Test <String, Integer> obj =
new Test<String, Integer>("Contrader", 15);

obj.print();
}
}

//output: Contrader
// 15

Vediamo infine come creare dei metodi generici:

11
class Test {
// Esempio di metodo generico
static <T> void genericDisplay(T element)
{
//Stampa tipo e valore del parametro
System.out.println(element.getClass().getName()
+ " = " + element);
}

public static void main(String[] args)


{
// Chiamiamo il metodo con un Integer
genericDisplay(11);

// Chiamiamo il metodo con una stringa


genericDisplay("Contrader");

// Chiamiamo il metodo con un Double


genericDisplay(1.0);
}
}

// java.lang.Integer = 11
// java.lang.String = Contrader
// java.lang.Double = 1.0

1.2.4 Incapsulamento
Per incapsulamento si intende il racchiudere un insieme di dati all’interno di una singola unità.
Alternativamente, si può pensare all’incapsulamento come a un ’guscio protettivo’ che previene
l’accesso diretto ai dati all’esterno del guscio stesso.
Tecnicamente, nell’incapsulamento, le variabili di una classe sono nascoste alle altre classi e l’accesso
a queste è possibile solamente attraverso dei metodi della classe guscio. Questo concetto è detto
anche data-hidding e in Java è realizzabile in Java tramite l’utilizzo della keyword private nella
dichiarazione della variabile o del metodo che si vuole incapsulare. tale keyword fa sı̀ che solamente
gli altri elementi della classe possano accedere direttamente alla proprietà, garantendo alle classi
esterne il corretto utilizzo della classe ma nascondendone l’effettiva struttura. In questo senso
l’incapsulamento si ricollega al principio di astrazione!
È best practice (i.e. fatelo!!!) dichiarare ogni variabile di una classe come private e gestirne
l’accesso esterno tramite i getter e i setter:

public class Demo {


private String password; //la pw viene incapsulata nella classe

public String getPassword() {


//eventuale codice aggiuntivo, ad es. per svolgere controlli
return this.password;
}

pubic void setPassword(String password) {


//come su, codice a piacere
this.password = password;
}
}

Ricordiamo infine che Java offre, oltre a private, altri tre modificatori d’accesso:
• public: se posto di accanto a una poprietà, questa è direttamente accessibile a una qualun-
que classe del progetto che importi la classe che la contiene. È ad esempio utilizzato nelle
dichiarazioni dei getter e setter.

12
• protected: può essere attribuito solo ai metodi e alle variabili interne alla classe e non può
essere applicato alla classe stessa. I metodi e le variabili dichiarate come protected sono
visibili da classi dichiarate nello stesso package e da sottoclassi e classi derivate dichiarate
ovunque.

• default: viene assegnato automaticamente dal compilatore solo quando nella scrittura del
sorgente si omettono gli altri modificatori. Di default indica che l’elemento è di accesso
pubblico ma solo ed esclusivamente al package della classe dove è inserito, diversamente da
public e protected dove è visibile anche in package esterni.
NOTA BENE: una classe può essere dichiarata private? Sı̀, ma solamente se è una classe interna,
ovvero una classe dichiarata all’interno di un’altra, che viene di fatto trattata come una proprietà
della classe esterna.

1.3 Classi astratte e Interfacce


Esaminiamo ora nel dettaglio i concetti di classe astratta e interfaccia, già introdotti nella sezione
precedente.

1.3.1 Classi Astratte


Una classe astratta è una classe che non può essere istanziata. Essa viene dichiarata tramite la
keyword abstract; tale keyword può anche essere applicata a metodi, ma non a variabili. Un
metodo astratto è un metodo non implementato, del quale si specifica solo la type-signature (i.e.
tipo e numero di argomenti e tipo restituito).
NOTA BENE: al contrario di quanto si possa trovare in giro, non è vero che una classe
astratta non può avere costruttore! Semplicemente un tale costruttore (cosı̀ come ogni altro
metodo) è accessibile solamente mediante una classe (concreta) che estende la classe astratta.
Vediamo un esempio:

public abstract class Persona {


protected String nome;

public Persona(String nome) {


this.nome = nome;
}

public abstract void greetings(); //metodo astratto

public class Impiegato extends Persona {

private Long id;

public Impiegato(String nome, Long id) {


super(nome); //QUI STO CHIAMANDO IL COSTRUTTORE DI PERSONA!!!
this.id = id;
}

@Override
public void greeting() {
System.out.println("Wewe amici del webb!");
}

Come notiamo dall’esempio, una classe astratta può contenere sia metodi astratti che non,
tuttavia se vi è anche solo un metodo astratto l’intera classe deve essere dichiarata astratta. Inol-
tre una classe che estende una classe astratta deve o implementare tutti i metodi astratti (e.g.
greetings() sopra) oppure essere dichiarata astratta essa stessa.

13
1.3.2 Interfacce
Un interfaccia può essere pensata come uno schema che definisce un insieme di caratteristiche
richieste ma che non ne esplicita l’implementazione. Al lato pratico, per dichiarare un’interfaccia
si usa la keyword Interface. Una classe può implementare un’interfaccia tramite la keyword im-
plements, affinchè l’implementazione sia corretta deve inoltre esplicitare tutti i metodi astratti
che eredita dall’interfaccia stessa. A differenza delle classi astratte, una classe può implementare
più di un’interfaccia.
OSS: se si implementano due interfacce che contengono un metodo con stesso nome, stessa signa-
ture per gli argomenti ma diverso tipo di return si avrà un errore, in quanto java non sarà in grado
di capire quale dei due chiamare! Se invece i due metodi hanno argomenti diversi non avremo
problemi, in quanto basterà fare un overload del metodo all’interno della classe.

Novità della JDK 8


Con la release delle JDK8 sono state introdotte due importanti novità per quanto riguarda le
interfacce:

1. La possibilità di esplicitare metodi all’interno di un’interfaccia tramite la keyword default.


2. La possibilità di definire metodi statici all’interno di un’interfaccia, i quali hanno lo stesso
comportamento dei metodi statici di una classe (i.e. possono essere invocati senza bisogno
di un’istanza concreta dell’interfaccia). N.B. questi metodi NON vengono ereditati da una
classe implementante.

Vediamo un esempio riassuntivo:

public interface Interfaccia {


void wewe();

//metodo statico, non ha bisogno di un’istanza per essere invocato


static void greetings() {
System.out.println("Wewe!");
}
}

public interface Interfaccia2 {


String wewe(String nome);

//metodo default, ha bisogno di un’istanza per essere invocato


default String saluto() {
return "Wewe";
}
}

public class Concreto implements Interfaccia,Interfaccia2 {

//notare come il metodo wewe() sia overloaded per la corretta //


implementazione
@Override
public void wewe() {
System.out.println("wewe");
}

@Override
public String wewe(String nome) {
return "Wewe " + nome;
}
}

Domanda il seguente codice è corretto? Se sı̀, che cosa otterrò in output?

14
public Interface Animale {
void verso();
}

public Class Cane implements Animale {


@Override
public void verso() {
System.out.println("WOF");
}
}

public Class Test {


public static void main(String[] args) {
Animale doggo = new Cane(); //occhio alla dichiarazione
doggo.verso();
}
}

Domanda il seguente codice è corretto? Se sı̀, che cosa otterrò in output?

public Interface Animale {


void verso();
}

public Class Cane implements Animale {


@Override
public void verso() {
System.out.println("WOF");
}

public String camminata() {


return "Trotto";
}
}

public Class Test {


public static void main(String[] args) {
Animale doggo = new Cane();
System.out.println(doggo.camminata());
}
}

Per approfondire le domande precedenti si rimanda a questo link (dopo avere sbattuto la testa
sul codice!).

1.4 Eccezioni
Un’eccezione è un evento inaspettato che interferisce con la normale esecuzione di un programma.
Java offre al programmatore la possibilità di catturare (catch) tali eccezioni: quando una di queste
avviene durante l’esecuzione di un metodo, viene creata un’istanza della classe Exception. Tale
oggettto contiene informazioni riguardanti l’eccezione, come il nome, la descrizione di cosa è andato
storto e lo stato del programma al momento del lancio di tale eccezione.
Alcune delle ragioni più comuni per cui un’eccezione viene lanciata includono:
• input invalido da parte dell’utente
• malfunzionamento del dispositivo
• assenza di connessione alla rete
• memoria insufficiente
• errori nel codice
• tentare di aprire un file non disponibile

15
1.4.1 Gerarchia delle eccezioni
Vediamo del dettaglio come le eccezioni vengono formalizzate in Java. Tutte le eccezioni estondono
la classe di Throwable, questa classe è alla base della gerarchia e ha due classi figlie: Exception e
Error. La differenza principale tra queste due è che un’eccezione viene utilizzata per indicare un
avvenimento non ordinario che il programma deve essere in grado di gestire, mentre un errore viene
utilizzato dalla JVM per indicare un problema con la JRE stessa che non deve essere normalmente
gestito dal programma, come ad esempio uno stack overflow (che corrisponde al StackOverflowEr-
ror).

Java contiene una vasta gamma di eccezioni già pronte all’uso (built-in), tuttavia è possibile
per il programmatore definire una eccezione personalizzata qualora ci fosse bisogno; vedremo tra
un attimo come fare ciò, occupiamoci però prima di descrivere le eccezioni built-in.
Una built-in exception può essere checked o unchecked.

Unchecked Exceptions
Sono le eccezioni che non vengono controllate dal compilatore: esse vengono rilevate a runtime e il
programma compilerà anche se non vengono gestite esplicitamente. A livello di gerarchia, tutte e
sole le unchecked exception sono quelle che estendono la classe RuntimeException. Un esempio di
una tale eccezione avviene quando proviamo a dividere per 0, come nel seguente esempio:

class DemoExc {
public static void main(String args[]) {
int x = 0;
int y = 10;
int z = y / x;
}
}

Output: Exception in thread ”main” java.lang.ArithmeticException: / by zero at Main.main(Main.java:5)


Java Result: 1
OSS: nonostante non diano problemi di compilazione, non vuol dire che non dovrebbero essere
gestite!

Checked Exceptions
Sono le eccezioni che vengono controllate durante la compilazione. Se all’interno di un metodo
è presente del codice che potrebbe lanciare una o più di tali eccezioni, tale metodo deve o ge-
stirle internamente oppure nella dichiarazione deve essere utilizzata la keyword throws seguita
dalle eccezione che vengono lanciate. La gestione interna avviene tramite l’utilizzo di un blocco
try/catch/finally; la sintassi di tale blocco è la seguente:

16
public void myMethod() {
try{
//qui va inserita la porzione di codice da controllare, supponiamo
ad esempio che vengano lanciate Eccezione1, Eccezione2, ... ,
EccezioneN
}
catch(Eccezione1 e) {
//qui va inserito il codice da eseguire quando viene lanciata l’
Eccezione1
}
catch(Eccezione2 e) {
//qui va inserito il codice da eseguire quando viene lanciata l’
Eccezione2
}
.
.
.
catch(EccezioneN e) {
//qui va inserito il codice da eseguire quando viene lanciata l’
EccezioneN
}
finally {
//qui va inserito il codice da eseguire in ogni caso anche se viene
lanciata un’eccezione
}
}

Consideriamo ad esempio il seguente programma che apre il file a.txt e ne stampa le prime tre righe,
ma supponiamo di fornire un path sbagliato per tale file. Avremmo in questo caso errore di compi-
lazione, in quanto utilizziamo il metodo FileReader() che lancia FileNotFoundException. Notiamo
che utilizziamo anche i metodi readLine() e close(), i quali a loro volta lanciano IOException.

class Demo {

public static void main(String[] args) {

try {
FileReader file = new FileReader("C:\\LocationFake\\a.txt");

BufferedReader fileInput = new BufferedReader(file);

// Stampo le prime tre righe


for (int counter = 0; counter < 3; counter++) {
System.out.println(fileInput.readLine());
}
// chiudo la connessione
fileInput.close();
}
catch (FileNotFoundException e) {
e.printStackTrace();
//o anche System.out.println(e.getMessage());
}
catch (IOException e) {
String msg = e.getMessage();
System.out.println(msg);
}
finally {
System.out.println("Nel blocco finally");
}

}
}

17
OUTPUT col metodo .printStackTrace():
java.io.FileNotFoundException: C:\LocationFake\a.txt (Impossibile trovare il
percorso specificato)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111)
at java.base/java.io.FileReader.<init>(FileReader.java:60)
at it.demo.Demo.main(Demo.java:23)
Nel blocco finally

OUTPUT col System.out.println(e.getMessage()):


C:\LocationFake\a.txt (Impossibile trovare il percorso specificato)
Nel blocco finally

OSS: in alternativa avremmo potuto gestire il tutto tramite throws, in questo caso il metodo
utilizzato implicitamente per il display è il .printStackTrace()

class Demo {
public static void main(String[] args) throws IOException{
// cerco di leggere il file
FileReader file = new FileReader("C:\\LocationFake\\a.txt");

BufferedReader fileInput = new BufferedReader(file);

// Stampo le prime tre righe


for (int counter = 0; counter < 3; counter++) {
System.out.println(fileInput.readLine());
}
// chiudo la connessione
fileInput.close();
}
}

OUTPUT:
java.io.FileNotFoundException: C:\LocationFake\a.txt (Impossibile trovare il
percorso specificato)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111)
at java.base/java.io.FileReader.<init>(FileReader.java:60)
at it.demo.Demo.main(Demo.java:23)

Domanda: come mai nella dichiarazione abbiamo specificato solamente IOException?


Perchè FileNotFoundException è una sottoclasse di IOException. In teoria avremmo potuto scri-
vere solamente ”throws Exception”, tuttavia è buona norma adottare un buon grado di precisione
per rendere il codice più facile da comprendere.

1.4.2 Gestione delle eccezioni da parte della JVM


Java offre una gestione di default delle eccezioni integrata nella JVM. Ogniqualvolta un’eccezione
accade all’interno di un metodo, il metodo crea un’istanza di quell’eccezione (i.e. un oggetto di
tipo Exception) e la passa alla JVM.
Viene quindi messa in atto la seguente procedura:
1. la JVM ottiene il callstack dall’Eccezione e lo usa per individuare il metodo che contiene il
blocco di codice che può gestire tale eccezione. Questo blocco viene detto Exception Handler.
2. la JVM risale alla fonte dell’errore scorrendo il callstack all’indietro.

18
3. se trova un handler adatto (ad esempio un blocco try/catch coerente col tipo dell’eccezione)
passa ad esso l’eccezione.
4. altrimenti passa l’eccezione all’handler di default (interno alla JVM), il quale stampa lo
stacktrace e termina il programma.

1.4.3 User Defined Exceptions


Java consente al programmatore di creare eccezioni personalizzate. Per fare ciò si deve estendere
la classe Exception: la nostra eccezione sarà quindi per forza checked e pertanto va gestita come
specificato in precedenza.

public class MyException extends Exception {


public MyException(String errorMessage) {
super(s);
}
}

Nel costruttore passiamo al costruttore di Exception il messaggio che vogliamo venga mostrato
quando questa eccezione viene lanciata.
Quando in un metodo vogliamo lanciare tale eccezione (che deve essere specificata nella dichiara-
zione del metodo) ne creiamo una nuova istanza e poi la lanciamo tramite la keywor throw (senza
la S!!!).

public class MyClass {


public void myMethod(String nome) {
if (nome.equals("Nome invalido che non vogliamo")) {
MyException e = new MyException("Nome invalido");
throw e;
//o anche direttamente
// throw new MyException("Nome invalido");
}
else { System.out.println(nome); }
}
}

1.5 Java Collections e Map


Una collection è fondamentalmete un gruppo di oggetti individuali rappresentati come una singola
unità. A partire dalla JDK 1.2, Java mette a disposizione un framework apposito, nel quale sono
contenute tutte le classi e interfacce relative alle collections. Le due principali interfacce di questo
framework sono la java.util.Collection e la java.util.Map, in base alle quali vengono implementate
tutte le altri classi/interfacce del framework.
Ecco alcuni dei vantaggi che si hanno utilizzando questo framework:
1. Consistenza dell’API: abbiamo infatti a disposizione sia delle interfacce (ad esempio List)
che delle classi che le implementano (ad esempio ArrayList), condividendo di conseguenza
un insieme di metodi comuni con tutte le altre implementazioni.
2. Facilità di programmazione: noi programmatori non ci dobbiamo preoccupare della struttura
della Collection, possiamo quindi concentrarci su come usarla al meglio nel nostro programma.
(Mette quindi in pratica il principio di astrazione)
3. Maggiori prestazioni e qualità: il framework ci fornisce delle implementazioni degli algoritmi
e delle strutture dati ad alte prestazioni. Possiamo quindi direttamente utilizzare queste
implementazioni ottenendo il miglior risultato col minimo sforzo.
Riportiamo per completezza uno schema della gerarchia interna al collections framework, con-
centrandoci solamente sulle funzionalità maggiormente utilizzate e rimandando alla documentazio-
ne per una trattazione esaustiva.

19
1.5.1 Collection
L’interfaccia Collection è dichiarata come

public interface Collection<E> extends Iterable<E>

Essa eredita il metodo .forEach() e espone i seguenti:

20
Metodi di Collection
Metodo Return Funzionamento
add(E e) boolean Aggiunge l’elemento e alla collection
addAll(Collection<? extends add(E e) Aggiunge alla collection tutti gli ele-
E> c) menti di c
clear() void Rimuove tutti gli elementi dalla collec-
tion
contains(Object o) boolean Restituisce true se o fa parte della col-
lection
containsAll(Collection< ? ex- boolean Restituisce true se c è una sottocolle-
tends E> c) zione della collezione data.
equals(Object o) boolean Compara la collection e o per vedere se
costituiscono ’effettivamente’ lo stesso
oggetto
hashCode() int Restituisce l’hashcode per la collection
isEmpty() boolean Restituisce true se la collezione non
contiene alcun elemento
iterator() Iterator<E> Restituisce l’iteratore un iteratore sugli
elementi della collection
(def.) parallelStream() Stream<E> Restituisce uno Stream parallelo che ha
la collection come fonte
remove(Object o) boolean Rimuove una singola istanza di o dalla
collection
removeAll(Collection<?> c) boolean Rimuove tutti gli elementi di c dalla col-
lection
(def.) removeIf (Predicate< ? boolean Rimuove gli elementi della collection
extends E> filter) che soddisfano filter
retainAll(Collection< ? ex- boolean Mantiene solamente gli elementi della
tends E> c) collection che stanno anche in c
size() int Restituisce il numero di elementi della
collection
(def.) splitIterator() SplitIterator<E> Crea uno SplitIterator sugli elementi
della collection
(def.) stream() Stream<E> Restituisce una stream che ha come fon-
te la collection
toArray() Object[ ] restituisce un array contenente gli ele-
menti della collection
toArray(T[ ] a) <T> T[ ] Restituisce un array di tipo T contenen-
te gli elementi della collection. OSS:
l’argomento a serve puramente per in-
dicare il tipo desiderato

Liste
List è un’interfaccia figlia di Collection. È dedicata alla gestione ordinata di oggetti, permettendo
la presenza di oggetti duplicati. Tale interfaccia è implementata da varie classi, come ad esempio
ArrayList e Vector. Poichè ciascuna di queste classi implementa List, possiamo istanziare una lista
utilizzando una qualsiasi di queste dichiarazioni:

List <T> al = new ArrayList<> ();


List <T> ll = new LinkedList<> ();
List <T> v = new Vector<> ();

Vediamo più nel dettaglio le classi che implementano List:


• ArrayList: questa classe fornisce degli array dinamici in Java, particolarmente utili quando
si ha necessità di manipolare uno stesso array durante il programma. La dimensione di un Ar-
rayList viene scalata dinamicamente quando si aggiungono o rimuovono elementi. Provvede
inoltre un accesso agli oggetti diretto, senza necessità di dover scorrere l’intera lista.

21
• LinkedList: essa implementa la struttura dati lista (doppiamente) concatenata in java,
ovvero una struttura dati lineare nella quale gli oggeti sono salvati come nodi, ognuno dei
quali provvede un puntatore all’elemento successivo e uno a quello precedente.
• Vector: è identico ad ArrayList nell’implementazione, con la sola differenza di essere sincrono
(mentre ArrayList è asincrono).
• Stack: è una sottoclasse di Vector e impementa la struttura dati dello Stack, seguendo il
principio FILO (first in last out). Oltre ai classici metodi push e pop, essa provvede i tre
metodi aggiuntivi empty, search e peek.

Set
Set è un’interfaccia figlia di Collection, che rappresenta una collezione non ordinata di elementi non
duplicati. È particolarmente utile quando si vogliono memorizzare solamente valori unici evitando
ripetizioni. Vome per le liste, un Set è istanziabile creando un’istanza di una sua qualsiasi classe
implementante, come ad esempio:

Set<T> hs = new HashSet<> ();


Set<T> lhs = new LinkedHashSet<> ();
Set<T> ts = new TreeSet<> ();

Le classi che implementano Set sono:


• HashSet implementa la struttura dati hash table. Non dà garanzia che gli oggetti vengono
inseriti all’interno nello stesso ordine con cui vengono passati, in quanto l’inserimento avviene
in base al loro hashcode. Permette inoltre l’inserimento di elementi NULL.

public static void main(String[] args) throws IOException{

HashSet<String> mySet = new HashSet<>();


mySet.add("Wewe");
mySet.add("amici del");
mySet.add("webbe");

//Usiamo un iterator per vederlo una volta nella vita :)


Iterator<String> myIter = mySet.iterator();

while (myIter.hasNext()) {
System.out.println(myIter.next());
}

//OUTPUT: Wewe
// webbe
// amici del

• LinkedHashSet: simile ad HashSet, utilizza le liste doppiamente concatenate per salvare


gli oggetti e ne garantisce l’ordine.

SortedSet
È un’interfaccia che estende Set, fornendo metodi per la gestione dell’ordinamento degli elementi.
Viene implementata dalla classe TreeSet, che usa un albero per il salvataggio degli oggetti e
gestisce l’ordine o in base all’ordine naturale degli oggetti (che deve essere coerente col metodo
equals) oppure tramite un Comparator che può essere fornito come argomento del costruttore.

22
1.5.2 Map
Una mappa è una struttura dati che contiene delle coppie (chiave, valore). Le chiavi devono essere
uniche per evitare ambiguità, in quanto le chiavi vengono utilizzate per accedere al rispettivo valore.
Una mappa è utile quando si vogliono gestire dei dati complessi in base alle chiavi, tipcamente più
semplici. L’interfaccia è dichiarata come segue e una sua istanza può essere ottenuta tramite le
classi implementanti, ad esempio:

Interface Map<K,V>;

Map<K,V> hm = new HashMap<> ();


Map<K,V> tm = new TreeMap<> ();

Le due principali classi che implementano Map sono:


• HashMap: fornisce un implementazione di base dell’interfaccia Map. Immagazzina i dati
in base al loro hashcode e ne consente l’accesso tramite le rispettive chiavi. Il salvataggio
dei valori avviene tramite una funzione di hash, che consente di mappare in maniera quasi
biunivoca (’resistente alle collisioni ’) le stringhe corrispondenti agli indirizzi di memoria in
stringhe di dimensioni più piccole, garantendo quindi maggiore efficienza e prestazioni delle
operazioni.
Consente inoltre la presenza di (al più) una chiave e di vari valori NULL. È interessante
sapere che una HashMap è utilizzata internamente da un HashSet.
• TreeMap: a differenza della HashMap, essa immagazzina i dati in un albero rosso-nero in
maniera ordinata. Come per i tree set l’ordine di default è quello naturale, ma l’utilizzo di
un comparatore custom è consentito tramite l’apposito costruttore.

1.6 Stream e Programamzione Funzionale


Un’altra grande novità introdotta dalla JDK 8 è l’arrivo della programmazione funzionale su Java.
La programmazione funzionale è un paradigma di programmazione in cui il flusso di esecuzione
del programma assume la forma di una serie di valutazioni di funzioni matematiche. Il punto di
forza principale di questo paradigma è la mancanza di side-effects delle funzioni, il che comporta
una più facile verifica della correttezza e della mancanza di bug del programma e la possibilità di
una maggiore ottimizzazione dello stesso.
In Java ciò viene realizzato principalmente in tre modi, tutti quanti introdotti dalla JDK 8:
1. Lambda Functions
2. Method References
3. Java Streams
Vediamoli nel dettaglio uno per uno:

1.6.1 Lambda Functions


Le funzioni lambda sono essenzialmente delle funzioni anonime, ovvero delle funzini esplicitate
direttamente quando vi è necessità, senza averle dichiarate in precedenza. Più nel dettaglio, le
lambda esprimono un’istanza di un’interfaccia funzionale, ovvero un’interfaccia che contiene un
singolo metodo (astratto), come ad esempio java.lang.Runnable. La sintassi di una funzione lambda
è la seguente:
x −→ f (x)
(x, y) −→ g(x, y)
(x1 , . . . , xn ) −→ h(x1 , . . . , xn )
Dove (x1 , . . . , xn ) sono gli argomenti in input e h(x1 , . . . , xn ) è ciò che viene restituito in output.
Ad esempio, la seguente lambda prende in input un parametro x e restituisce in output x+2

x -> x+2 //in questo caso f(x) = x+2

23
Le lambda sono inoltre utili in quanto permettono di snellire notevolmete la dichiarazione di una
funzione, consideriamo ad esempio il seguente codice, nel quale dichiariamo una stessa funzione in
due modi:

Function<Integer,Integer> myFunction = new Function<Integer, Integer>() {


@Override
public Integer apply(Integer integer) {
return integer+2;
}
};

Function<Integer,Integer> myFunction2 = x->x+2;

OSS: nella dichiarazione di myFunction abbiamo utilizzaato una classe anonima, che ci consente
di dichiarare e istanziare una classe allo stesso tempo, rendendo il codice più conciso. Per maggiori
dettagli, si rimanda a questo link.

Le lambda ci offrono le seguenti funzionalità:


1. Una funzione che può essere creata senza appartenere a nessuna classe.

2. Se abbiamo necessità di passare una Function come argomento di un metodo (ad esempio
nel .map() delle stream, come vedremo tra un attimo), possiamo utilizzare una lambda.

1.6.2 Method References


Le method references sono delle espressioni che ci consentono di far riferimento a un metodo già
definito all’interno di una data classe madre in maniera coincisa e chiara. Sono preferibili rispetto
a una lambda nel caso in cui questa chiami solamente un metodo già esistente.
La sintassi delle referenze di metodo è la seguente:

ClasseMadre::nomeDelMetodo

Le tramite le referenze di metodo ci consentono di referenziare sia metodi statici che non. In que-
st’ultimo caso ci dobbiamo assicurare che la classe madre sia istanziata in precedenza. Vediamo ora
un esempio volutamente artificioso per illustrarne il funzionamento; vedremo esempi più concreti
(e utili) con le stream.

public class Esempio {

public static Float dimezza(Integer x) {


return ((float) x)/2;
}

public static void test(Integer input, Function<Integer,Float> function) {


System.out.println(function.apply(input));
}

public static void main(String[] args) {

test(2,Esempio::dimezza);

}
}

Nell’esempio di sopra, test prende come argomenti un input di tipo Integer e una funzione (adatta
all’input) e stampa il risultato della funzione applicata all’input. Quando chiamiamo test possiamo
quindi passare in input il metodo dimezza() tramite referenza di metodo.

24
1.6.3 Java Streams
Una stream è una sequenza di oggetti che supporta vari metodi, i quali possono essere composti
nella stream-pipeline per produrre il risultato desiderato. Le caratteristiche principali delle stream
sono:

1. non sono una struttura dati, bensı̀ vengono generate da una di queste, come ad esempio una
Collection o un Array. Di conseguenza le stream non possono essere usate per la gestione dei
dati, ma solamente per la manipolazione di dati già esistenti. È importante ricordare che una
stream non fornisce alcun accesso diretto ai dati in circolo (e.g. non esiste un’indicizzazione)
La sintassi per generare aprire una stream è la seguente:

• nel caso di una collection è sufficiente chiamare il metodo .stream() sull’oggetto:

List<Integer> myList = new ArrayList<>();


//popolo la lista
Stream<Integer> myStream = myList.stream();

• Nel caso di un array si utilizza invece il metodo statico .stream() della classe Arrays, il
quale prende in input un array e lo converte in una stream dello stesso tipo:

Integer arr = new Integer[];


//popolo l’array
Stream<Integer> myStream = Arrays.stream(arr);

ATTENZIONE: in questo caso è importante che l’array non contenga tipi primiti-
vi, in quanto l’interfaccia Stream è generica e di conseguenza prende come parametro
un oggetto. È quindi buona norma usare i rispettivi type-wrappers quando si ha in
mente di adottare un approccio funzionale al codice.
2. Le stream non cambiano la struttura dati originale, forniscono solamente il risultato dei
metodi della pipeline.
Le operazioni applicabili alle stream si dividono in due macrogruppi:
1. Operazioni intermedie: restituiscono come output una stream dello stesso tipo di quella su cui
vengono applicate, possono quindi essere concatenate per spezzare un’operazione complessa
nella composizione di tante piccole operazioni
2. Operazioni terminali: restituiscono diversi (o nessun) output, di conseguenza chiudono la
stream.
È utile sapere che le operazioni intermedie sono lazy, ovvero non vengono eseguite finchè un’ope-
razione terminale non viene chiamata.
Vediamo ora le operazioni principali.

Operazioni intermedie
• .map(): prende in input una Function <T,V> dove T deve essere compatibile col tipo della
stream (ovvero deve essere o lo stesso tipo oppure una sua superclasse). Restituisce in output
una stream di tipo V (lo stesso tipo dell’ output della funzione), che ha in circolo il risultato
della function sugli elementi della stream iniziale.
Vediamo un esempio:

List<Integer> myList = new ArrayList<>(Arrays.asList(1,2,3,4));


List<String> myList2 = myList.stream() //apro la stream pipe
//mappo in str
.map(Object::toString)
//concateno la stringa " Ciao" a ogni //elemento
.map(x -> x.concat(" Ciao"))
//operazione terminale da inserire qu

25
.[...]
System.out.println(myList2);

//output: [1 Ciao, 2 Ciao, 3 Ciao, 4 Ciao]

Fate attenzione all’utilizzo delle method reference e delle lambda come argomento della map!
(Nella prima map si è dovuto utilizzare Object come classe contenitore in quanto la referenza
Integer::toString è ambigua.)

• .filter(): prende in input un Predicate<T>, dove T è compatibile col tipo della strem e
restituisce in output una stream dello stesso tipo di quella iniziale, composta dagli elementi
che soddisfano il predicato (ovvero dali elementi che dati in input al predicato danno true in
output). Si ricorda che un predicato altro non è che una funzione a valori booleani. Di fatto
reduce viene quindi utilizzata per scremare i valori in base a una proprietà desiderata. Ad
esempio, supponiamo di voler fare la stessa operazione dell’esempio precedente ma solamente
sugli elementi pari della lista:

List<Integer> myList = new ArrayList<>(Arrays.asList(1,2,3,4));


List<String> myList2 = myList.stream() //apro la stream pipe
//filtro e tengo solo i pari
.filter(s->s%2==0)
//mappo in str
.map(Object::toString)
//concateno la stringa " Ciao" a ogni elemento
.map(x -> x.concat(" Ciao"))
//operazione terminale da inserire qu
.[...]
System.out.println(myList2);

//output: [2 Ciao, 4 Ciao]

Anche in questo caso, essendo un predicato fondamentalmente una funzione, osserviamo come
l’utilizzo delle lambda ci consenta di scrivere un codice snello e facilmente leggibile.

Operazioni Terminali
• .collect() prende come input un Collector, ovvero un oggetto che si occupa di raccogliere
gli oggetti della stream in una particolare struttura dati. La classe Collectors contiene al
suo interno vari metodi statici che ci forniscono un tale collector, come ad esempio Collec-
tors.toList() e Collectors.toSet(). Per maggiori dettagli ri rimanda, al solito, alla documen-
tazione.
Vediamo quindi come possiamo utilizzare il collect per completare l’esempio precedente:

List<Integer> myList = new ArrayList<>(Arrays.asList(1,2,3,4));


List<String> myList2 = myList.stream() //apro la stream pipe
//filtro e tengo solo i pari
.filter(s->s%2==0)
//mappo in str
.map(Object::toString)
//concateno la stringa " Ciao" a ogni elemento
.map(x -> x.concat(" Ciao"))
.collect(Collectors.toList());
System.out.println(myList2);

//output: [2 Ciao, 4 Ciao]

NOTA: a partire da Java 16 è disponibile anche l’ operazione terminale toList(). La


differenza rispetto a .collect(Collectors.toList()) è chela lista che restituisce in output è
immutabile.

26
• .forEach() prende come argomento un Consumer<T>, dove T è compatibile col tipo della
stream, e ha void come output-type. Consumer è un’interfaccia funzionale che espone il
metodo .accept(), di conseguenza, anche in questo caso, possiamo usare sia delle lambda che
delle referenze di metodo come argomento del forEach(). Al lato pratico, il forEach() esegue
l’operazione specificata su ogni elemento della stream; supponiamo ad esempio, nell’esempio
precedente, di voler semplicemente stampare i risultati delle operzioni intermedie, senza
salvarli. Per farlo possiamo fare cosı̀:

List<Integer> myList = new ArrayList<>(Arrays.asList(1,2,3,4));


myList.stream() //apro la stream pipe
//filtro e tengo solo i pari
.filter(s->s%2==0)
//mappo in str
.map(Object::toString)
//concateno la stringa " Ciao" a ogni elemento
.map(x -> x.concat(" Ciao"))
.forEach(System.out::println);
// o anche: .forEach(s -> System.out.println(s))

//output: 2 Ciao
// 4 Ciao

• .reduce() prende come input un oggetto di tipo T, con T compatibile con il tipo della stream,
e un BinaryOperator<T>, ovvero una funzione che prende in inut due argomenti di tipo T
e restituisce in output un oggetto ancora di tipo T. (Nota: questa è una semplificazione per
scopi didattici. In realtà il metodo reduce è overloaded e ammatte altre due signature, per
approfondire si rimanda alla documentazione). Vediamo con mano come funziona: suppo-
niamo di voler ottenere la somma degli elementi di una stream di interi, possiamo utilizzare
reduce come segue

List<Integer> myList = new ArrayList<>(Arrays.asList(1,2,3,4));


Integer myInt = myList.stream()
.reduce(0, (ans,x) -> ans += x);
System.out.println(myInt);

//output: 10

Smembriamo il codice: il primo argomento che passiamo al reduce indica il valore iniziale
che si vuole dare al risultato.
Poichè se la stream non contiene dati vogliamo ottenere 0 come somma, è sensato assegnare
proprio questo come valore iniziale.
Come secondo argomento abbiamo passato invece una lambda che prende due input (rispet-
tivamente ans e x) e aggiorna ans sommandoci x.
Quando il reduce viene chiamato, ans viene automaticamente inizializzato al valore passato
da noi come valore iniziale (in questo caso 0), in seguito l’operatore binario viene eseguito
tante volte quante sono gli elementi della stream. Nel nostro caso, aggiunge di volta in volta
gli elementi della stream ad ans, la quale alla fine dell’iterazione avrà come valore proprio la
somma.
E se avessimo voluto ottenere la somma degli elementi e poi sommarci uno? Semplice, basta
passare 1 come valore iniziale:

List<Integer> myList = new ArrayList<>(Arrays.asList(1,2,3,4));


Integer myInt = myList.stream()
.reduce(1, (ans,x) -> ans += x);
System.out.println(myInt);

//output: 11

27
Per finire, vediamo come possiamo utilizzare il reduce per concatenare le stringhe dell’esempio
iniziale: (notare come in questo caso i valore iniziale sia la stringa vuota)

List<Integer> myList = new ArrayList<>(Arrays.asList(1,2,3,4));


String concatenata = new String();
concatenata = myList.stream() //apro la stream pipe
//filtro e tengo solo i pari
.filter(s->s%2==0)
//mappo in str
.map(Object::toString)
//concateno la stringa " Ciao" a ogni elemento
.map(x -> x.concat(" Ciao"))
//concateno tutte le nuove stringhe
.reduce("", (ans,s) -> ans = ans.concat(" "+s));
System.out.println(concatenata);

//output: 2 Ciao 4 Ciao

Consiglio: il miglior modo per imparare a fare qualsiasi cosa è sporcarsi le mani: questa era
solamente un’introduzione alla programmazione funzionale, integratela con una buona dose di
ricerche personali e soprattutto provate a aggiornare un codice già esistente sostituendo tutti i cicli
for presenti con delle stream equivalenti. Buona fortuna! :)

28
Capitolo 2

Database

Un databse è semplicemente un insieme di dati salvati su una macchina che possono essere con-
sultati in vari modi. In questo capitolo presenteremo i database relazionali e non, esaminando le
differenze tra i due.

2.1 DB Relazionali o SQL


I database SQL organizzano i dati implementando un modello relazionale tramite l’utilizzo di tabel-
le. Ogni tabella corrisponde a un tipo preciso di dato, come ad esempio un impiegato di un’azienda.
Ogni colonna rappresenta una proprietà di tale dato, come ad esempio il nome dell’impiegato o il
suo salario. Infine, ogni riga rapresenta un dato concreto e particolare, con le sue proprietà. Questi
database utiizzano SQL (Structured Query Language) per l’accesso ai dati.
Tramite le query si possono effettuare ricerche particolarizzate sulle tabelle, specificando le condi-
zioni su una o più proprietà che devono essere soddisfatte dall’oggetto della nostra ricerca.
Ogni record di una tabella deve avere una proprietà che lo identifica univocamente all’interno della
tabella stessa. Tale proprietà è detta primary key. In questo tipo di database è inoltre possibile
definire delle relazioni tra tabelle diverse tamite delle chiavi esterne, che vanno a legare una
proprietà di un record della tabella ’proprietaria’ (si dice che la tabella è l’owning side di tale
relazione) ad un record della tabella a cui tale chiave fa riferimento (inverse side).
Alcuni esempi di questo tipo di database sono:
• Oracle
• Microsoft SQL Server
• PostgreSQL

• MySql
• MariaDB
• IBM DB2

2.2 DB Non Relazionali o NO-SQL


Rispetto a un database relazionale, un database NO-SQL ha una struttura dati meno stretta e
permette maggiore flessibilità e adattabilità. Essi sono quindi utili quando si ha a che fare con
dei dati eterogenei e non facilmente strutturabili in un modello relazionale, quindi difficilmente
rappresentabili in un sistema di tabelle.
I database non relazionali hanno come struttra base il file, ogni record è infatti un file a sè,
all’interno del quale sono contenute tutte le informazioni relative a esso. Per questo motivo, i
database non relazionali sono spesso più performanti rispetto a quelli SQL, i quali spesso per
recuperarre un singolo record devono accedere a più tabelle.
Alcuni esempi di questo tipo di database sono:

• MongoDB
• Google Cloud Firestore

29
• Cassandra
• Redis
• Apache HBase

• Amazon DybamoDB

2.3 SQL
Concentriamoci ora sui database relazionali, rinfrescando innanzitutto le operazioni di base di SQL
tramite delle query esempio:

16 select * from corso; /*seleziona tutte le righe della tabella corso*/


17 select * from corso order by id asc; /*ordina i risultati in modo asc in
base all’id*/
18 select * from corso where id >= 12;
19 select * from corso where id >= 12 and nome like ’matematica’;
20 delete * from corse where id between 1 and 13;
21 /*etc

2.3.1 Join
Vediamo in particolare l’operazione JOIN. Questa operazione è utilizzata per combinare tra loro
dati appartenenti a due o più tabelle distinte in base a una proprietà (leggere ’colonna’) comune a
tutte quante. Un JOIN può essere dei seguenti tipi:
1. INNER JOIN

2. LEFT JOIN
3. RIGHT JOIN
4. FULL JOIN

Nel seguito seguiremo questo esempio (non avevo voglia di rifare le tabelle, scusate per i nomi non
esattamente facili :) )

Inner Join
Questa operazione utilizza la keyword INNER JOIN (o anche semplicemente JOIN, è equiva-
lente) per selezionare tutte le righe che soddisfano le condizioni specificate in entrambe le tabelle.
Il result set sarà quidni formato da tali righe.
Capiamo meglio con un esempio: supponiamo di avere le due tabelle Student e StudentCourse

Facciamo una inner join delle due tabelle, combinando la colonna ROLL NO di entrambe le tabelle.

30
22 SELECT StudentCourse.COURSE_ID, Student.NAME, Student.AGE FROM Student
23 INNER JOIN StudentCourse
24 ON Student.ROLL_NO = StudentCourse.ROLL_NO;

Il result set sarà dato da

Left Join e Right Join


Queste due funzionano essenzialmente allo stesso modo. Trattiamo quindi il left join, e lasciamo
come esercizio il right join. (Sostituite left con right e ci siamo).
Questa perazione utilizza la keyword LEFT JOIN per restituire TUTTE le righe della tabella
a SINISTRA del join combinando la proprietà specificata se nella tabella a destra è presente un
match, inserendo NULL altrimenti. Continuando lo stesso esempio, la seguente query:

25 SELECT Student.NAME,StudentCourse.COURSE_ID
26 FROM Student
27 LEFT JOIN StudentCourse
28 ON StudentCourse.ROLL_NO = Student.ROLL_NO;

produce il seguente result set:

Notare come in corrispondenza di NIRAJ non ci sia un valore del COURSE ID, in quanto il suo
ROLL NO (8) non è presente nella tabella a destra.

Full Join
Questo join utilizza la keyword FULL JOIN per combinare di fatto un LEFT e un RIGHT JOIN.
Il result set sarà quindi formato da tutte le righe di tutte e due le tabelle, quando possibile (ovvero
quando la proprietà specificata matcha tra due righe) queste righe verranno combinate tra loro,
mentre per le righe in cui non si ha un match tale proprietà verrà messa NULL.

29 SELECT Student.NAME,StudentCourse.COURSE_ID
30 FROM Student
31 FULL JOIN StudentCourse
32 ON StudentCourse.ROLL_NO = Student.ROLL_NO;

31
2.3.2 Relazioni tra tabelle: Foreign Key
Una foreign key è una colonna che fa riferimento a un record di un’altra tabelle tramite la primary
key di questo. Viene quindi utilizzata per mostrare le relazioni tra le tabelle e fa da ponte tra
queste. La tabella in cui una foreign key viene definita è l’owning side della relazione, mentre
la tabella che viene referenziata dalla foreign key è l’ opposite side. Una foreign key può essere
definita nei seguenti modi:

33

34 /*appena si dichiara la colonna*/


35 nome_colonna tipo_colonna FOREIGN KEY REFERENCES TabellaEsterna(tab_est_PK)
36

37 /*alla fine delle dichiarazioni delle colonne, ad esempio*/


38 CREATE TABLE Orders (
39 OrderID int NOT NULL,
40 OrderNumber int NOT NULL,
41 PersonID int,
42 PRIMARY KEY (OrderID),
43 CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID) REFERENCES Persons(
PersonID)
44 );

Proprietà
1. Una record non può essere aggiornato se esistono delle FK che lo puntano (almeno per come
abbiamo dichiarato le chiavi sopra, vedremo tra un attimo come rimediare)
2. una FK deve referenziare una la PK della tabella a cui punta

3. La FK deve essere dello stesso tipo della PK a cui punta

La proprietà 1 definisce un vincolo molto forte nella relazione. Nella maggiorparte dei ca-
si vorremmo evitare una tale restrizione, per farlo si utilizzano rispettivamente le keyword ON
DELETE e ON UPDATE subito dopo la dichiarazione fatta sopra, ad esempio:

46

47 CREATE TABLE Orders (


48 OrderID int NOT NULL,
49 OrderNumber int NOT NULL,
50 PersonID int,
51 PRIMARY KEY (OrderID),
52 CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID) REFERENCES Persons(
PersonID)
53 ON DELETE CASCADE
54

55 );

Queste keyword specificano cosa fare quando il record referenziato viene rispettivamente cancellato
e modificacto. I vari comportamenti e le loro rispettive keyword sono i seguenti:
1. CASCADE: è la più utilizzata, in quanto specifica che le modifiche effettuate sulla PK puntata
si debbano propagare alla tabella contenente la FK.

2. RESTRICT: è il comportamento che si ha di default. Non permette la modifica di un record


puntato da una FK.
3. NO ACTION: uguale a RESTRICT.
4. SET NULL: setta la FK a NULL se si modifica ciò che referenziava in precedenza. Ciò è utile
ad esempio quando si vogliono mantenere dei record anche quando la tabella a cui facevano
riferimento viene cancellata.

32
2.3.3 Tipi di relazione
Ci sono tre tipi di relazioni principali:
1. One-to-one: si ha quando ogni riga nella tabella1 ha solamente una riga a lei legata nella
tabella2. Ad esempio ogni azienda ha un solo CEO, un impiegato ha un solo ufficio etc.
2. One-to-many: è il tipo di relazione più comune. Si ha quando un record nella tabella1 è
legato a più record della tabella2 MA un record nella tabella2 è legato al più ad un record
della tabella1. Ad esempio: un libro contiene più capitoli ma un capitolo fa parte di un solo
libro.

3. Many-to-many: si ha quando un record nella tabella1 è legato a più record della tabella2 e
viceversa. Ad esempio: un supermarket vende prodotti prodotti di più marche e una stessa
marca può essere venduta da più supermercati diversi.

33
Capitolo 3

Spring

Spring Framework (o più brevemente Spring) è un framework java specificamente pensato per ri-
durre il carico di lavoro nello svilupo di una web application. Spring rimuove alcune delle maggiori
complessità legate all’utilizzo diretto degli EJB (Enterprise Java Beans) tramite l’utilizzo di un
project builder, come Maven o Gradle, e di tecniche quali AOP (Aspect-Oriented Programming),
l’utilizzo dei POJO (Plain Old Java Object) e l’implementazione del IoC (Inversion of Control ),
permettendoci di concentrarci sul codice e gestendo per noi l’infrastruttura dell’applicativo. Il fo-
cus principale di spring è quello di aiutare il programmatore nella gestione dei vari business objects,
permettendo lo sviluppo di applicazioni semplici, affidabili e scalabili con molta più facilità rispetto
ai framework e API classici, come JDBC, le JSP o le Servlet.
Spring si suddivide in più sotto-framework, detti moduli o layer, che possono essere usati sepa-
ratamente o in concomitanza e provvedono varie funzionalità alla nostra web-app.

3.1 Inversion Of Control e Dependency Injection


Le applicazioni Java cono composte in genere da vari oggetti che ’collaborano’ tra loro. In questo
senso, si dice che gli oggetti sono legati tra loro da dipendenze.
Nonostante Java offra varie funzionalità per lo sviluppo di applicazioni, non ha in sè un un modo
di organizzare i vari building blocks in un’infrastruttura coerente, lasciando questo compito allo
sviluppatore. Per porre rimedio a questa mancanza, sono stati progettati vari design pattern per
organizzare la composizione delle classi che formano un’applicazione. Tuttavia, questi pattern non
risolvono il problema principale: il programmatore deve comunque organizzare ’a mano’ l’infra-
struttura della sua applicazione, fornando solamente le linee guida per farlo. (NOTA BENE: ciò
non vuol dire che non siano importanti!)
Spring dal canto suo affronta questo problema implementando l’Inversion Of Control: rispetto
alla programmazione classica, in cui una parte del codice chiama direttamnte librerie esterne o
altre parti del programma, applicando l’inversion of control è il framework stesso a gestire queste
dipendenze. Spring implementa l’IoC tramite la dependency injection, lasciando che sia un
assembler a gestire le connessioni tra i vari oggetti e non gli oggetti stessi.
Vediamo concretamente cosa vuol dire. Un oggetto gestisce direttamente una dipendenza quando
è esso stesso a istanziare un oggetto di cui ha bisogno; ad esempio, supponiamo di avere creato
una classe di utility e di volerla utilizzare in un’altra classe, senza usare la DI faremmo cosı̀:

public class MyClass {

MyUtility myUtil = new MyUtility();

//codice che usa funzioni di MyUtility


}

Implementando l’IoC, cediamo il controllo al framework, lasciando che sia esso stesso a creare
le istanze e a ’iniettarle’ quando ve n’è bisogno. Per capire bene come funziona questo processo
dobbiamo introdurre alcuni concetti fondametali.

34
3.1.1 IoC Container
Un IoC container rappresenta il luogo in cui il framework salva le istanze dei vari oggetti che andrà
poi a iniettare. Il vantaggio di un tale contenitore è che è possibile avere una sola istanza per
ogni oggetto, riutilizzando sempre quella quando ve ne sia bisogno anzichè crearne ogni volta
una nuova.
Spring codifica questo container tramite l’interfaccia ApplicationContext, la quale è reponsabile di
istanziare, configurare e assemblare gli oggetti che andremo poi a iniettare, i quali vengono detti
Spring Beans (o anche solo beans quando chiaro dal contesto). Essa si occupa inoltre di gestire il
ciclo di vita di tali beans.
Spring fornisce inoltre varie classi che implementano questa interfaccia, come ad esempio Clas-
sPathXmlApplicationContext e AnnotationConfigApplicationContext. Ma come fa il container a
sapere quali classi vogliamo che siano gestite come bean? Tramite dei metadati di configurazione,
che possono essere dei file di configurazione in formato xml o delle annotazioni, i quali devono essere
passati ’in qualche modo’ al costruttore dell’ApplicationContext. In queste dispense utilizzeremo
esclusivamente le annotazioni, in quanto più facili sia da capire che da realizzare.
La corretta configurazione dell’IoC container è fondamentale per un corettamento funzionamento
della nostra app, tuttavia ciò non è sempre facile da fare. Vedremo in seguito che per fortuna
il team di Spring ci risparmia questo compito tramite l’utilizzo di Spring Boot, un meraviglioso
strumentopolo che approfondiremo tra un po’.

3.2 Crezione dei bean


Per creare un bean tramite l’utilizzo delle annotazioni ci sono due modi:
1. Tramite le annotazioni @Configuration e @Bean
2. Tramite l’annnotazione @Component (che ammette varie specializzazioni).

@Configuration
Se vogliamo utilizzare il primo metodo dobbiamo innanzitutto creare una classe di configurazione,
annotandola con @Configuration

@Configuration
public class MyConf {
.
.
.
}

All’interno di una tale classe dobbiamo definire dei metodi che restituiscano i vari bean desiderati,
ciascuno dei quali va annotato con @Bean. Supponiamo ad esempio di aver creato una classe
Negozio, per averla a disposizione come bean nel container dobbiamo fare cosı̀:

public class Negozio {


String address;
}

@Configuration
public class MyConf {

@Bean
public Negozio negozio() {
return new Negozio();
}

35
È importante capire che possiamo avere più classi di configurazione! Nonostante teoricamente tutti
i bean possano essere dichiarati all’interno di una stessa classe, è buona norma avere una classe
di configurazione per ogni macro-categoria di bean, in modo tale da garantire maggior chiarezza
della struttura del progetto. L’annotazione @Configuration deve inoltre andare a annotare la classe
principale del nostro progetto, che di conseguenza può essere utilizzata anch’essa per definire dei
bean.

@Component
Il secondo modo consiste nell’annotare direttamente la classe che si vuole trattare come bean con
l’annotazione @Component. In questo caso la classe verrà automaticamente riconosciuta come
candidata per essere un bean a compiletime e in seguito istanziata a runtime. Non abbiamo quindi
bisogno in questo caso di un file di configurazione aggiuntiva.

@Component
public class Negozio {
String address;
}

3.3 Dependency Injection


Abbiamo visto come i bean vengono creati e dove vengono conservati, resta da capire come essi
vengano consegnati alle classi che ne necessitano.
In spring, la DI può avvenire in due luoghi: tramite i setter o direttamente sui parametri. In en-
trambi i casi si utilizza l’annotazione @Autowired, che collega il parametro all’inidirizzo di memoria
del bean in maniera automatica: è qui che si esprime l’IoC nel suo massimo splendore! Vediamo
ora come realizzare un’iniezione in entrambi i modi: nel seguente esempio andremo ad iniettare un
bean CentroCommerciale all’interno di due negozi diversi

@Component
public class CentroCommerciale {
final String INDIRIZZO = "Via di Qui 69";
}

public class Negozio1 {

private CentroCommerciale centroCommerciale;

@Autowired
public setCentroCommerciale( CentroCommerciale centroCommerciale ) {
this.centroCommerciale = centroCommerciale;
}

public class Negozio2 {

@Autowired
private CentroCommerciale centroCommerciale;

3.4 Moduli di Spring


Come detto in precedenza, Spring si suddivide in vari sotto-framework, detti moduli. Tali moduli
sono 20 e la loro organizzazione è descritta nell’immagine che segue:

36
• Core Container: consiste nei moduli Core, Beans, Context e Expression Language. Core
e Beans provvedono le parti fondamentali del framework, come l’IoC e la DI. Implementano
in maniera sofisticata il factory pattern, rimuovendo la necessita di programmare i singleton
e separando la configurazione e la specifica delle dipendenze dalla logica del programma.
Context si basa su Core e Beans e consiste in una via d’accesso agli oggetti framework-based.
Supporta l’internalizzazione, la propagazione degli eventi e il caricamento delle risorse. È la
sede dell’ApplicationContext.
Expression Language fornisce un potente linguaggio per la manipolazione e la query di un
grafo degli oggetti a runtime, consentendo ad esempio di recuperare gli oggetti tramite il loro
nome dall’IoC container.
• Data Access/Integration: consiste dei moduli JDBC, ORM, OXM, JMS e Transaction.
JDBC provvede un layer di astrazione che rimuove la necessità di programmare e gestire le
connessioni col database.
ORM provvede dei layer d’integrazione per le varie API relazionali, come JPA, JDO e Hiber-
nate. Tramite il suo utilizzo queste possono essere utilizzate in combo con le potenti feature
offerte da spring, ad esempio tramite la gestione dichiarativa delle transazioni col db (i.e. i
’motodi query’ che avete visto nel DAO).
OXM provvede un layer di astrazione per le implementazioni dei mapping Object/XML.
JMS consente di creare e inviare messaggi.
Transactions supporta la gestione dichiarativa e programmatica delle transazioni per tutti i
POJO.
• Web: consiste nei moduli Web, Web-Servlet, WebSocket e Web-Portlet.
Spring Web provvede delle feature di base per lo sviluppo web, come l’upload di file e l’i-
nizializzazione dell’ IoC container utilizzando dei servlet listener e un’application context
web-oriented.
Web-Servlet contiene l’implementazione del pattern MVC da parte di Spring.
Web-Portlet contiene una seconda implementazione del pattern MVC, da utilizzare nel caso
l’ambiente sia basato su Portlet anzichè Servlet. È identico al precedente per funzionalità.
• AOP: provvede un’implementazione del pattern AOP coerente con lo standard AOP-Alliance.
Esso ci permette ad esempio di definire dei interceptors che ci permettono di separare le parti
di codice che implementano funzionalità che dovrebbero stare disgiunte.
Aspects provvede l’integrazione con AspectJ.
Instrumentation provvede supporto per le classi e delle implementazioni dei classloader
utilizzabili in certi application server.

37
• Test: Supporta il testing delle component tramite tool esterni come JUnit o TestNG. Provve-
de inoltre un caricamento consistente dell’ApplicationContext e dei mock-objects che possono
essere utilizzati per testare componenti dipendenti da altre in totale isolamento.

3.5 Spring Boot


Per capire cos’è spring boot ragioniamo sulle due parole che compongono il nome: la prima è Spring,
che rappresenta il framework discusso finora, mentre la seconda sta per bootstrap. Combinando le
due abbiamo quindi che spring boot è un progetto che permette allo sviluppatore un rapido inizio di
un progetto spring, evitandogli gran parte delle fasi iniziali di configurazione. L’autoconfigurazione
fornita da spring boot consente di istanziare automaticamente alcuni bean necessari per startare
un progetto spring. Ad esempio, fornisce la classe SpringApplication, la quale può essere usata per
bootstrap-are e lanciare un’applicazione basata su Spring da un metodo main di Java. Per fare ciò
svolge le seguenti operazioni:
1. crea un’istanza appropriata dell’ApplicationContext
2. Registra una CommandLinePropertySource per esporre gli argomenti passati da linea di
comando come proprietà di Spring
3. aggiorna l’application context, caricando tutti i bean
4. triggera eventuali bean CommandLineRunner.
Essa espone inoltre il metodo statico run(Class, String[]) che viene utilizzato per lanciare l’intera
applicazione dal metodo main e prende in input la classe che contiene il main e gli eventuali argo-
menti passati da linea di comando.
Un’altra importante feature è l’annotazione @SprinBootApplication, che annota la classe principa-
le e combina le annotazioni @EnableAutoConfiguration @ComponentScan e @Configuration. Offre
inoltre un server Apache Tomcat integrato, in modo da ridurre la possibilità di errore nella confi-
gurazione di questo.
Spring Initializr offre allo sviluppatore un’interfaccia grafica che consente di automatizzare la crea-
zione del progetto e il setup delle dipendenze. Permette l’utilizzo sia di maven che di gradle come
project builder e consente di scaricare il file zip del progetto pronto all’uso, ma anche di visua-
lizzare all’interno dell’initializr il progetto che si andrà a a scaricare, permettendo ad esempio di
copiare solamente il pom.xml nel caso in cui ci volessimo aggiungere delle dipendenze aggiuntive a
un progetto già esistente.
Spring boot supporta inoltre delle configurazioni aggiuntive nella forma di un file application.properties,
nel quale possiamo specificare informazioni aggiuntive necessarie per il corretto svolgimento del pro-
gramma, come ad esempio l’url del database al quale ci si vuole collegare o la porta del localhost
nella quale vogliamo esporre il nostro servizio.

3.6 Annotazioni Utili


Ci concentriamo ora sullo sviluppo di una una REST api utilizzando spring boot, approfondendo
in particolare le varie annotazioni utilizzate e le loro funzionalità.

3.6.1 Specializzazioni di @Component


Abbiamo già discusso come possiamo creare un bean tramite l’annotazione @Component. Nel con-
testo di un’applicativo web, SPring ci mette a disposizione delle ’annotazioni figlie’ di quest’ultima,
che oltre a creare un bean ci consentono di specificarne la natura e aggiungere delle funzionalità
aggiuntive. Queste annotazioni sono tre: @RESTController, @Service e @Repository. Di queste,
solamente la prima provvede funzionalità aggiuntive rispetto a @Component, in quanto incorpora
in sè l’annotazione @ResponseBody (che vedremo tra un attimo) ed la inserisce automaticamente
nei return dei metodi.
Nota: In realtà esiste anche @Controller, che non provvede alcuna funzionalità aggiuntiva rispetto
a @Component e che solitamente non viene utilizzata quando si costruisce in architettura REST.
Perchè usare quindi le annotazioni specifiche anzichè quella base? Innanzitutto per rendere più
chiara la funzione di ogni classe e migliorare quindi la leggibilità del codice, ma anche perchè ci
sono aree di Spring che provvedono dei benefit aggiuntivi di automatizzazione per ciascuna an-
notazione specifica. Di conseguenza è best practice utilizzare un annotazione specifica qualora

38
possibile. @Controller, @Service, @Repository e @Component fanno tutte parte della dependency
org.springframework.stereotype, mentre @RESTController fa parte di org.springframework.web.
Domanda: tornate al terzo sprint, dove è stata utilizzata l’annotazione @Component? Perchè?

3.6.2 Annotazioni di Spring Web


Le principali annotazioni fornite da org.springframework.web sono:
• @RESTController
• @CrossOrigin abilita la comunicazioni tra un dominio esterno e il gestore di richieste che
va ad annotare. Se annota un’intera classe abilita la comunicazione con l’esterno per tutti i
metodi presenti all’interno. Per garantire il permesso solamente a domini di cui ci fidiamo,
possiamo specificare gli url di questi come argomento dell’annotazione con la sintassi @Cros-
sOrigin(origins = ”http://sitosicuro.it”). Assieme a @RequestMapping viene solitamente
utilizzata per annotare l’intero controller.
• @RequestMapping annota un gestore di request http. Esso può essere applicato sia all’intero
controller che ai metodi specifici. I due argomenti più utilizzati sono path (o gli equivalenti
name e value), il quale va a legare il gestore annotato all’url specificato, e method, il quale
va ad indicare il tipo di richiesta http che dovrebbe arrivare a tale gestore. Se utilizziamo
vari controller, questa annotazione annota ciascuno di essi, specificando il path genstito dai
metodi al suo interno. Ogni metodo interno dovrà essere a sua volta annotato in maniera da
specificare l’url specifico di cui si deve occupare. Vediamo un esempio:

@RestController
@RequestMapping(value = "/utente")
@CrossOrigin(origins = "http://localhost:4200")
public class UtenteController {

@RequestMapping(value = "/insert", method=RequestMethod.POST)


public UtenteDto inserici([...]) {
...
}

In questo esempio, ogni richiesta inviata a un url della forma ”http://localhost:8080/utente/*”


verrà gestita da uno dei metodi all’interno di questo controller. Ad esempio il metodo inseri-
sci gestirà le richieste POST inviate a ”http://localhost:8080/utente/insert”, inviando nella
response il return del metodo (codificato opportunamente).
A partire da Spring 4.3, abbiamo a disposizione delle annotazioni che evitano la necessità
di specificare il tipo di richiesta come parametro; ad esempio qui sopra avremmo potuto
annotare inserisci con @PostMapping(value=”/insert”) e non sarebbe cambiato nulla. Le
annotazioni in questione sono le seguenti:
1. @GetMapping
2. @PostMapping
3. @PutMapping
4. @PatchMapping
5. @DeleteMapping
• @RequestBody va ad annotare l’argomento del metodo, mappando il body della request in
un oggetto java, ad esempio

@RestController
@RequestMapping(value = "/utente")
@CrossOrigin(origins = "http://localhost:4200")
public class UtenteController {

39
@PostMapping(value = "/insert")
public UtenteDto inserici(@RequestBody RegisterDTO dto) {
//operazioni che utilizzano il dto passato nella request
}

• @RequestParam va ad annotare l’argomento di un metodo e consente l’accesso a un parametro


della request di cui specifichiamo la chiave o come argomento dell’annotazione o come nome
dell’argomento del metodo. Permette inoltre di iniettare un valore di default nell’argomento
qualora non fosse presente alcun parametro sotto la chiave specificata.

@RestController
@RequestMapping(value = "/utente")
@CrossOrigin(origins = "http://localhost:4200")
public class UtenteController {

//cerca il parametro della request che ha come chiave id e se non


lo trova setta l’argomento del metodo uguale a 5

@GetMapping("/findById")
public UtenteDto findById(@RequestParam(value="id", defaultValue=
"5") Long nomeACaso) {
...
}

//cerca il parametro della request che ha coem chiave username e


se non lo trova ho un errore

@GetMapping("/findByUsername")
public UtenteDto findById(@RequestParam String username) {
...
}

• @ResponseBody va ad annotare il return type di un metodo nel caso in cui non si utilizzi
@RestController. Codifica l’oggetto restituito opportunamente nel body della response. Se
applicato alla classe del controller viene applicato automaticamente a tutti i metodi al suo
interno. Ha quindi senso dire che @RestController è equivalente all’unione di @Controller e
@ResponseBody come annotazioni a livello di classe.
• @ResponseStatus consente di specificare uno status voluto per la response.
• @ExceptionHandler annota un metodo che si fa carico di una determinata eccezione all’in-
terno del controller. Prende come argomento la classe dell’eccezione specifica che si vuole
gestire e può essere usato in ocmbinazione con @ResponseStatus per una gestione effica-
ce e facile da interpretare delle eccezioni. Un metodo annotato in questo modo viene ri-
conosciuto dalla JVM come adatto ogniqualvolta un’eccezione di quel tipo viene lanciata
all’interno del controller (ripassate la gestione interna delle eccezioni nel capitolo 1). Ad
esempio, se nell’esempio precedente passiamo un argomento invalido al metodo inserisci, que-
sto lancerà una InvalidArgumentException; per gestire questa eccezione definiamo il metodo
handleInvalidArgument come segue:

@RestController

40
@RequestMapping(value = "/utente")
@CrossOrigin(origins = "http://localhost:4200")
public class UtenteController {

@PostMapping(value = "/insert")
public UtenteDto inserici(@RequestBody RegisterDTO dto) {
//operazioni che utilizzano il dto passato nella request
}

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
void handleIllegalArgument(IllegalArgumentException exception) {
// ...
}

• @ControllerAdvice è una specializzazione di @Component che permette di utilizzare i metodi


annotati con @ExceptionHandler contenuti al suoi interno per ogni controller dell’applica-
zione.

3.6.3 Annotazioni per le entità della JPA


Le entità altro non sono che POJO che rappresentano i dati che andranno salvati nel database. In
particolare, un’entità rappresenta una tabella mentre ogni istanza di tale entità rappresenta una
riga della tabella.

@Entity
È l’annotazione che specifica che una determinata classe è da considerare come un’entità. Affinche
tale annotazione sia valida, la classe in questione deve contenere una primary key. Per dichiarare
che un campo di un’entità deve essere trattato come chiave primaria si utilizza l’annotazione @Id.
Le annotazioni maggiormente utilizzate all’interno di un’entità sono le seguenti:
• @GeneratedValue è un annotazione field level (i.e. annota un parametro della classe) che
indica che quel valore deve essere generato automaticamente. Possiamo specificare la stra-
tegia per generare tali parametri come argomento dell’annotazione tramite la sintassi strate-
gy=GenerationType.STRAT, dove STRAT può essere rimpiazzato con uno tra AUTO, TA-
BLE, SEQUENCE, o IDENTITY. (Domanda bonus: guardando solamente la sintassi di
sopra, che oggetto è GenerationType? Risposta)

• @Table è un annotazione class-level che viene utilizzata per specificare il nome della tabella
e dello schema che lo contiene tramite gli argomenti name e schema rispettivamente. Può
essere omessa e in questo caso il nome della table coinciderà col nome della classe entità.
• @Column è un annotazione field-level che può prendere come argomenti varie configurazioni.
Ad esempio:

//...
@Column(name="STUDENT_NAME", length=50, nullable=false, unique=false)
private String nome;
//...

In questo caso il parametro nome della classe corrisponderà a una colonna chiamata STU-
DENT NAME che ammette valori lungii 50 caratteri, non può essere null e può contenere
uno stesso valore su più righe.

• @OneToOne, @OneToMany, @ManyToMany: annotano un parametro che codifica una


foreignkey all’interno della tabella. Più nel dettaglio:

41
– @OneToOne viene utilizzata per implementare una relazione one-to-one tra entità. Deve
essere utiliizata assieme all’annotazione @JoinColumn, nella quale specifichiamo il nome
della colonna che contiene la primary key nella tabella a cui la foreign key punta, tramite
la sintassi @JoinColumn(referencedColumnName = ”id”). Nel caso in cui la relazione
sia bidirezionale, l’annotazione @OneToOne va specificata anche nella tabella che non
contiene la foreign key. Vediamo un esempio per capire meglio.

@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;

// ... getters and setters


}

@Entity
@Table(name = "address")
public class Address {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...

@OneToOne(mappedBy = "address")
private User user;

//... getters and setters


}

Nell’esempio dichiariamo una foreign key all’interno della tabella users che punta alla
tabella address. In questo caso la tabella che contiene la FK è users, di conseguenza è
lı̀ che dobbiamo inserire l’annotazione @JoinColumn.
– @OneToMany e @ManyToOne: supponiamo di avere due entità, ad esempio Carrello e
Item, e di volerle legare tra loro con una relazione one to many (in uno stesso carrello ci
possono stare più articoli). Utilizzando le annotazioni @OneToMany e @ManyToOne
possiamo procedere cosı̀:

@Entity
@Table(name="CART")
public class Cart {

/ /...

@OneToMany(mappedBy="cart")
private Set<Item> items;

// getters and setters


}

42
@Entity
@Table(name="ITEMS")
public class Item {

//...
@ManyToOne
@JoinColumn(name="cart_id", nullable=false)
private Cart cart;

public Item() {}

// getters and setters


}

Anche in questo caso la relazione è bidirezionale. È best practice salvare la foreign key
nel lato many-to-one della relazione, per questo è l’entità Item ad essere ownling side.
Ciò va fatto per ridurre la posiibilità di ambiguità.

3.6.4 @Transactional
Questa annotazione indica che una classe o un’interfaccia è responsabile per la gestione delle
transazioni col database. Generalmente può essere messa sia a livello di service che di DAO.

3.6.5 Annotazioni di Lombok


Lombok è una libreria che ci evita la scrittura di boilerplate-code, rendendo il codice più snello e
di facile lettura. Le principali annotazioni offerte da Lombok sono:

• @Data genera getter e setter per tutti i field della classe, denominati in base al nome
dell’attributo in camelCase (e.g. numeroTelefono -¿ getNumeroTelefono).
• @AllArgsConstructor genera un costruttore con tutti i field della classe come argomento.
• @NoArgsConstructor genera un costruttore privo di argomenti.

• @RequiredArgsConstructor genera un costruttore con i field annotati come required come


argomento.
• @Slf4j genera un logger.

3.6.6 @Mapper
Questa annotazione indica che un interfaccia espone i metodi che si vogliono implementare per la
mappatura Entity< − >DTO da parte di MapStruct.

3.7 Hibernate
Hibernate è un ORM per gestione di persistenze du un database relazionale. Si occupa quindi di
generare automaticamente le query esplicite in SQL in base alle operazioni effettuate direttamente
sulle entità, risparmiando quindi questo compito allo sviluppatore ma soprattutto garantendo la
portabilità dell’applicazione. Per fare ciò fa uso delle annotazioni presentate nella sezione 2.6.3.
All’avvio dell’applicativo è hibernate che si occupa di generare le tabelle corrispondenti alle entità
che abbiamo definito all’interno del progetto, utilizzando le annotazioni per configurare le relazioni
tra esse. Il linguaggio utilizzato da hibernate si chiama hql (hibernate query language). Questo è
simile a sql nella sintassi, ma a differenza di questo opera non su record nel database ma su entità
della JPA, garantendo quindi portabilità e scalabilità. Grazie a SpringBoot abbiamo a disposizione
hibernate già configurato e pronto all’uso, tuttavia è comunque possibile fornire configurazioni ag-
giuntive all’interno dell’ application.properties. Una delle feature più potenti che ci offre hibernate
è l’utilizzo dei cosiddetti query-methods all’interno delle repository, che vengono poi tradotte da
hibernate in query in linguaggio nativo. Per apprezzare a fondo questo aspetto provate a dare
un’occhiata ai DAO delle prime due settimane e mettetelo a confronto con i DAO del progetto
finale!

43
Vediamo alcuni esempi di query con Hibernate. Per lo scopo di questi esempi, supponiamo di avere
prima creato un’entità Corso che ha al suo interno la proprietà ’nome’.
• Query-Methods: quando una query è relativamente semplice, è possibile dichiararla tramite il
nome di un metodo nella repository. La sintassi standard segue quella dei metodi già presenti
nell’interfaccia CrudRepository, che andiamo ad estendere quando creiamo le nostre:

@Repository
@Transactional
public interface CorsoRepository extends crudRepository<Corso,Long> {

//equivalente alla query "select * from corso where corso.nome =


nome"
List<Corso> findAllByNome(String nome);

• Query HQL: quando una query è troppo complessa per essere espressa efficientementte dal
nome di un metodo è consigliabile esplicitarla in hql. Per fare ciò annotiamo un metodo
con @Query, la quale prenderà come argomento la query che vogliamo eseguire. Il seguente
esempio mostra anche come far riferimento a uno o più argomenti del metodo all’interno della
query:

@Repository
@Transactional
public interface CorsoRepository extends crudRepository<Corso,Long> {

@Query("select c from Corso c where c.nome = :myName and c.inizio


= :myStart andc.fine<=:myEnd")
List<Corso> customSearch(@Param("myName") String myName,
@Param("myStart") Date myStart,
@Param("myEnd") Date myEnd
);

• Native query ci sono rari casi in cui hql potrebbe non bastare, in tal caso è possibile dichiarare
esplicitamente una query in SQL utilizzando sempre l’annotazion e@Query con parametro
nativeQuery = true.
• Criteria Query: Lecriteria query differiscono nettamente dagli esempi visti sin’ora, ne faremo
una trattazione concisa, rimandando a questo link per i dettagli.
Tramite la Criteria API, hibernate ci consente di costruire delle query come dei veri e propri
oggetti Java, seguendo un approccio funzionale tramite l’applicazione di più metodi conca-
tenati per la costruzione dell’intera struttura.
Per inizializzare una query su una entità, generiamo un’istanza della classe CriteriaQuery,
ad esempio utilizzando il metodo createQuery dell’interfaccia CriteriaBuilder, il quale prende
come argomento la classe dell’entità su cui vogliamo fare la query.

\@Autowired //supponiamo di avere un bean adatto


CriteriaBuilder myBuilder;

CriteriaQuery<Corso> myQuery = myBuilder.createQuery(Corso.class);

In seguito, creiamo una Root tramite il metodo .from() di myQuery. A questo punto siamo
pronti per strutturare la query. Vediamo un esempio esplicativo: costruisco una query equi-
valente a ”select * from corso where nome = ’matematica’ or id ¿= 12”. Tutta la costruzione

44
avviene attraverso il CriteriaBuilder, che espone dei metodi che codificano tutte le maggiori
operazioni che possiamo fare sul db.

Session session = HibernateUtil.getHibernateSession();


CriteriaBuilder myBuilder = session.getCriteriaBuilder();

CriteriaQuery<Corso> critQuery = myBuilder.createQuery(Corso.class);


Root<Corso> root = critQuery.from(Corso.class).

critQuery.select(root).
.where(
mybuilder.or( //prende i due predicati su cui fare un
or come argomento
myBuilder.equal(root.get("nome"),"matematica"), //
testa uguaglianza
myBuilder.gt(root.get("id"),12) //testa >=
);

//costruiamo infine la query vera e propria e eseguiamola


Query<Corso> query = session.createQuery(critQuery);
List<Corso> results = query.getResultList();

45
Capitolo 4

Rest API

4.1 REST
REST sta per Representational State Transfer. È un’architettura che definisce uno schema per lo
sviluppo di servizi web; una API che fornisce l’accesso a tali servizi in maniera semplice e flessibile
si dice REST API. La tecnologia REST è in genere preferita al più robusto SOAP (Simple Object
Access Protocol ) in quanto pesa di meno sulla banda. Tutte le comunicazioni in un’architettura
REST fanno uso delle richieste HTTP. Possiamo riassumere il funzionamento dell’architettura
REST come segue: una richiesta http viene mandata dal client al server, il quale manda indietro
una response http, la quale comunica l’esito della comunicazione al client. Una tale response può
essere codificata in vari formati, quali HTML, XML o JSON, che è solitamente il più usato per la
sua capacità di codificare informazioni complesse in maniera facilmente leggibile sia da un umano
che da una macchina.

4.2 Metodi HTTP


I metodi più utilizzati in un’architettura REST sono POST, GET, PUT, PATCH e DELETE,
Esistono anche OPTIONS e HEAD, che tuttavia vengono utilizzati più raramente (indovinate cosa
mi hanno chiesto al primo colloquio esterno :) ). In breve, i cinque metodi principali corrispondono
alle operazioni CRUD, ovvero alla creazione, lettura, modifica e eliminazione dei dati.
• GET: è utilizzato per recuperare una rappresentazione di una data risorsa, passando le
informazioni utili per i recupero come parametri nell’header della request. Questa risorsa
viene restituita nella response, la quale ha status 200 se tutto va bene. Se qualcosa va storto,
si può avere status 404 (NOT FOUND) oppure 400 (BAD REQUEST).
• POST: è utilizzato per la creazione di nuove risorse. Utilizza il body della request per
inviare una codifica della risorsa che si vuole inserire, solitamente in formato JSON. Se tutto
va bene la response ha status 201 e contiene un identificativo (ad esempio un id se si opera
con un database nel server) per accedere alla risorsa appena creata. Se qualcosa va storto
può restituire una response con status 400 oppure 500 (INTERNAL SERVER ERROR) se la
request era valida ma c’è stato un errore interno al server (ad esempio se salta la connessione
col db).
• PUT: viene utilizzata quando si vuole modificare una risorsa nella sua interezza. Anche in
questo caso utilizza il body per l’invio della risorsa da modificare. Le response sono uguali
a quelle del POST. IMPORTANTE: un metodo PUT dovrebbe essere utilizzato secondo
il seguente schema: se la risorsa inviata non è già presente, il metodo si occupa di crearla,
mentre se è presente ne aggiorna le informazioni.
• PATCH: utilizzata per modificare parzialmente una risorsa. La differenza principale col
PUT è che in quest’ultimo la risorsa deve essere passata per intero allinterno del body, con
con eventuali parametri mancanti che dovranno essere messi NULL, mentre nel body di una
PATCH request vanno inseriti solamente l’identificatore della risorsa e i parametri che si
vogliono effettivamente modificare, lasciando inalterati gli altri.
• DELETE: viene utilizzata per eliminare una risorsa, del quale si passa un identificatore come
parametro nell’header della request. Se l’eliminazione va a buon fine la response ritorna con
status 200(OK).

46
4.3 Standard d’Industria
Nello sviluppo di una REST API è importante seguire alcune regole e best practices che costituisco-
no di fatto lo standard industriale, permettendo una maggiore velocità nello sviluppo e garantiscono
che l’API sia facilmente comprensibile a un utente esterno. Le principali regole da seguire sono le
seguenti:
1. Interfaccia uniforme: è un vincolo chiave, che suggerisce la presenza di un modo uniforme
per interagire con un dato server, indipendentemente dal dispositivo utilizzato o dal tipo di
applicazione. Le quattro linee guida per seguire questa regola sono:
• Resource Based:
• Manipolazione delle risorse tramire rappresentazioni: il client deve essere in possesso
di una rappresentazione di una risorsa che contenga abbastanza informazioni per per-
mettergli manipolarla (qualora in possesso dei dovuti permessi) tramite le chiamate
REST.
• Self-descriptive Messages: ciascun messaggio deve includere abbastanza informazioni su
come deve essere processato, in modo tale che il server possa analizzare facilmente la
request.
• Hypermedia as the Engine of Application State: ogni response deve contenere dei
collegamenti che consentano al client di trovare altre risorse in maniera semplice.
2. Stateless: ogni request deve essere indipendente dalle altre, ovvero deve contenere in sè lo
stato necessario per la sua gestione; inoltre il server non deve salvare nulla riguardante la
sessione. In un architettura REST il client deve includere tutte le informazioni di cui il server
ha bisogno per gestire una request all’interno della request stessa, ad esempio come parametri
dell’header.
3. Cacheable: ogni response deve includere se può essere o meno conservata nella cache e se
sı̀ per quanto tempo. Una gestione accurata della cache elimina totalmente o parzialmente
alcune delle interazioni client-server, andando a ridurre notevolmente il peso sulla largheza
di banda.
4. Client-Server: Le applicazioni REST dovrebbero seguire un’architettura Client-Server, dove
il client richiede delle risorse e il server è colui che gliele fornisce. Client e server devono
essere oggetti separati, legati tra loro solamente dalle comunicazioni e non dovrebbero aver
bisogno di conoscere alcunchè sulla struttura dell’altro.
5. Layered System: l’architettura di un’applicazione dovrebbe essere composta da più layer.
Ogni layer deve essere indipendente dagli altri e non dovrebbe aver bisogno di alcuna infor-
mazione sulla loro business logic per funzionare correttamente.

4.4 REST VS SOAP


REST e SOAP sono fondamentalmente due cose diverse, di conseguenza una comparazione diret-
ta ha poco senso. Possiamo tuttavia elencare le principali differenze che intercorrono tra i due
approcci.
• sOAP sta per Simple Object Access Protocol, essendo un protocollo segue quindi uno stretto
standard per la comunicazione tra client e server. REST da canto suo è un’architettura e
non ha uno standard nel senso stretto del termine, ma segue le linee guida descritte sopra.
• SOAP utilizza solamente il formato XML per codificare le informazioni racchiuse nei mes-
saggi, mentre REST può utilizzare anche altri formati, come HTML e JSON.
• Poichè REST è un’architettura, una REST API può utilizzare SOAP come protocollo. Il
contrario, ovviamente, non ha alcun senso.
• SOAP è più robusto ma allo stesso tempo è più difficile da implementare e richiede una
maggiore larghezza di banda.
• dal punto di vista della sicurezza, SOAP offre SSL e WS-Security, mentre REST offre SSL e
HTTPS. Per questo motivo SOAP è preferito in API in cui la sicurezza deve essere massima,
coem ad esempio quando si gestiscono transazioni monetarie.

47
Capitolo 5

Microservizi

I microservizi sono un approccio per sviluppare e organizzare l’architettura dei software secondo
cui quest’ultimi sono composti di servizi indipendenti di piccole dimensioni che comunicano tra
loro tramite API ben definite. Questi servizi sono controllati da piccoli team autonomi.
Le architetture dei microservizi permettono di scalare e sviluppare le applicazioni in modo più
rapido e semplice, permettendo di promuovere l’innovazione e accelerare il time-to-market di nuove
funzionalità.

5.1 Architettura monolitica vs. architettura di microservizi


Nelle architetture monolitiche tutti i processi sono strettamente collegati tra loro e vengono eseguiti
come un singolo servizio. Ciò significa che se un processo dell’applicazione sperimenta un picco nella
richiesta, è necessario ridimensionare l’intera architettura. Aggiungere o migliorare una funzionalità
dell’applicazione monolitica diventa più complesso, in quanto sarà necessario aumentare la base di
codice. Tale complessità limita la sperimentazione e rende più difficile implementare nuove idee.
Le architetture monolitiche rappresentano un ulteriore rischio per la disponibilità dell’applicazione,
poiché la presenza di numerosi processi dipendenti e strettamente collegati aumenta l’impatto di
un errore in un singolo processo.
Con un’architettura basata su microservizi, un’applicazione è realizzata da componenti indi-
pendenti che eseguono ciascun processo applicativo come un servizio. Tali servizi comunicano
attraverso un’interfaccia ben definita che utilizza API leggere. I servizi sono realizzati per le fun-
zioni aziendali e ogni servizio esegue una sola funzione. Poiché eseguito in modo indipendente,
ciascun servizio può essere aggiornato, distribuito e ridimensionato per rispondere alla richiesta di
funzioni specifiche di un’applicazione.

5.2 Caractteristiche dei Microservizi


Autonomia
Ciascun servizio nell’architettura basata su microservizi può essere sviluppato, distribuito, eseguito
e ridimensionato senza influenzare il funzionamento degli altri componenti. I servizi non devono
condividere alcun codice o implementazione con gli altri. Qualsiasi comunicazione tra i componenti
individuali avviene attraverso API ben definite.

Specializzazione
Ciascun servizio è progettato per una serie di capacità e si concentra sulla risoluzione di un problema
specifico. Se, nel tempo, gli sviluppatori aggiungono del codice aggiuntivo a un servizio rendendolo
più complesso, il servizio può essere scomposto in servizi più piccoli.

5.3 Vantaggi dei Microservizi


Agilità
I microservizi promuovono le organizzazioni di team indipendenti di dimensioni ridotte che diven-
tano proprietari del servizio che gestiscono. I team agiscono in contesti ridotti e ben delineati cosı̀

48
che possano lavorare in modo più indipendente e rapido. Ciò riduce i tempi del ciclo di sviluppo.
Potrai trarre un enorme vantaggio dal throughput aggregato dell’organizzazione.

Scalabilità e flessibilità
I microservizi ti consentono di scalare ciascun servizio in modo indipendente per rispondere alla
richiesta delle funzionalità che la tua applicazione supporta. Ciò permette ai team di ridimensionare
in modo corretto l’infrastruttura in base alle necessità, misurare in modo accurato i costi di una
funzionalità e proteggere la disponibilità dell’applicazione nel caso in cui il servizio sperimenti un
amento nella richiesta.

Semplicità di distribuzione
I microservizi supportano l’integrazione continua e la distribuzione continua, cosı̀ da poter pro-
vare nuove idee in modo più semplice e ripristinare impostazioni precedenti quando qualcosa non
funziona. Gli errori, dunque, influiscono di meno sul costo delle operazioni, permettendoti di spe-
rimentare, aggiornare il codice in modo più semplice e accelerare il time-to-market delle nuove
funzionalità.

Libertà tecnologica
Le architetture basate su microservizi non applicano un unico approccio all’intera applicazione.
I team hanno la libertà di scegliere gli strumenti migliori per risolvere i loro problemi specifici.
Di conseguenza, i team che costruiscono i microservizi possono scegliere il miglior strumento per
ciascun lavoro.

Codice riutilizzabile
Dividere il software in moduli piccoli e ben definiti permette ai team di utilizzare funzioni per più
scopi. Un servizio scritto per una certa funzione può essere utilizzato come blocco costruttivo per
un’altra funzionalità. Ciò permette all’applicazione di effettuare il bootstrap in modo indipendente,
poiché gli sviluppatori possono creare nuove capacità senza dover scrivere del codice da zero.

Resilienza
L’indipendenza dei servizi aumenta la resilienza di un’applicazione in caso di errori. In un’ar-
chitettura monolitica, un errore in un unico componente potrebbe avere ripercussioni sull’intera
applicazione. Con i microservizi, le applicazioni possono gestire completamente gli errori di un
servizio isolando la funzionalità senza bloccare l’intera applicazione.

5.4 Microservizi in Spring


SPring supporta lo sviluppo di un’architettura a microservizi fornendo nell’initializr

19 sui servizi:
20 pom:
21 spring eureka client
22 application.properties:
23 eureka.client.enabled= true
24 eureka.client.register-with-eureka= true
25 eureka.client.fetch-registry= true
26 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
27 classe main:
28 @EnableDiscoveryClient
29

30 sul discovery server:


31 pom:
32 spring eureka server
33 application.yml:
34 server:
35 port: 8761
36 eureka:

49
37 client:
38 register-with-eureka: false
39 fetch-registry: false
40 instance:
41 hostname: localhost
42 logging:
43 level:
44 ’[com.netflix.eureka]’: ’OFF’
45 ’[com.netflix.discovery]’: ’OFF’
46 nel main:
47 @EnableEurekaServer

50
Capitolo 6

JSON Web Token

Il JSON Web Token è un open standard che definisce un modo compatto e e autonomo per tra-
smettere delle informazioni in maniera sicura sotto forma di un oggetto JSON. Un’informazione
trasmessa in questo modo può essere verificata grazie a una firma digitale. JWT supporta sia firme
basate su crittografia a chiave segreta (utilizzando l’algoritmo HMAC), che firme su crittografia a
chiave pubblica (ad esempio utilizzando l’algoritmo RSA o ECDSA).
Alcuni contesti in cui l’utilizzo dei JWT è utile sono:
• Autorizzazione: è la principale sfera di utilizzo per il JWT. Dopo aver effettuato il login,
l’utente riceve un jwt dal server e ogni request che invia deve includere quest’ultimo per
garantirgli l’accesso ai servizi garantiti dalle informazioni (claim) presenti nel token.
• Scambio di informazioni: i JWT rappresentano un buon modo per trasmettere informazioni
in maniera sicuro, in quanto grazie all’utilizzo di una firma digitale si può garantire la prove-
nienza di un tale token. In aggiunta, poichè la firma digitale viene generata tramite l’header
e il payload del token, questa garantisce anche l’integrità del messaggio.

6.1 Struttura
Nella sua forma compatta, un JWT consiste in tre parti separati da punti. Queste parti sono, in
ordine:
1. Header: consiste di due parti. La prima parteindica il tipo di token (ovvero dice che questo
oggetto è un JWT) mentre la seconda indica l’algoritmo utilizzato per la firma. Ad esempio
un header di un token che utilizza l’algoritmo HS256 appare cosı̀:

{
"alg": "HS256",
"typ": "JWT"
}

2. Payload: è la seconda parte del token che contiene i claim, ovvero delle asserzioni riguardante
un’entità (tipicamente un utente) e altri dati aggiuntivi. Ci sono tre tipi di claim: claim
registrati, claim publici e claim privati.
• I claim registrati sono un insieme di claim predefiniti che, pur non essendo obbligatori,
sono raccomandati quando si genera un JWT. Questi offrono uno standard per dei claim
ricorrenti, come ad esempio iss (che sta per issuer, ovvero chi ha generato il token), exp
(expiration date, data di scadenza), sub (subject, ovvero il detentore del toke, colui a
cui i claim fanno riferimento) e altri.
• I claim pubblici possono essere definiti a piacimento dagli utilizzatori dei JWT. Per
evitare le collisioni con l’algoritmo di crittografia questi devono essere definiti nella
IANA JWT Registry oppure come un URI che contiene un namespace resistente alle
collisioni. Una collisione consiste in una coppia di stringhe che vengono mappate in una
stessa stringa dall’algoritmo, poichè lo spazio di partenza è tipicament di cardinalità
maggiore rispetto allo spazio di arrivo (in quanto le funzioni di hash per essere utili

51
devono mappare una stringa in una più piccola), queste sono inevitabili ma possono
essere rese ’abbastanza rare’ utilizzando una funzione resistente alle collisioni.
• Claim privati: sono claim personalizzati, creati per la condivisione di informazioni ad
hoc tra due parti.
Un esempio di payload è il seguente:

{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

3. Signature: consiste nella codifica dell’header, del payload e di un eventuale segreto (i.e. una
stringa segreta in possesso unicamente delle parti che utilizzano il token per comunicare).
Tale codifica deve essere fatta tramite l’algoritmo specificato nell’header, ad esempio, per
firmare un token tramire l’agoritmo HMAC SHA256 dobbiamo fare cosı̀:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

La firma viene utilizzata per verificare l’integrità del messaggio e che il mittente sia effetti-
vamente chi dice di essere.

6.2 Funzionamento
Dopo che l’utente ha effettuato il login, esso riceve un token che contiene delle informazioni sul-
l’utente e che gli fornisce delle credenziali per l’accesso ai servizi. A causa di questo, il token deve
essere trattato con grande cura per evitare problemi di sicurezza; in particolare, bisogna assicu-
rarsi che il token scada dopo un certo periodo di tempo relativamente breve, in modo da renderlo
inutilizzabile qualora intercettato. Sempre per ragioni di sicurezza, è consigliato non salvare infor-
mazioni sensibili nello storage del browser.
Ogniqualvolta un utente ha bisogno di accedere a una risorsa/path protetto, deve includere il JWT
nella request, tipicamente nel parametro ’Authorization’ dell’header, seguendo lo schema Bearer.
Questo schema di autenticazione è stateless e quindi in linea con un’architettura REST.
Quando una request arriva a un path protetto, questo controlla se un token è presente nell’header
e ne verifica la validità. Se tale verifica va a buon fine garantisce l’accesso alle risorse richieste,
altrimenti restituisce una response negativa.
Poichè i token vengono mandati nell’header della request, bisogna fare attenzione a cosa includere
nel payload in modo da non renderli troppo grandi, in quanto alcuni server hanno un limite di 8kb
per le dimensioni dell’header.

6.3 Implementazione
Per quanto riguarda il lato pratico, per implementare il JWT nella nostra app full stack dobbiamo
gestire la comunicazione sia lato front che lato back.

6.3.1 Front End


La parte in front è la più semplice, in quanto dobbiamo solamente includere il token all’interno
della request. Per fare ciò abbiamo bisogno di un interceptor, ovvero di un layer che intercetta
tutte le request e le response e può effettuare delle operazioni su di esse. L’inserimento del token
nell’header deve essere fatto in tutte le request successive al login. La prima operazione da fare
è quindi recuperare il token fornitoci nella response del login e memorizzarlo in storage, in modo
da averlo a disposizione quando dovremo mandare una nuova request. Nell’inerceptor abbiamo

52
bisogno di due operazioni: una che intercetti una request e la cloni inserendo nell’header il token
e una che intercetti le response e salvi da esse il token aggiornato. Se autilizziamo il framework
Angular, possiamo implementare l’interfaccia HttpInterceptor, la quale espone il metodo intercept:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>

Un’implementazione di tale interfaccia è qualcosa del genere:

@Injectable()
export class ContraderInterceptor implements HttpInterceptor {
constructor(private jwtService: JwtService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<
HttpEvent<any>> {

if (/*ho un token*/) {
const token = /*recupero il token*/
request = request.clone({ //inserisco il token
setHeaders:
{
Authorization: ‘Bearer ${token}‘
}});
}
return next.handle(request); //invio la request clonata
}
}

Per gestire anche le response modifichiamo il return utilizzando il metodo .pipe(), il quale in soldoni
crea l’equivalente di una stream con l’esito della request:

return next.handle(request).pipe(
tap(
event => {
if (event instanceof HttpResponse) {
//controlli vari ed eventuali sugli errori
const jwt = event.headers.get(’Authorization’);
//salvo il jwt opportunamente
}
}
)
)

Affinchè un tale interceptor funzioni, dobbiamo ricordarci di dichiararlo nell’AppModule (o in un


modulo specifico se ne vogliamo ridurre lo scope) sotto la voce providers:

providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ContraderInterceptor,
multi: true
}

53
6.3.2 Back End
Lato back dobbiamo implementare un’infrastruttura che si occupi di verificare la validità di una
request prima di inoltrarla al resto del server. Vediamo come fare utilizzando il modulo Security
di Spring.
Includiamo inoltre nel pom la seguente dipendenza, che ci permette di operare con oggetti che
codificano un JWT in maniera semplice:

<dependency>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

Iniziamo ora col creare una classe di configurazione, nella quale andremo a dichiarare i bean
necessari. Creiamo inoltre una component che ci andrà a fornire un encoder per le password:

@Configuration
public class MyWebConfiguration {
//qui inserirremo le configurazioni
}

@Component
public class MyEncoderUtil {
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

NOTA: Nelle varie guide è spesso estesa la classe WebSecurityConfigurerAdapter, della quale
veniva overrode il metodo cofigure. Questa classe è stata recentemente deprecata, vediamo quindi
come fare senza di lei.
Creiamo ora un nuovo package, nel quale andremo a definire tutte le classi di utility per la festione
dei JWT, torneremo alla fine alla classe di configurazione per mettere assieme i pezzi. Per una
corretta configurazione del JWT abbiamo bisogno di:
• Generare il token
• Validare il token:

1. Controllare la firma
2. Controllare claim e permessi

TokenManager
Per prima cosa, creiamo la classe TokenManager, che sarà responsabile di creare e validare i token:

@Component
public class TokenManager implements Serializable {

private static final long serialVersionUID = 7008375124389347049L;

public static final long TOKEN_VALIDITY = 10 * 60 * 60;

@Value("${secret}") //supponiamo di averlo nell’application.


properties
private String jwtSecret;

//note
public String generateJwtToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();

54
return Jwts.builder().setClaims(claims).setSubject(userDetails.
getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() +
TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
}

//note
public Boolean validateJwtToken(String token, UserDetails userDetails)
{
String username = getUsernameFromToken(token);
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
Boolean isTokenExpired = claims.getExpiration().before(new Date());

return (username.equals(userDetails.getUsername()) && !


isTokenExpired);
}

public String getUsernameFromToken(String token) {


final Claims claims = Jwts.parser().setSigningKey(jwtSecret).
parseClaimsJws(token).getBody();
return claims.getSubject();
}

Abbiamo due metodi:


• generateJwtToken(): viene usato per generare un token dopo una autenticazione da parte
dell’utente. In questo caso per il payload vengono utilizzati lo username, la data di genera-
zione del token e la sua data di scadenza. Per costruire effettivamente il token utilizziamo
un oggetto di tipo JwtBuilder, ottenuto grazie al metodo .builder() di Jwts.
Per inserire i claim abbiamo usato setClaims, al quale abbiamo passato una mappa che può
essere utilizzata per definire i nostri claim custom. In seguito abbiamo utilizzato i metodi
appositi per settare i claim registrati. Infine abbiamo firmato il token col metodo .signWith()
al quale abbiamo passato l’algoritmo da utilizzare e la chiave segreta (che NON SI DEVE
ESPLICITARE NEL CODICE ma sempre importare da una fonte che non si includerà
nel deploy, come ad esempio il file application.properties). Infine abbiamo utilizzato il me-
todo compact() per costruire il token come stringa URL-safe, in accordo con gli standard di
serializzazione del JWT.
• validateJwtToken(): viene utilizzato per validare il token, ovvero per verificare che la
request che ci arriva è autenticata (i.e. contiene un token) e che il toke è lo stesso che era
stato generato e inviato allo user in precedenza.
Per prima cosa, abbiamo bisogno di parsificare il token tramite il metodo parser(). Quindi
settiamo la chiave segreta che abbiamo utilizzato per generare il token, seguito dal metodo
parseClaimJws() per parsiare la JSON Web Signature (JWS) e ottenere la JWS dei Claims,
infine il metodo getBody() viene utilizzato per ottenere l’insieme dei claim utilizzati nella
generazione del token.
Da questi claim estraiamo il soggetto e la data di scadenza per verificare che il token sia
valido. Affinchè il token sia valido, lo username deve coincidere con lo username passato al
metodo e il token non deve essere scaduto.

JwtUserDetailsService
La seconda classe che creiamo è JwtUserDetailsService, che implementa la classe UserDetailsService
di Spring Security concretizzando il metodo loadUserByUsername():

@Service
public class JwtUserDetailsService implements UserDetailsService {
@Override

55
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {

//faccio la chiamata al database e se trovo lo user corrispondente


ritorno uno User (che implementa UserDetails) con username e pw
, altrimenti lancio l’eccezione, abbiamo volutamente
semplificato il codice in quanto dipende dalla struttura
specifica del progetto e da come vengono gestite le eccezioni

DTO dto = getDto(); //pseudocodice, sapete come fare

return new User( dto.getUSername(),


dto.getPassword(),
new ArrayList<>()
);

)
}

JWT Controller
Creiamo ora un controller che sarà responsabile dell’autenticazione:

@RestController
@CrossOrigin
public class JwtController {
//bean creati da noi
@Autowired
private JwtUserDetailsService userDetailsService;
@Autowired
private TokenManager tokenManager;

//la creeremo tra poco nella classe di configurazione principale


@Autowired
private AuthenticationManager authenticationManager;

@PostMapping("/login")
public ResponseEntity<> createToken(@RequestBody LoginDto dto) throws
Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
dto.getUsername(),
dto.getPassword()
)
);
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}

final UserDetails userDetails = userDetailsService.


loadUserByUsername(dto.getUsername());
final String jwtToken = tokenManager.generateJwtToken(userDetails);

Map<String,String> headers = new HashMap<>();


headers.put(’Autorization’, jwtToken);

56
return ResponseEntity(headers, HttpStatus.OK); //qui possiamo
inserire anche un body prima dell’header, per questo esempo ci
interessa solo il token
}
}

JwtFilter
Prima di vedere come creare l’AuthenticationManager, occupiamoci del filter. Questa classe è
usata per intercettare ogni request in entrata e vedere se contengono un token valido nell’header.
Se cosı̀ non fosse restituiamo un errore 401 (Unauthorized).

@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService userDetailsService;
@Autowired
private TokenManager tokenManager;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

String tokenHeader = request.getHeader("Authorization");


String username = null;
String token = null;
if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) {
token = tokenHeader.substring(7); //piglio il token vero e
proprio che inizia all’ottavo carattere (i primi 7 sono,
appunto, la stringa "Bearer ")

//qui ho due eccezioni da gestire: IllegalArgumentException e


ExpiredJwtException, la gestione viene omessa per essere
brevi
username = tokenManager.getUsernameFromToken(token);
} else {
System.out.println("Token non esistente o codificato male");
//ovviamente questo a puro scopo dimostrativo, comunicatelo
al client
}
if (null != username && SecurityContextHolder.getContext().
getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername
(username);
if (tokenManager.validateJwtToken(token, userDetails)) {
UsernamePasswordAuthenticationToken
authenticationToken = new
UsernamePasswordAuthenticationToken(
userDetails, null,
userDetails.getAuthorities()
);
authenticationToken.setDetails(new
WebAuthenticationDetailsSource().buildDetails(request))
;

SecurityContextHolder.getContext().setAuthentication(
authenticationToken);
}
}
filterChain.doFilter(request, response);

57
}
}

Infine, creiamo la classe JwtAuthenticationEntryPoint che estende la classe Authenticatio-


nEntryPoint di Spring e s i occupa di rimandare al client ogni request non autenticata con codice
d’errore 401:

@Component
public class JwtAuthenticationEntryPoint implements
AuthenticationEntryPoint,
Serializable {
@Override
public void commence(HttpServletRequest request, HttpServletResponse
response,
AuthenticationException authException)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Unauthorized");
}
}

Fine dei lavori


Possiamo finalmente concludere il tutto vedendo cosa aggiungere alla classe che abbiamo creato
all’inizio: MyWebConfiguration:

@Configuration
public class MyWebConfiguration {

@Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtFilter filter;
@Autowired
MyEncoderUtil myEncoderUtil;

@Bean
public AuthenticationManager authenticationManager(HttpSecurity http)
throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(myEncoderUtil.passwordEncoder())
.and()
.build();
}

@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) {
http.csrf().disable()
.authorizeRequests().antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(
authenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.
STATELESS);

http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.
class);

58
return http.build();
}
}

59
Parte II

Front-End

60
Capitolo 7

TypeScript

In questo capitolo introduciamo Javascript, affrontandone la trattazione assumendo una buona


conoscenza di base di Java. Ci concentreremo quindi sulle differenze tra i due linguaggi e sui punti
più cruciali, lasciando che siano gli snippet di codice inseriti a far prendere familiarità con le basi
e la sintassi.
Vedremo in seguito il typescript, un’estensione (superset) di javascript che introduce la tipizzazione.
Anticipiamo che il comportamento a runtime di javascript e typescript è identico, di conseguenza
nella seconda parte utilizzeremo ciò che abbiamo appreso su Javascript e vederemo come si traduce
in typescript.

7.1 Javascript
Una delle cose più sorprendenti di JavaScript (che d’ora in poi indicheremo solamente con JS per
brevità) è che nonostante sia un linguaggio orientato a oggetti, in esso non è presente il concetto
di classe (almeno fino a ECMAScript 6). Consideriamo ad esempio il seguente programma:

var eroe = {};

eroe.nome = "ContraderMan";
eroe.forza = 100;

Quello che abbiamo appena fatto è stato inizializzare un oggetto (eroe) e definirne due proprietà:
nome e forza. In JS non abbiamo una dichiarazione tipizzata come in java (ovvero per dichiarare
un oggetto non dobbiamo specificare di che tipo questo oggetto sia), per dichiararre una variabile
utilizziamo solamente la keyword var. NOTA BENE: la tipizzazione è comunque presente ma è
JS stesso a capire con che tipo di dati ha a che fare e a prendere gli accorgimenti necessari. JS è
quindi un linguaggio weakly typed.
In un certo senso, è utile pensare a un oggetto di JS come a una mappa <String, Object> Java,
ad esempio quanto appena visto si potrebbe pensare come:

Map<String, Object> eroe = new HashMap<>();

eroe.put("nome", "ContraderMan");
eroe.put("forza", 100);

7.1.1 JS Functions
Un concetto nuovo che JS introduce rispetto a Java è quello di Function. In JS una funzione
è semplicemente un valore di tipo Function. Riprendiamo l’esempio precedente e creiamo una
funzione che assegnamo a una variabile myFunction. Settiamo poi una nuova proprietà dell’oggeto
eroe utilizzando questa variabile.

61
var myFunction = function() {
console.log("La mia funzione");
}

eroe.parla = myFunction;

Possiamo chiamare delle function come se fossero dei metodi Java, ad esempio per chiamare la
funzione definita sopra scriveremo semplicemente eroe.parla().
La cosa che più si avvicina a questo approccio in Java è l’utilizzo di un’interfaccia funzionale. Ad
esempio, una possibile interpretazione di quanto appena fatto in Java potrebbe essere:

public interface Power {


void use();
}

public class Eroe {

private Power power;

public void setPower(Power power) {


this.power = power;
}

public parla() {
this.power.use();
}

public static void main(String[] args) {

Eroe eroe = new Eroe();

eroe.setPower(
() -> S.out.println("Nella mia funzione");
)

eroe.parla();
}

Vediamo ora come cambia l’utilizzo della keyword this: saremo sorpresi di scoprire che JS ci
permette di fare delle cose abbastanza carine rispetto a Java.
Iniziamo con un esempio:

var superMan = {

heroName: "SuperMan",

sayHello: function() {

console.log("Ciao, mi chiamo " + this.heroName );

};

superMan.sayHello();

62
Iniziamo con l’osservare come abbiamo dichiarato direttamente le proprietà dell’oggetto su-
perMan: tramite coppie (nome,valore) separati da due punti. Questo tipo di notazione è detta
JavaScript Object Notation, per gli amici JSON. In secondo luogo notiamo come abbiamo referen-
ziato la variabile heroName all’interno dello stesso oggetto, in maniera analoga a quanto faremmo
in Java. Ma che succede se tramandiamo la funzione a un oggetto che non ha nessuna variabile
heroName al suo interno? Ad esempio:

var nuovaFunzione = superMan.sayHello;

nuovaFunzione();

In questo caso otterremmo in output ”Ciao, mi chiamo undefined”. Per ovviare a questa
problematica, in JS possiamo passare il contesto a cui deve fare riferimento la keyword this tramite
il metodo .call, ad esempio se avessimo scritto:

var spiderMan = {
heroName: "Spiderman"
};

var nuovaFunzione = superMan.sayHello;

nuovaFunzione.call(spiderman);

avremmo ottenuto in putput ”Ciao, mi chiamo Spiderman”, in quanto la keyword this va a cercare
all’inteno dell’oggetto spiderman la proprietà ad essa associata.

7.1.2 Ereditarietà in JS
In JS non abbiamo ereditarietà a livello di classi: gli oggetti possono estendere direttamente
altri oggetti. Più nel dettaglio, ogni volta che definiamo un oggetto questo avrà una proprietà
implicita che punta a un ’oggetto padre’. Tale proprietà si chiama proto e l’oggetto a cui punta
è detto il prototype. Per questo motivo, questo tipo di ereditarietà viene chiamata Prototypical
Inheritance. Vediamo come funziona: quando chiamiamo una proprietà di un oggetto, JS la
cercherà inizialmente nell’oggetto stesso e, se non la trova, proverà nel prototype, poi nel prototype
del prototype e cosı̀ via. Vediamo un esempio per capire meglio:

var supereroe = {

editor: "EditorSerioSRL"

};

var spalMan = {};

spalMan.__proto__ = supereroe;

console.log(spalMan.editor); //stampa EditorSerioSRL

Il concetto di ereditarieta prototipica permette di svolgere in JS qualsiasi task che si può svolgere
in Java tramite l’ereditarietà classica. Ma che succede coi costruttori?

7.1.3 Costruttori e funzioni costruttore


In JS possiamo ritrovare un tentativo di rendere la creazione di un oggetto simile a JAVA, nella
forma delle funzioni costruttore. Una funzione costruttore definisce nella sua stessa dichiarazione
un oggetto, specificandone le proprietà e può essere chiamata tramite la keyword new alla stessa
maniera di un costruttore in Java. Ad esempio

63
function SuperHero(nome, forza) {
this.nome = nome;
this.forza = forza;
}

var contraderman = new SuperHero("Alberto", 169);

console.log(contraderman.nome); //Stampa "Alberto"

Questa sintassi è tuttavia sconsigliata: supponiamo ad esempio di voler far si che tutti i supere-
roi abbiano un metodo sayHello() al loro interno. Ad esempio, lo potremmo inserire in un prototype
object degli oggetti SuperHero tramite la sintassi FunzioneCostruttore.prototype.nomeMetodo,
come di seguito

SuperHero.prototype.sayHello = function() {
console.log("Ciao, il mio nome e’ " + this.nome);
}

Tuttavia questa soluzione è artificiosa e di difficile lettura, inoltre non assomiglia per nulla a
Java! Per questo motivo si preferisce accantonare del tutto l’utilizzo delle funzioni costruttore in
favore di Object.create. Vediamo subito un esempio per capire di che si tratta:

var superHeroPrototype = {

sayHello: function() {
console.log("Ciao, il mio nome e’ " + this.nome);
}

};

var superman = Object.create(superHeroPrototype);


superman.nome = "Superman";

Per maggiori dettagli su come utilizzare a pieno questo meccanismo rimandiamo a questo articolo.

7.1.4 Closure e Lambda


In JS, una Closure non è molto diversa da una funzione lambda di Java. Consideriamo il seguente
esempio (in Java):

public interface FlyCommand {


public void fly();
}

public class EoreVolante {

private String name;

public EroeVolante(String name) {


this.name = name;
}

public void fly(FlyCommand command) {


command.fly();
}

64
}

public static void main(String[] args) {


String destinazione = "Marte";

EroeVolante superMan = new EroeVolante("Superman");

superMan.fly(
() -> S.out.println("In volo verso " + destinazione)
);
}

In Java, ogniqualvolta referenziamo una variabile all’interno di una lambda, questa deve essere
(effectively) final, ovvero non deve cambiare. Se ad esempio nel main avessimo scritto

String destinazione = "Marte";

EroeVolante superMan = new EroeVolante("Superman");

superMan.fly(
() -> S.out.println("In volo verso " + destinazione)
);

destinazione = "Terra";

Avremmo ottenuto il seguente errore:


java: local variables referenced from a lambda expression must be final or effectively final.
Ciò non accade con le closure di JS. Quella che a prima vista può sembrare una differenza irrilevante
è in realtà una feature molto potente, in quanto ci consente di creare dei moduli incapsulati anche
senza avere a disposizione i modificatori d’accesso che ci fornisce Java. Consideriamo ad esempio:

function createHero(nome) {

var heroName = nome;

return {

fly: function(destination) {
console.log(heroName + "in volo per" + destination);
}

In questo caso di fatto l’unico modo per accedere alla variabile heroName è attraverso la funione
fly, la quale di fatto protegge l’accesso a heroName alla stessa maniera di un getter.
Ad esempio

var superman = createHero("Superman");

superman.fly("Marte");
console.log("Il nome da eroe e’ " + superman.heroName);

avremo in ordine ”Superman in volo verso Marte” e ”Il nome da eroe è undefined”. Una funzione
come createHero che incapsula delle informazioni accessibili solo tramite delle interfacce (che sono
restituite come Function nel return) è detta modulo.

65
7.1.5 Scope delle variabili
Un altra differenza da ricordare è l’assenza di uno scope per le variabili. Ciò si traduce, ad esempio,
nel fatto che una variabile utilizzata in un loop sia visibile anche ad di fuori di esso. Ad esempio:

function counterLoop() {

console.log(’counter before declaration = ’ + i);

for (var i = 0; i < 3 ; i++) {


console.log(’counter = ’ + i);
}

console.log(’counter after loop = ’ + i);


}

counterLoop();

darà come output:

counter before declaration = undefined


counter = 0
counter = 1
counter = 2
counter after loop = 3

È interessante anche notare come alla linea 3 non abbiamo un errore: questo in quanto l’interprete
scansiona l’intera funzione e salva la lista delle variabili al suo interno e solo dopo inizia a inter-
pretare la funzione riga per riga. Per via di questo comportamento, è best practice dichiarare tutte
le variabili che si andranno ad utilizzare all’inizio del codice, in modo da rendere il codice leggibile
e da evitare sorprese.
NOTA: a partire da ECMAScript 6 sono presenti anche le keyword let e const, entrambe le quali
ci consentono di dichiarare delle variabili con scope delimitato, con la seconda che consente anche
di rendere tali variabili immutabili. A differenza delle variabili dichiarate ocn var, quelle dichiarate
con let e const non sono disponibili fino a che la loro dichiarazione non è raggiunta nel codice, per
questo motivo dichiarare tutte le variabili all’inizio del codice è ancora più consigliato.

7.1.6 Classi in JS
Come anticipato all’inizio, a partire da ECMAScript6 è stato introdotto anche in JS il concetto di
classe. Queste sono di fatto delle ’funzioni speciali’ che presentano due componenti principali nella
sintassi: le dichiarazioni e le espressioni.
Per definire una classe tramite una dichiarazione utilizziamo la keyword class come segue:

class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}

Per istanziare una classe si può utilizzare la keyword new, alla stessa maniera di Java.
Un altro modo per definire una classe è tramite un’espressione, ad esempio

66
let Rectangle = class { //o anche class Nome per assegnare un nome
diverso dalla variabile
constructor(base, altezza) {
this.base = base;
this.altezza = altezza;
}
};
console.log()

Il metodo constructor è un metodo speciale, il quale permette di creare e inizializzare oggetti di


questa classe. Il funzionamento di una classe in JS è pressocchè identico alle classi Java, rimandiamo
a questo articolo per una descrizione completa.

7.2 TypeScript
TypeScript (che per brevità abbrevieremo con TS) è un’estensione di JS che introduce tipi, classi,
interfacce e muùoduli opzionali. È importante ricordare che typescript usa lo stesso runtime di JS.

7.2.1 Tipi in TS
In Java, siamo abituati al fatto che ogni valore/oggetto sia di uno e un solo tipo. In TS ciò non
è più vero: i tipi sono un insieme di valori che condividono qualcosa tra loro. Pensare ai tipi
come degli insiemi ci permette di semplificare alcune operazioni, come ad esempio passare come
parametro di un metodo un valore che può essere o una stringa o un intero, in quanto non esiste
un tipo che rappresenta un tale valore.
In TS ciò diventa naturale appena realizziamo che pensare ai tipi come adegli insiemi ci permette
di fare tutte le operazioni insiemistiche con tali tipi. Nell esempio precedente potremo indicare
che il tipo del parametro deve essere l’unione del tipo/insieme stringa e del tipo/insieme number,
tramite l’operatore — :

myMethod(param: string | number) { ... }

Un’altra differenza con quanto siamo abituati a fare in Java è che gli oggetti in TS non sono
di un tipo preciso. Ad esempio, se costruiamo un oggetto che soddisfa una data interfaccia,
possiamo usare tale oggetto ogniqualvolta una tale interfaccia è richiesta, anche se non abbiamo
mai dichiarato esplicitamente una relazione tra l’oggetto e l’interfaccia. Ad esempio:

interface Pointlike {
x: number;
y: number;
}
interface Named {
name: string;
}

function logPoint(point: Pointlike) {


console.log("x = " + point.x + ", y = " + point.y);
}

function logName(x: Named) {


console.log("Hello, " + x.name);
}

const obj = {
x: 0,
y: 0,
name: "Origin",

67
};

logPoint(obj);
logName(obj);

Poichè la const obj soddisfa sia l’interfaccia PointLike che quella Named, possiamo passare obj
come parametro sia di logPoint che di logName senza alcun problema.
I tipi in TS sono organizzati in maniera strutturale non nominale, ovvero sono le proprietà
al loro interno a sancire le relazioni tra tipi, non le loro dichiarazioni. obj implementa di fatto
entrambe le interfacce di sopra, nonostante nella sua dichiarazione ciò non figuri mai. (Se questo
tipo di libertà vi piace, clickate qui :) )
Alcune conseguenze della tipizzazione strutturale includono gli empty types e gli identical ty-
pes.
Vediamo di che si tratta esaminando due esempi:

class Empty {}

function fn(arg: Empty) {


[ ... ]
}

fn({ k: 10 });

In questo caso il codice è perdettamente valido in quanto l’oggetto k:10 ha tutte le proprietà di
Empty, in uqanto Empty non ha proprietà!

class Car {
drive() { //fa qualcosa }
}
class Golfer {
drive() { //fa qualcos altro }
}
//assegnamento valido
let w: Car = new Golfer();

Questo è un caso di identical types; anche in questo caso la dichiarazione è corretta in quanto
sia la classe Car che la classe Golfer hanno la stessa struttura. Per evitare errori, è buona norma
utilizzare identical types solo quando questi sono effettivamente correlati.

7.2.2 Tipi comuni


In questa sezione ci occupiamo di descrivere i tipi più comuni che si incontrano nella stesura di un
codice TS. I tre tipi primitivi di TS sono string, number e boolean. A differenza di Java, in TS
non abbiamo diversi valori specifici per diversi tipi di numero, come ad esempio float o int, ma fa
tutto parte di number.
Per indicare gli array si procede come in Java, semplicemente facendo seguire una coppia di pa-
rentesi quadre al tipo. Ad esempio, myArr : string[] dichiara che la variabile myArr è un array
di stringhe. Un altro modo per dichiarare un array è utilizzare Array¡T¿, dove T è il tipo degli
elementi dell’array. (Sı̀, i generici esistono anche in TS e il loro funzionamento è pressocchè identico
a Java, per una guida dettagliata guardate qui)
Un altro tipo importante è any, che può essere utilizzato quando vogliamo evitare che un parti-
colare valore causi errori di type-check. Quando dichiariamo un valore di tipo any, tutte le sue
proprietà saranno in automatico di tipo any. Inoltre esso potrà inoltre essere chiamato come una
function e assegnarlo a un altro valore di qualsiasi tipo. In pratica l’utilizzo di any disattiva ogni
meccanismo di controllo dei tipi, ad esempio le seguenti righe di codice sono tutte valide:

68
let obj: any = { x: 0 };
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

Vediamo ora come si applica la tipizzazione alle funzioni. In TS possiamo specificare sia il tipo
degli argomenti che il tipo dell’output di una funzione. La sintassi è la seguente:

myFunction(arg1 : Type1, ... argN : TypeN): returnType { ... }

Nell’esempio precedente la sintassi è la stessa se al posto di un tipo primitivo si passano degli


argomenti che hanno di tipo-oggetto, ad esempio

function stampaCoordinate( pt : { x: number; y: number}) {


console.log("L’ascissa ha valore " + pt.x);
console.log("L’ordinata ha valore " + pt.y);
}

stampaCoordinate({x: 3 , y: 4});

Per indicare che una proprietà di un oggetto è opzionale si fa seguire un punto di domanda al
nome della proprietà. Ad esempio, se vogliamo che la coordinata z sia opzionale nell’oggetto del
metodo precedente, scriveremmo:

function stampaCoordinate( pt : { x: number; y: number; z?: number}) {


console.log("L’ascissa ha valore " + pt.x);
console.log("L’ordinata ha valore " + pt.y);
if( z!== undefined) {
console.log("La cordinata z ha valore " + pt.z);
}
}

stampaCoordinate({x: 3 , y: 4});
stampaCoordinate({x: 1, y: 2, z: 3});

Ogniqualvolta si opera con una proprietà opzionale bisogna sempre controllare che essa non sia
undefined, come abbiamo fatto sopra.
Approfondiamo un attimo sull’unione di tipi. OCme anticipato prima, in TS possiamo indicare
che un valore possa essere di un itpo oppure un altro utilizzando l’operatore di unione — . Quando
lavoriamo con tipi unione, dobbiamo stare attenti a utilizzare solamente le proprietà che sono
comuni ai due metodi. Se per esempio proviamo a scrivere

function stampaID(id: number | string) {


console.log( id.toUpperCase() );
}

Otterremmo il seguente errore:


Property ’toUpperCase’ does not exist on type ’string — number’.
Property ’toUpperCase’ does not exist on type ’number’.
Una soluzione potrebbe essere quella di controllare il tipo effettivo dell’argomento, ad esempio
tramite l’operatore typeof, ma in generale va bene una qualsiasi soluzione che consenta di chiamare
proprità specifiche solo dopo essersi assicurati che l’oggetto si di tipo giusto.

69
function stampaID(id: number | string) {
if ( typeof id === "string" ) {
console.log( id.toUpperCase() );
} else { console.log(id); }
}

Un concetto a prima vista inutile ma che nasconde un gran potenziale è quello di tipo letterale.
Un tipo letterale indica che una variabile può far riferimento solamente a un valore specifico, ad
esempio

let x: "hello" = "hello";


// OK
x = "hello";
// ...
x = "Buonasera";
// avremo l’errore: Type ’"Buonasera"’ is not assignable to type ’"hello"’.

L’esempio di sopra utilizzava delle stringhe, tuttavia i tipi letterali si possono utilizzare anche per
number e boolean. Il potenziale nascosto dei tipi letterali è che utilizzando unioni di questi si può
garantire che un qualcosa accetti solamente determinati valori. Ad esempio, possiamo garantire
che una funzione accetti solamente degli input predefiniti:

function myFunction( libera: string, nonLibera: "destra" | "centro" | "


sinistra") { ... }
myFunction("ciao", "destra"); //ok
myFunction("Hola", "chica"); //errore: Argument of type ’"chica"’ is not
assignable to parameter of type ’"destra" | "centro" | "sinistra"’.

Vediamo infine due tipi primitivi che vengono utilizzati per indicare un valore assente o non ini-
zializzato: null e undefined. Il comportamento di entrambi questi tipi dipende dall’opzione di
compilazione strictNullChecks. Se tale opzione è disattivata, si può accedere a valoripotenzial-
mente null/undefined e questi possono essere assegnati normalmente. Se invece tale opzione è
attiva, bisognerà testare un valore potenzialmente null/undefined prima di poterci operare, come
avevamo visto in precedenza per le proprietà opzionali. Per il controllo su null possiamo testare
direttamente per uguaglianza con il tipo null, ad esempio

function doSomething(x: string | null) {


if (x === null) {
// do nothing
} else {
console.log("Hello, " + x.toUpperCase());
}
}

In alternativa, TS ci mette a disposizione l’operatore ! per indicare che un valore non deve essere
null/undefined. Tramite questo operatore, possiamo ad esempio scrivere:

function doSomething(x?: number | null) {


console.log(x!.toFixed());
}

70
Capitolo 8

HTML e CSS

In questo capitolo affronteremo le basi di html e css, due tool fondamentali per lo sviluppo di una
qualsiasi applicazione web in quanto responsabili della parte grafica e strutturale di questa.

8.1 HTML
HTML sta per HyperText Markup Language ed è il linguaggio di markup standard per la creazione
di pagine web. Esso si occupa did escrivere la struttura di una pagina tramite una serie di elementi,
i quali dicono al broswer come mostrare a schermo il contenuto.

8.1.1 Struttura
Ogni documento html ha una struttura ben precisa:

<!DOCTYPE html>
<html>
<head>
metadati e altre informazioni
<title>Page Title</title>
</head>
<body>

Contenuto della pagina

</body>
</html>

1. il tag !DOCTYPE html dichiara che un documento è da trattarsi come un documento


HTML5.
2. l’elemento html contiene tutto il documento: tutti gli altri elementi devono andare al suo
interno.
3. l’elemento head contiene meta-dati relativi alla pagina. Un esempio di elemento contenuto
al suo interno è title, che specifica il titolo della pagina in questione ed è visualizzato nella
del broswer.
4. l’elemento body delimita il corpo della pagina. Al suo interno andranno quindi tutti gli
elementi che si vogliono visualizzare, come ad esempio intestazioni, paragrafi, tabelle, link,
immagini, liste etc.

8.1.2 Elementi HTML


Un elemento html è definito da un tag di apertura ed un tag di chiusura: tutto ciò che è contenuto
tra questi due tag è l’elemento vero e proprio.

71
<tag> contenuto </tag>
ad esempio

<h1> Ciao mondo, questo un heading </h1>


<p> Questo un paragrafo </p>
<br>

Alcuni elementi, come l’elemento <br> di sopra (che serve per andare a capo), noon hanno nè
contenuto nè un tag di chiusura. Questi elementi sono detti empty elements.
Vediamo ora brevemente alcuni degli elementi più importanti:
• div: è un generico elemento html che viene solitamente utilizzato come contenitore per
dividere il contenuto del body in più zone. Il tag associato è <div>.

• Headings: sono definiti dai tag <h1>, <h2>, ... , <h6>, in ordine di importanza.
• Paragrafi: sono definiti dal tag <p>.
• Links: sono definiti dal tag <a>. Il tag di apertura contiene al suo interno l’attributo href,
nel quale si specifica la destinazione del link. Gli attributi vengono utilizzati in generale per
fornire informazioni aggiuntive su un elemento HTML, li vedremo più nel dettaglio a breve.

<a href="http://www.sitosicuro.xyz/"> Clicka qui trust bro </a>

• Immagini sono definite dal tag <img>. Questo è un empty element. All’interno del tag
di apertura troviamo gli attributi src, alt, width e heigth, i quali indicano rispettivamente
la fonte dell’immagine, un testo da visualizzare qualora l’immagine non fosse disponibile, la
larghezza e l’altezza dell’immagine. Questi attributi non devono per forza andare nell’ordine
detto qui sopra.

<img src="gattino.jpg" alt="Foto di un gattino" width="104" height


="142">

L’attributo source può far riferimento sia a un’immagine hostata all’interno del sito sia a un
URL esterno.

8.1.3 Attributi
Vediamo più nel dettaglio alcuni attributi e le best practices legate al loro utilizzo.

Style
L’attributo style è utilizzato per specificare lo stile grafico di un dato elemento. È buona norma
utilizzarlo solamente in fase di test e specificare lo stile finale di una pagina all’interno di un foglio
di stile apposito, come vedremo nella seconda parte di questo capitolo. Ad esempio, per stilizzare
un paragrafo rendendo il testo rosso faremmo:

<p style="color:red;"> Il mio paragrafo rosso</p>

72
Lang
L’attributo lang viene utilizzato all’interno del tag <hmtl> per specificare la lingua della pagina.
Questo non influisce su come la pagina viene visualizzata ma aiuta i motori di ricerca a visualizzare
le pagine più rilevanti. Il linguaggio viene specificato attraverso delle abbreviazioni, come ”en” per
l’inglese o ”it” per l’italiano.

<html lang="it">
...
</html>

L’attributo title
Viene utilizzato per definire informazioni aggiuntive su un elemento, che verranno visualizzate
quando si passa sopra all’elemento col mouse:

<p title="Info aggiuntive"> Mio paragrafo </p>

Class
L’attributo class viene utilizzato per indicare un gruppo a cui l’elemento appartiene. È partico-
larmente utile quando si vuole far riferimento a più elementi in maniera concisa.

<p class="mio_paragrafo"> Questo un mio paragrafo </p>

ID
L’attributo id viene utilizzato quando si vuole individuare univocamente un elemento all’interno
della pagina.

<p id="my-id"> Questo un mio paragrafo individuato univocamente </p>

Suggerimenti e best practices


Lo standard HTML non richiede l’utilizzo di caratteri minuscoli per i nomi degli attributi, ad
esempio potremmo scrivere equivalentemente title=”Info aggiuntive” o TITLE=”Info aggiuntive”.
Tuttavia è raccomandato dal W3C il primo approccio, in quanto in linea con altri documenti che
invece lo richiedono, come l’XHTML. Un’altra best practice è quella di racchiudere il valore degli
attributi all’interno di virgolette o apici. Ad esempio potremmo scrivere href = miolink.xyz o
href=’miolink.xyz’ al posto di href = ”miolink.xyz”. L’utilizzo degli apici al posto delle virgolette
è necessario quando all’interno del valore sono presenti delle virgolette, come ad esempio title
= ’Dwayne ”The Rock” Johnson’. L’utilizzo di apici/virgolette è inoltre necessario se il valore
contiene uno spazio al suo interno. È inoltre raccomandato che un id venga utilizzato per un unico
elemento.

8.1.4 Tabelle
Le tabelle ci permettono di organizzare i dati all’interno di righe e colonne. Iniziamo con un
esempio e analizziamo i vari elementi che appaiono:

73
<table>

<tr>
<th>Company</th>
<th>Contact</th>
<th>Country</th>
</tr>

<tr>
<td>Alfreds Futterkiste</td>
<td>Maria Anders</td>
<td>Germany</td>
</tr>

<tr>
<td>Centro comercial Moctezuma</td>
<td>Francisco Chang</td>
<td>Mexico</td>
</tr>

</table>

• <table> è il tag che definisce la tabella, esso contiene al suo interno tutti gli altri elementi
che costituiscono i vari blocchi.
• <tr> (table row ) definisce una riga della tabella, contiene al suo interno tutti i blocchi relativi
a tale riga.
• <td> (table data) definisce un blocco base della tabella.
• <th> (table header ) definisce un blocco di intestazione della tabella.

La struttura di una tabella è abbastanza semplice, tuttavia questo è uno strumento molto potente
in quanto permette una facile organizzazione dei dati all’interno della pagina. HTML ci mette
inoltre a disposizione dei tag aggiuntivi che ci consentono di modellare più raffinatamente una
tabella. Alcuni di questi tag includono:
• <caption> consente di aggiungere un titolo alla tabella: <caption> Mio titolo </caption>.
• <colgroup> e <col> definiscono rispettivamente un gruppo di colonne e le proprietà
relative a quel gruppo di colonne.
• <thead> definisce l’header della tabella.
• <tbody> definisce il corpo della tabella.
• <tfoot> definisce il footer della tabella.

8.1.5 Liste
Le liste ci consentono di raggruppare elementi correlati tra loro sotto forma di liste. Le liste possono
essere principalmente di due tipi: unordered e ordered. In entrambi i casi, gli elementi della lista
sono specificati dal tag <li> (list item).

Unordered List
Una lista di questo tipo è specificata dal tag <ul> (unordered list), all’interno del quale sono
specificati gli elementi.

<ul>
<li>Caff</li>
<li>T</li>
<li>Latte</li>
</ul>

74
sarà visualizzata come segue
• Caffè
• Tè
• Latte

Orderd List
Una tale lista è specificata dal tag <ol> (orderd list). Gli elementi di questa lista sono associati a
un numero, in base all’ordine in cui vengono dichiarati. Ad esempio, la stessa lista di sopra sarà
visualizzata cosı̀:
1. Caffè
2. Tè
3. Latte

Description List
In aggiunta alle liste ordinate e non, HTML supporta le liste descrittive, definita dal tag <dl>
(description list). A differenza dei due casi precedenti, gli elementi di questo tipo di lista sono
composti da due tag: il primo è <dt> (data term) indica il nome dell’elemento, mentre il secondo
è <dd> (data description) si occupa di descriverlo. Ad esempio:

<dl>
<dt>Caff</dt>
<dd>100% Arabica</dd>
<dt>T</dt>
<dd>Qualita’ matcha verde</dd>
</dl>

<!--output-->
Caff
- 100% Arabica
T
- Qualita’ matcha verde

8.2 CSS
Il CSS (Cascading Style Sheet) è la porzione di codice che si occupa di stilizzare i contenuti di una
pagina web. Per applicare del CSS a una pagina html abbiamo due modi:
• Includere il codice nell’head della pagina all’interno di un tag <style>.
• Salvare il codice all’interno di un file apposito (con estensione .css) e poi importarlo nell’head
della pagina tramite un tag link:

<link href="styles/style.css" rel="stylesheet">

Questo è l’approccio raccomandato, in quanto permette una più facile modularizzazione della
pagina. L’attributo rel sta per relationship e indica che il file che stiamo importando avrà il
ruolo di foglio di stile per la pagina.
Vediamo ora un esempio di codice CSS e analizziamone la struttura:

p {
color: red;
}

75
Questo snippet rende rosso il colore del testo in tutti gli elementi paragrafo della pagina. Esso
si divide in due macro parti: il selettore (in questo caso p) e il blocco delle dichiarazioni, ovvero
le regole della forma proprietà:valore che si vogliono applicare (in questo caso color: red;). Le
dichiarazioni devono essere racchiuse in un blocco tra parentesi graffe e separate tra loro da un
punto e virgola.

8.2.1 Selettori
Ci sono vari tipi di selettori, ad esempio, la p nel codice di sopra è detto selettore di elemento,
che seleziona tutti gli elementi di un tipo specifico, in questo caso tutti i paragrafi. Altri tipi di
selettore includono:
• Selettore di ID: seleziona l’elemento con l’id specificato. Ad esempio

html:
<p id = "my-unique-id"> Questo testo rosso </p>

css:
#my-unique-id {
color: red;
}

• Selettore di classe: seleziona tutti gli appartenenti a una data classe, ad esempio

html:
<p class="my-class"> Questo testo rosso </p>
<h1 class="my-class"> Anche questo testo rosso </h1>

css:
.my-class {
color: red;
}

• Selettore di attributo: seleziona gli elementi della pagina che hanno l’attributo specificato,
ad esempio:

html:
<p title="info"> Questo paragrafo lo modifico </p>
<p> Questo no</p>

css:
p[title] {
color: red;
}

• Selettore di stato o pseudo-class selector: selezionano un determinato elemento quando


si trova in uno stato specifico. La sintassi è selettore base:stato, dove il selettore base è uno
qualunque tra quelli discussi fin’ora.
Ad esempio, modifichiamo un paragrafo per rendere il testo rosso quando ci passiamo sopra
col mouse:

html:
<p> Mio paragrafo </p>

css:
p:hover {
color:red;
}

76
8.2.2 Dichiarazioni
In CSS, le dichiarazioni ci permettono di stilizzare un qualsiasi elemento in maniera molto appro-
fondita. Vediamo un esempio per iniziare a prendere familiarità con alcune di queste:

body {
width: 600px;
margin: 0 auto;
background-color: #ff9500;
padding: 0 20px 20px 20px;
border: 5px solid black;
}

Andiamo con ordine:


• Il selettore è body, ciò indica che le dichiarazioni riguardano tutti gli elementi della pagina
html.
• width:600px; indica che il body avrà sempre larghezza 600px. Questo tipo di dichiarazione
è assoluta e vedremo che non è un buona idea se si vogliono progettare dei siti responsive.
• margin: 0 auto; è una dichiarazione con più valori, il primo indica il margine verticale (che
in questo caso è messo a zero), mentre il secondo indica il margine orizzontale (in questo
caso, il valore auto divide lo spazio disponibile in maniera omogenea tra destra e sinistra).
Questa sintassi è analoga per la proprietà padding.
• background-color: #FF9500; setta il colore dello sfondo tramite il codice hex passato. In
generale, in css possiamo specificare un colore anche tramite tripla RGB o tramite il nome.
• padding: 0 20px 20px 20px; quando si usano quattro valori in una dichiarazione di
questo tipo, essi fanno riferimento in ordine a sopra, destra, sotto e sinistra. Ciò è analogo
per margin.
• border: 5px solid black; questa dichiarazione specifica larghezza, stile e colore del bordo
di tutto il body.
Una descrizione di ogni possibile dichiarazione per ogni possibile elemento è fuori dal nostro focus
per queste dispense, il mio consiglio è al solito di smanettare cercando sul web quando si han-
no necessità specifiche. Per terminare il capitolo, concentriamoci su due concetti fondamentali:
position e flexbox.

8.2.3 Position
Concentriamoci ora sulla proprietà position, che permette di specificare il tipo di posizionamento
di un elemento. Questa può assumere in tutto 5 valori:
1. static
2. relative
3. fixed
4. absolute
5. sticky
Inoltre, settare la proprietà position abilita l’uso delle proprietà top, bottom, left e right, che
consentono di posizionare effettivamente l’elemento. Il comportamento di queste ultime dipende
inoltre dal valore dato a position!

position: static
Questo è il tipo di posizionamento di default per gli elementi html. In questo caso le proprietà top,
bottom, left e right non hanno alcun effetto sull’elemento, il quale è sempre posizionato in accordo
con la struttura html della pagina.

77
position: relative
Questo tipo di posizionamento fa si che le proprietà top, bottom, left e right modifichino la posizione
dell’elemento rispetto alla sua posizione naturale. In questo caso la posizione degli altri contenuti
non verrà aggiustata per colmare eventuali spazi vuoti lasciati dall’elemento.

position: fixed
Questo tipo di posizionamento fa sı̀ che un elemento sia posizionato relativamente alla viewport,
che significa che l’elemento starà sempre allo stesso posto anche quando la pagina viene scrollata.
Ad esempio, se volessimo posizionare un elemento in basso a destra scriveremmo:

div.elemento {
position: fixed;
bottom: 0;
right: 0;
//altre eventuali dichiarazioni
}

position: absolute
Questo tipo di posizionamento fa sı̀ che un elemento sia posizionato relativamente all’elemento che
lo contiene che abbia anch’esso una dichiarazione position. Se un tale elemento non è presente
questo posizionamento fa automaticamente riferimento al body del documento. Gli elementi po-
sizionati in questo modo non seguono lo scorrere normale della pagina e possono sovrapporsi ad
altri elementi.

position: sticky
Un elemento con questo tipo di posizionamento è posizionato in base allo scroll in atto sull’elemento
che lo contiene. In pratica un elemento sticky cambia il suo posizionamento tra relative e fixed
in base alla posizione di scroll: è posizionato in maniera relative fino a che non si raggiunge una
posizione di offset data, raggiunta tale posizione diventa fixed. Ad esempio, stilizziamo una div in
modo che rimanga fissa in cima quando si raggiunge la sua posizione scrollando col mouse:

div.sticky {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0;
}

8.2.4 FlexBox
Il layout FlexBox (abbreviazione di Flexible Box ) provvede una maniera efficiente di disporre e
allineare elementi all’interno di un contenitore, anche quando le loro dimensioni sono sconosciute
e/o dinamiche. L’idea principale che sta dietro a quest’approccio è dare all’elemento contenitore
il potere di modificare ordine e dimensioni degli elementi al suo interno in maniera da gestire in
modo ottimale lo spazio disponibile in base al display del dispositivo utilizzato. Un flex container
si espande e riduce automaticamente a sua volta a tale scopo.
Iniziamo a vedere la terminologia e le proprietà associate a questo layout. Iniziamo con dare
un’occhiata alla seguente immagine per capire il flow degli elementi:

78
• main axis: è l’asse principale rispetto a cui gli elementi del container sono posizionati.
Questa non è necessariamente orizzontale, ma dipende dalla proprietà flex-direction (che
tratteremo tra poco).
• main-start — main-end: indicano le estremità rispetto a cui gli elementi sono posizionati.
• main size: corrisponde alla dimensione principale tra l’altezza e la larghezza.
• cross axis: è l’asse perpendicolare a quella principale. La sua direzione dipende dalla
direzione di quest’ultimo.
• cross-start — cross-end Indicano la direzione delle linee su cui vengono disposti gli oggetti.
• cross size è la dimensione secondaria rispetto alla main size.

Vediamo ora le proprietà legate a flexbox, iniziando da quelle del container:

Proprietà del flex container


• display: flex definisce l’elemento come un flex container, abilitando quindi tutte le proprietà
flex per gli elementi al suo interno.
• flex-direction: stabilisce la direzione e il verso del main-axis. I valori che può prendere
sono:
1. row: (valore di default) da sinistra a destra.
2. row-reverse: da destra a sinistra.
3. column: dall’alto verso il basso.
4. column-reverse: dal basso verso l’alto.
• flex-wrap: indica come organizzare gli item nelle varie righe. I valori che può prendere sono:
1. nowrap: (valore di default) posiziona tutti gli elementi in un’unica riga.
2. wrap: dispone gli elementi su più righe, in ordine dall’alto verso il basso.
3. wrap-reverse: uguale a wrap ma dal basso verso l’alto.
• flex-flow: combina le due precedenti e ha come valore di default row nowrap (i.e. i due
valori di default rispettivi). Accetta come valore una qualsiasi combinazione dei valori di
sopra.
• justify-content: definisce la disposizione dei contenuti rispetto al main axis. I valori che
può prendere sono:
1. flex-start: (default) gli elementi sono spinti verso l’inizio della flex-direction.
2. flex-end: come sopra ma verso la fine.
3. start (/ end): gli elementi sono spinti verso l’inizio (/fine) della direzione indicata in
writing-mode.
4. left (/ right): gli elementi sono spinti verso sinistra (/destra).
5. center: gli elementi sono disposti al centro della riga a cui appartengono.

79
6. space-between: gli elementi sono distribuiti uniformemente sulla riga, col primo attac-
cato all’inizio e l’ultimo attaccato alla fine.
7. space-around: gli elementi sono distribuiti uniformemente sulla riga.
8. space-evenly: gli elementi sono disposti in modo che lo spazio tra due elementi ’confi-
nanti’ sia sempre lo stesso.
• align-items: definisce come gli elementi sono disposti sulla loro riga rispetto al cross-axis.
I valori che può prendere sono:
1. stretch: (default) adatta le dimensioni degli elementi per coprire lo spazio disponibile.
2. flex-start / start / self-start: gli elementi sono disposti all’inizio del cross-axis. Le
differenze tra questi sono analoghe al caso del justify-content.
3. flex-end / end / self-end: come sopra ma rispetto alla fine del cross-axis.
4. center
5. baseline: gli elementi sono allineati in modo che le loro basi siano sullo stesso livello.

• align-content: è utilizzato per disporre le righe di un flex container quando vi è dello spazio
in più nella direzione del cross-axis. I valori che può prendere sono:
1. normal: (default)
2. flex-start / start: le righe sono organizzate all’inizio del container. Flex-start segue la
flex-direction mentre start la direzione di writing-mode.
3. flex-end/end: come sopra ma rispetto alla fine del container.
4. center
5. space-between: le righe sono distribuite uniformemente, la prima all’inizio del container
e l’ultima alla fine.
6. space-around / space-evenly / stretch: analoghe al caso di justify content ma per le
righe.

Proprietà dei flex item


• order: controlla l’ordine in cui l’elemento appare nel container.
• flex-grow: indica l’abilità di un elemento di aumentare le proprie dimensioni se necessario.
Prende come valore un numero puro (senza unità di misura) che serve come proporzione. Se
tutti gli elementi hanno questa proprietà con valore 1, lo spazio rimanente viene distribuito
in maniera uniforme tra essi; se uno di questi la ha a 2 riceverà il doppio dello spazio rispetto
agli altri e cosı̀ via.
• flex-shrink: come sopra, ma riguarda l’abilità di decrescita.

• flex-basis: definisce le dimensioni di partenza prima che lo spazio rimanente venga distri-
buito. Il valore può essere una lunghezza o una keyword:
1. auto: rimanda alla dimensione principale.
2. content: rimanda alla dimensione del contenuto dell’elemento.
• flex: combina flex-grow, flex-shrink e flex-basis, con il secondo e terzo parametro opzionale
e valori di default 0 1 auto. È raccomandata rispetto a usare le tre proprietà individuali per
brevità.
• align-self : sovrascrive il valore della proprietà align-items del container per l’elemento in
questione. Può prendere gli stessi valori di quest’ultima.

A questo link trovate un comodo cheat-sheet per la visualizzazione di queste proprietà.

80
Capitolo 9

Angular

Angular è una piattaforma di sviluppo basata su TypeScript. Tra le sue features, angular include:
• Un framework componen-based per lo sviluppo di single page web-app scalabili.
• Un insieme di librerie built-in che provvedono una vasta varietà di funzionalità, tra cui
supporto alla navigazione, gestione dei form e comunicazione client-server.

• Una developer tools suite che copre le fasi di sviluppo, testing, build e update dell’applica-
zione.
Alcuni dei vantaggi e svantaggi comportati dall’utilizzo di angular includono:

Vantaggi
• Implementazione dell’architettura MVVM (Model View View-Model), che consente di isolare
la logica dal layer di interfaccia grafica e quindi di separare effettivamente le funzionalità
dell’app, rendendo più facile lo sviluppo e il mantenimento. In questa architettura, il layer
Model è rappresentato dai DTO che codificano gli oggetti con cui l’app avrà a che fare (che,
per garantire una corretta implementazioen di un’app full stack, devono essere uguali ai
DTO definiti nel lato server). Il layer View è invece rappresentato dai file .html e .css, che
curano appunto la parte grafica della nostra app. Il layer View-Model è infine dato dai file
TypeScript che regolano la dinamicità delle componenti, rappresentando di fatto la logica di
collegamento tra la View e il Model.
• Struttura dell’app in moduli, che consente di incapsulare varie funzionalità strettamente
legate tra loro all’interno di un unico scope.
• Utilizzo dei service e implementazione della Dependency Injection, con tutti i vantaggi che
questo comporta (vedere discussione qui Capitolo 3).
• Utilizzo di TypeScript come linguaggio, che consente di eliminare gran parte degli errori più
comuni in fase di sviluppo, rendendo il codice più pulito e facile da comprendere rispetto a
JavaScript.

Svantaggi
• Curva di apprendimento molto ripida, in particolare se paragonata ad altri framework quali
REACT o VUE.
• Verbosità e complessità.
• Limitatezza nelle opzioni delle SEO (Search engine optimization) e una poco supportata
accessibilità ai search engine crawlers (che di base consentono la calssificazione dei risultati
di una ricerca in ordine di rilevanza).

Per terminare l’introduzione, descriviamo brevemente come installare Angular e creare un primo
progetto vuoto. Per prima cosa, abbiamo bisogno di installare la command line interface (CLI)
di Angular. Per fare ciò possiamo affidarci a npm (Node Package Manager), che ci viene fornito
nell’installazione base di NodeJS (un potente runtime environment per JS).
Dopo aver installato Node apriamo un terminale e lanciamo il comando npm install -g @angular/cli

81
(il flag -g sta per global e rende anhular disponibile all’intero sistema). Terminata l’installazione,
possiamo creare la struttura base di un progetto direttamente sa linea di comando tramite ng new
nome-app. Questo comando genera in automatico tutte le configurazioni di base e inizializza il
modulo principale, inoltre ci consente qualora lo volessimo (ovvero sempre) di aggiungere le opzioni
di routing alla nostra app. Non male per un singolo comando!
Possiamo già lanciare la nostra applicazione base: spostiamoci all’interno della cartella del progetto
(che verrà creata automaticamente dal comando precedente) e lanciamo l’app sempre dalla cli
tramite ng serve -o (Dove il flag -o oppure l’equivalente –open aprono automaticamente il broswer al
localhost:4200, che è la porta di default per le app angular). Terminate le fasi iniziali, addentriamoci
nel funzionamento del framework!

9.1 Moduli
Un modulo, come anticipato in precedenza, consiste nella cellula base di un progetto Angular,
all’interno del quale vengono incluse tutte le funzionalità che sono legate strettamente tra loro.
Alla creazione del progetto, angular ci fornisce in partenza l’app-module. Questo è il modulo
principale dell’app, composto da 5 file:
• app.module.ts: è il file che gestisce di fatto l’applicazione: al suo interno vengono dichiarati
tutti gli altri moduli (sia interni che importati) che verranno utilizzati nell’applicazione.
Viene inoltre specificata la componente da bootstrappare all’avvio dell’applicazione.
• app.component.ts /.html /.css: sono i tre file che definiscono la componente principale
associata a questo modulo. Esploreremo più nel dettaglio le componenti nella prossima
sezione.
• app-routing.module.ts: è il file in cui vengono specificate le path, ovvero delle coppie url/-
component che verranno utilizzate dal router per la navigazione all’interno dell’applicazione.
In generale, per creare un nuovo modulo utilizzeremo il commando ng generate module nome-
modulo. Lanciando questo comando verrà in automatico creata una nuova directory, all’interno
della quale potremo un file nome.module.ts. Se vogliamo aggiungere un file per il routing inter-
no al modulo possiamo runnare lo stesso comando aggiungendo il flag –routing prima del nome.
Esaminiamo la struttura di un file *.module.ts: la prima cosa che notiamo è il decoratore @Ng-
Module. Questo definisce che un tale file contiene una classe che rappresenta un modulo angular.
Questo decoratore prende come argomento un oggetto JSON che viene utilizzato per specificare
varie funzionalità del modulo:
1. declarations: specifica le componenti contenute all’interno del modulo. Di default è vuoto
(in quanto non abbiamo ancora creato nessuna componente).
2. imports: specifica i moduli che vengono importati all’interno del modulo. Gli exportables (ov-
vero gli oggetti dichiarati con la keyword export presenti in questi moduli saranno utilizzati
all’interno di ogni template del nostro modulo.) Di default contiene CommonModule, il quale
fornisce le funzionalità di base di Angular che approfondiremo in seguito, e (se lo abbiamo
aggiunto) il relativo modulo di routing. Solamente queste prime due proprietà (declarations
e imports) saranno presenti alla creazione del modulo.
3. providers: nel quale vengono specificati tutti i moduli importati dei quali si vuole utilizzare
un injectable, ovvero un oggetto di cui si vuole iniettare la dipendenza all’interno di una
componente del nostro modulo.
4. bootstrap: la lista delle componenti che vengono automaticamente lanciate quando il nostro
modulo viene lanciato.
La classe decorata da @NgModule è generalmente vuota e dichiarata tramite la keyword export
se si vuole rendere disponibile il contenuto del modulo all’interno dell’app. Esaminiamo come
queste proprietà vengano utilizzate nel routing module: all’interno di questo file viene innanzitutto
dichiarata una const di tipo Routes (ovvero un array di oggetti Route), la quale specifica una lista
di coppie url/component La struttura di tale array è la seguente:

const routes: Routes = [


{ path: "/miopath", component: MiaComponent }
];

82
Un oggetto di tipo route comprende inoltre la proprietà opzionale children, la quale prende come
valore un array di Route, i path delle quali sono da considerarsi relativi al path della route madre.
Ad esempio:

const routes: Routes = [


{ path: "/miopath", component: MiaComponent, children: [
{path: "/figlio1", component: PrimoFiglio},
{path: "/figlio2", component: SecondoFiglio}
]
}
];

Questa const verrà poi passata come argomento al metodo forRoot del RouterModule all’interno
degli import, il quale crea e configura un modulo con tutte le routes e le direttive fornite. Alla fine
la struttura sarà qualcosa di simile:

const routes: Routes = [


{ path: "/miopath", component: MiaComponent, children: [
{path: "/figlio1", component: PrimoFiglio},
{path: "/figlio2", component: SecondoFiglio}
]
}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class NomeRoutingModule {}

9.2 Componenti
Le compoonenti sono il building block principale di una applicazione angular. Ciascuna di esse
consiste di tre file:
1. un template HTML che specifica ciò che viene mostrato a schermo.
2. una classe TypeScript che regola il comportamento della componente.

3. Un file CSS (opzionale) che definisce lo stile della componente.


Per creare una componente, apriamo un terminale all’interno del modulo a cui vogliamo che que-
sta componente appartenga e lanciamo il comando ng generate component nome-comp dalla cli.
Questo creerà una cartella contenente i tre file menzionati qui sopra, assieme a un file contente le
specifiche per il testing. Inoltre registrerà automaticamente la component all’interno delle decla-
rations del modulo contenitore.
Esaminiamo in particolare il file .component.ts, nel quale è presente la classe che gestisce il com-
portamento della componente. Tale classe è decorata con @Component, il quale specifica al suo
interno tre proprietà:
1. selector: specifica il selettore css che può essere utilizzato nei template per includere la
componente come una direttiva. Di default ha la forma app-nome-comp.
2. templateUrl: specifica il path relativo o l’url del file html della component. Di default fa
riferimento al file creato automaticamente dal comando.
3. styleUrls: definisce un array di url relativi ai file css da utilizzare per lo stile della componente.
Di default l’array include solamente il file creato automaticamente dal comando.

83
All’interno della classe (dichiarata come exportable) troviamo invece tutta la logica che regola il
comportamento della view. Di default essa implementa l’interfaccia OnInit, la quale espone un
metodo (ngOnInit(): void ) che consente di specificare tutte le azioni da effettuare ogni volta che la
componente viene lanciata. Contiene inoltre un costruttore, il quale verrà utilizzato principalmente
per andare a iniettare le dipendenze che ci occorrono (vedremo più nel dettaglio come in seguito).
Ad esempio, la seguente componente contiene una variabile che viene inizializzata appena la classe
viene creata:

import { Component, OnInit } from ’@angular/core’;

@Component({
selector: ’app-superdummy’,
templateUrl: ’./superdummy.component.html’,
styleUrls: [’./superdummy.component.css’]
})
export class superdummyComponent implements OnInit {

miaVar: string;

constructor() { }

ngOnInit(): void {
miaVar = "Sono inizializzata";
}

Il metodo ngOnInit è detto lifecycle hooks, ovvero un metodo che regola un momento della vita
della componente, in questo caso la creazione. Esso è l’unico di questi eventi che si deve obbliga-
toriamente implementare dal contratto con l’interfaccia, i restanti sono comunque utili e una loro
descrizione dettagliata è consultabile qui.

9.2.1 Comunicazione tra componenti


Sarebbe conveniente avere un modo per condividere i dati tra una componente madre e le rispettive
componenti figlie. Angular ci viene incontro in ciò tramite l’utilizzo di due decoratori: @Input()
e @Output(), entrambi importabili da @angular/core. @Input() permette alla component madre
di aggiornare dei dati nella component figlia, @Output() permette l’analogo da figlia a madre.
Vediamo come utilizzare entrambi:

@Input()
Per utilizzare questo decoratore è necessario configurare sia la componente madre che quella figlia.
Supponiamo ad esempio di voler passare una stringa, innanzitutto dichiariamo la variabile nella
component figlia e decoriamola:

@Component({
selector: ’app-figlia’,
templateUrl: ’./figlia.component.html’,
styleUrls: [’./figlia.component.css’]
})
export class figliaComponent implements OnInit {

@Input() myVar: string = ’’;

constructor() { }

ngOnInit(): void {
}

84
In seguito leghiamo tale stringa a un dato presente nella component madre come segue:
1. richiamiamo la componente figlia nel template della madre tramite il suo selettore CSS.
2. utilizzaimo il data binding per legare myVar alla variabile che vogliamo passare all’interno
del tag (ad esempio alla variabile ”varMadre” opportunamente dichiarata nella component
madre)

<app-figlia
[myVar]="varMadre"
></app-figlia>

@Output()
Questo decoratore funziona in maniera leggermente differente: esso decora una proprietà nella
component figlia che si vuole trasmettere alla component madre. Vediamo come:

1. si codifica tale proprietà come un EventEmitter (importabile anche esso da @angular/core)


e lo si decora con @Output(). Supponiamo ad esempio di voler comunicare una stringa:

@Component({
selector: ’app-figlia’,
templateUrl: ’./figlia.component.html’,
styleUrls: [’./figlia.component.css’]
})
export class figliaComponent implements OnInit {

@Output() myVar = new EventEmitter<string>();

constructor() { }

ngOnInit(): void {
}

2. Sempre all’interno della classe figlia si crea un metodo che emetta un evento con un valore
specifico. Tale valore è quello che ci interessa propagare alla component madre. Tale metodo
può essere triggerato ad esempio da un pulsante:

export class figliaComponent implements OnInit {

@Output() myVar = new EventEmitter<string>();

constructor() { }

ngOnInit(): void {
}

public emetti(value: string) {


this.myVar.emit(value);
}

3. Nella componente madre, si crea un setter per la variabile che vogliamo ricevere dalla figlia:

85
varMadre: string;

public setVarMadre(value: string) {


this.varMadre = value;
}

4. Infine, si lega l’emitter della componente figlia al metodo creato in precedenza. Anche in
questo caso, questo viene fatto all’interno del tag CSS che specifica la componente figlia nel
template della madre.

<app-figlia
(myVar) = "setVarMadre($event)"
></app-figlia>

9.3 Template e Data Binding


Angular estende le funzionalità base dell’HTML, consentendo la realizzazione di un DOM (do-
cument object model ) dinamico. Ricordiamo che Angular consente la realizzazione di single page
applications, di conseguenza la struttura base html (¡html¿, ¡head¿ e ¡body¿) si definisce solamente
all’interno dell’index e non deve essere ripetuta nei template delle componenti. Inoltre i template
angular non supportano emlementi ¡script¿, per ridurre il rischio sùdi attcchi script-injection.

9.3.1 Interpolazione
L’interpolazione ci consente di visualizzare un dato definito nel TS della component all’interno
del template. Questo dato deve essere delimitato da una coppia di doppie parentesi graffe. Ad
esempio, se vogliamo mostrare a schermo il valore di una variabile scriveremmo

//nel TS
customer: CustomerDto; //al suo interno ho una property ’nome’
/* inizializzo il customer in qualche modo*/

//nell’HTML
<p> Nome del cliente: {{customer.nome}}</p>

L’interpolazione può essere usata anche all’interno dei tag, ad esempio per settare la fonte di
un’immagine:

//nel TS
myUrl: string = "sitosicuro.it/immaginesicuratrust";

//nell’HTML
<img src="{{myUrl}}" ... >

9.3.2 Data Binding


Angular fornisce tre tipi di binding, in base alla direzione del legame:

1. One-Way dalla source alla view (dal TS all’HTML): utilizza una coppia di parentesi quadre
e la sintassi [target]=”espressione” (anche l’interpolazione fa parte di questa categoria).
2. One-Way dalla view alla source: utilizza una coppia di parentesi tonde e la sintassi (target)
= ”statement”.

86
3. Two-Way che lega i dati dalla source alla view e viceversa. Utilizza una coppia di parentsi
”a banana” e la sintassi [(target)] = ”espressione”.
Il target di un data binding può essere una proprietà, un evento o anche il nome di un attributo.
Tramite data binding ci si può legare a un qualunque membro del TS che sia pubblico.
Esaminiamo ora i diversi tipi di binding, assieme ai target a cui fanno riferimento.
• Property Binding: può avere come target una proprietà di un elemento, di una component
o di una direttiva. Nel seguente esempio, facciamo property binding sulle proprietà alt, src e
ngClass:

<img [alt]="hero.name" [src]="heroImageUrl">

<div [ngClass]="{’special’: isSpecial}"></div>

• Event Binding: può avere come target un evento di un elemento, di una component o di
una direttiva. Nel seguente esempio, facciamo event binding su click e myClick:

<button type="button" (click)="onSave()">Save</button>

<div (myClick)="clicked=$event" clickable>click me</div>

• Two-way binding: può avere come target sia eventi che proprietà, ad esempio

<input [(ngModel)] = "name">

9.4 Direttive
Le direttive Angular sono classi che provvedono dei comportamenti aggiuntivi agli elementi di una
applicazione. Queste si dividono in due tipi:
• Direttive d’attributo: modificano l’aspetto e il comportamento di un elemento, di una
componente o di un’altra direttiva.
• Direttive strutturali: cambiano il layout del DOM (document object model ), aggiungendo
o rimuovendo elementi del layout.

Direttive d’attributo
La più comuni direttive d’attributo built in sono NgClass, NgStyle e NgModel.
NgClass aggiunge e rimuove un insieme di classi CSS allo stesso tempo. Essa può essere usata con
espressioni ternarie o metodi appositi. Ad esempio:

//se isSpecial = true viene assegnata la classe special alla div

<div [ngClass]="isSpecial ? ’special’ : ’’">...</div>

Per un esempio dell’utilizzo con un metodo guardate qui.


NgStyles viene utilizzato per impostare più proprietà css di un elemento in maniera inline (ovvero
all’interno del tag). Il suo utilizzo è utile per cambiare dinamicamente lo stile in base a una o
più proprietà. Per utilizzarlo, ci si serve di un metodo dichiarato nella componente, all’interno del
quale si setta un oggetto corrispondente alle proprietà CSS che si vogliono assegnare.

87
currentStyles: Record<string, string> = {};
/* dichiaro tre booleani canSave, isUnchanged e isSpecial e li inizializzo
a true*/
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
’font-style’: this.canSave ? ’italic’ : ’normal’,
’font-weight’: !this.isUnchanged ? ’bold’ : ’normal’,
’font-size’: this.isSpecial ? ’24px’ : ’12px’
};
}

Infine, si lega tale metodo alla proprietà ngStyle nel tag dell’elemento desiderato:

<div [ngStyle]="currentStyles">
Questa div ha di default font-style italic, font-weight bold e font-
size 24px.
</div>

NgModel viene utilizzato per mostrare una proprietà definita nella logica e per aggiornare tale
proprietà quando l’utente opera delle modifiche. È utilizzata in maniera particolare all’interno
dei form per legare un input a una variabile definita nel typescript corrispondente. Ad esempio,
supponiamo di aver definito una variabile nomeAttuale nel typescript e di volerla legare a un input
di un form:

<input type="text" [(ngModel)]="nomeAttuale">

In questo modo il contenuto di tale variabile sarà corrispondente al contenuto di tale elemento.

Direttive strutturali
Le direttive strutturali sono responsabili del layout HTML dell’applicazione. Queste possono ag-
giungere o rimuovere gli elementi a cui fanno riferimento. Le più comuni sono NgIf, NgFor e
NgSwitch.
NgIf aggiunge o rimuove un elemento in base al valore di un booleano: se tale valore è true l’ele-
mento (e tutto ciò che contiene) viene mostrato, se è false no. Per utilizzare questa direttiva basta
dichiararla nel tag dell’elemento tramite la sintassi *ngIf=”myBool”, dove myBool è il booleano
a cui si vuol fare riferimento. Questa direttiva supporta anche una forma if-else, con la seguente
sintassi:

<div *ngIf="myBool; else tagDellElse">


Contenuto mostrato se myBool=true
</div>
<ng-template #tagDellElse>
Contenuto mostrato se myBool=false
</ng-template>

ngFor viene utilizzato per mostrare una lista di elementi dinamicamente. Supponiamo ad esempio
di aver definito un array di stringhe items nel TS, per mostrarli tutti a schermo nel template si
utilizza *ngFor nel tag dell’elemento che si vuole ripetere. Ad esempio:

<div *ngFor="let item of items"> {{item.name}} </div>

88
NgFor supporta anche la sintassi estesa *ngFor=”let item of items; let i=index”, la quale ci consente
anceh di avere a disposizione l’indice di ogni elemento che viene ripetuto. Ciò è particolarmente
utile quando si vuole far riferimento a un particolare elemento dell’iterazione.
NgSwitch sceglie che ’forma’ di un elemento mostrare in base al valore di una determinata variabile
interruttore. Esso imita la sintassi di uno switch Java/Javascript. Se ad esempio vogliamo cambiare
cosa viene mostrato all’interno di una div in base a una variabile orarioAttuale faremmo cosı̀:

<div [ngSwitch]="orarioAttuale">
<p *ngSwitchCase="mattina"> Buongiornissimo! </p>
<p *ngSwitchCase="pomeriggio"> Buon pomeriggio! </p>
<p *ngSwitchCase="sera"> Buonaseeeeeeeee’ </p>
//se orarioAttuale non ha nessuno dei tre valori precedenti mostro
questo
<p *ngSwitchDefault> WEWE AMICI DEL WEB</p>
</div>

9.5 Dependency Injection


La dependency injection è uno dei meccanismi fondamentali di angular come framework. Questo
meccanismo permette alle classi decorate coi relativi decoratori di configurare autonomamente le
dipendenze a loro necessarie. In questo meccanismo, chi fruisce della dipendenza è detto consumer
mentre chi la fornisce provider. Angular facilita l’interazione tra queste due parti grazie a un’astra-
zione detta injector. Quando una dipendenza viene richiesta dal consumer, l’injector controlla se
un’istanza è già presente, altrimenti ne crea una nuova e la salva nel registro. Un injector globale
(detto root injector ) è creato da Angular all’avvio dell’applicazione. Vediamo ora nel dettaglio
come si creano i providers e come si iniettano nei consumers.

Provider
Affinché una classe possa essere iniettata all’interno di un’altra, essa deve essere decorata con
@Injectable().

@Injectable()
export class MyService { ... }

In seguito, dobbiamo rendere disponibile tale dipendenza all’interno dell’app. Una dipendenza può
essere resa disponibile in tre livelli differenti:
• Component-Level : dichiarando la classe nell’array dei providers. Ad esempio:

@Component({
selector: ’my-comp’,
template: ’...’,
providers: [MyService]
})

In questo caso MyService (e tutte le sue funzionalità) sono disponibili all’interno di MyCom-
ponent e di tutte le componenti e direttive che vengono utilizzate nel template. Quando si
registra un provider a questo livello, si ottiene una nuova istanza ogni volta per ogni istanza
della componente.
• NgModule-Level : dichiarando la classe nell’array dei providers analogamente a sopra. In
questo caso, una stessa istanza della classe è disponibile per tutte le componenti del modulo.
• Root-Level : questo è fattibile in due modi. O dichiarando la classe nei providers del’app-
module oppure all’interno del decoratore @Injectable(), utilizzando la proprietà providedIn:

89
@Injectable({
providedIn: ’root’
})
export class MyService { ... }

In questo caso, una singola istanza del service è disponibile in tutta l’applicazione. Questo
consente inoltre ad angular di rimuovere il servizio dalla app compilata qualora questo non
fosse utilizzato, ottimizzand cosı̀ le prestazioni.

Consumers
Il modo più comune iniettare una dipendenza all’interno di una classe è utilzzare il costruttore.
Quando Angular crea una nuova istanza di una classe, determina di quali dipendenze questa abbia
bisogno guardando i tipi dei parametri dichiarati nel costruttore. Una volta che trova le dipendenze
necessarie (e eventualmente istanzia quelle mancanti) angular passa tali istanze al costruttore per
creare effettivamente la classe. Ad esempio, supponiamo di voler iniettare MyService all’interno di
una component:

@Component({
selector: ’my-comp’,
template: ’...’,
providers: [MyService]
})
export class MyComponent implements OnInit {

constructor(private service: MyService) {}

ngOnInit(): void {
}

9.6 Collegamento col Back-End


Concentriamoci ora nello specifico sull’utilizzo di Angular per lo sviluppo del lato client all’interno
di un’architettura REST. Per fare ciò si utilizzano dei service appositi, i quali possono essere
iniettati nelle component ogniqualvolta si voglia fare una comunicazione con il back-end.
Prima di entrare nel dettaglio su come questa comunicazione venga effettuata, abbiamo bisogno di
approfondire un concetto fondamentale di Angular: gli observable.

9.6.1 Observable
Gli observable svolgono un ruolo di supporto nella comunicazione di più parti di un’applicazione.
Essi seguono il design pattern dell’observer in cui un oggetto detto subject gestisce una lista di suoi
dipendenti, chiamati observers e comunica ad essi ogni cambio di stato automaticamente. Gli Ob-
servable sono dichiarativi, ovvero essi non emettono i valori gestiti fino a che non vengono chiamati
da un utente, tramite una procedura detta subscribe. Vediamo quindi come vengono implementati
praticamente: per creare un observable in TS si crea un’istanza della classe Observable<T>, dove
T è il tipo degli oggetti che si vogliono gestire tramite quell’observable. Questa classe espone il
metodo .subscribe(), che viene utilizzato quando si vuole accedere agli oggetti gestiti e permette di
specificare come ottenere e generare valori o messaggi dall’observable. Il metodo subscribe pren-
de come argomento un observer : questo è un oggetto TS che definisce la gestione delle notifiche
inviate dall’observable. Il metodo subscribe restituisce un oggetto di tipo Subscription, il quale
contiene un metodo .unsubscribe(), che può essere utilizzato per terminare la ricezione di notifiche
dall’observable.
I tipi di notifica che un observable può inviare sono tre e e vengono gestiti da tre handler nell’ob-
server che si passa al metodo subscribe. Questi handler sono esplicitati come function e sono i
seguenti:

90
1. next: è l’unico handler obbligatorio, che definisce come gestire ciascun valore emesso.
2. error: è l’handler che specifica il comportamento da seguire in caso di errore. Un errore
termina l’esecuzione dell’observable.

3. complete: specifica che notifica inviare al termine dell’esecuzione dell’observable.


Gli ultimi due handler sono opzionali: se non vengono specificati un observable semplicemente
non invierà alcuna notifica in caso di errore o al termine dell’esecuzione. Vediamo ora un esempio
pratico, in cui creiamo artificialmente un observable tramite il metodo of(... items) della libreria
RxJS, il quale restituisce un Observable che gestisce i valori passati.

const myObservable = of(1,2,3);

const myObserver = {
next: (x: number) => console.log("Valore attuale: " + x),
error: (err: Error) => console.log("Errore inaspettato, termino l’
esecuzione"),
complete: () => console.log("Esecuzione terminata"),
};

//facciamo il subscribe passando myObserver: questo poteva essere anche


dichiarato esplicitamente direttamente nel subscribe
myObservable.subscribe(myObserver);

// Logs:
// Valore attuale: 1
// Valore attuale: 2
// Valore attuale: 3
// Esecuzione terminata

La funzione subscribe supporta inoltre la dichiarazione inline delle function:

//forma con observer


myObservable.subscribe({
next: (x: number) => console.log("Valore attuale: " + x),
error: (err: Error) => console.log("Errore inaspettato, termino l’
esecuzione"),
complete: () => console.log("Esecuzione terminata"),
});

//forma equivalente con handler inline


myObservable.subscribe(
x => console.log("Valore attuale: " + x),
err => console.error("Errore inaspettato, termino l’esecuzione"),
() => console.log("Esecuzione terminata")
);

9.6.2 Utilizzo delle chiamate HTTP


Ora che abbiamo un’idea di cosa siano gli observable e di come funzionino, vediamo come vengono
utilizzati per gestire le comunicazioni col back-end. Prima di entrare nel vivo della discussione,
parliamo un attimo degli environments.
Quando creiamo un progetto da cli, Angulare crea automaticamente la cartella environments, con
al suo interno due file TS: environment.ts e environment.prod.ts. Questi file hanno lo scopo di con-
tenere le variabili d’ambiente da utilizzare rispettivamente nelle fai di development e di produzione
della nostra applicazione. Angular sostituisce automaticamente il secondo al primo in fase di pro-
duzione, grazie alla dichiarazione nell’array fileReplacements, all’interno del file di configurazione
angular.json. Nel nostro caso, utilizzeremo questi due file per salvare gli url del back-end sia in fase

91
di development (tipicamente la porta locale 8080 se lavoriamo con Spring) che in fase di produzione.

//contenuto del file environment.ts

export const environment = {


production: false, //presente di default alla creazione
apiEndPoint:"http://localhost:8080/" //variabile aggiunta da noi
};

Questi URL ci serviranno quando andremo a fare le chiamate vere e proprie nei service. Vediamo
innanzitutto un esempio di un service base e analizziamolo passo passo. Per prima cosa, creiamo
una cartella service e apriamola nel terminale. Lanciamo quindi il comando ng generate service
nome-service, questo creerà automaticamente due file: nome-service.service.ts e un file con le
specifiche di testing, nome-service.service.spec.ts. Il service sarà già configurato come injectable
globale.

import { Injectable } from ’@angular/core’;


import { environment } from ’src/environments/environment’;

@Injectable({
providedIn: ’root’
})
export class DummyService {

constructor() { }

Per prima cosa importiamo la const evironment, che utilizzeremo per recuperare l’endpoint del-
l’API del back-end. In seguito, iniettiamo un HttpClient nel costruttore. Per fare ciò, dobbiamo
importare il modulo HttpClientModule nel modulo principale dell’applicazione e dichiararlo nei
provider sempre all’interno di questo. Questo oggetto espone al suo interno i vari metodi utilizzati
per effettuare le varie chiamate HTTP, semplificandoci notevolmente la vita.

import { Injectable } from ’@angular/core’;


import { environment } from ’src/environments/environment’;
import { HttpClient } from ’@angular/common/http’;
@Injectable({
providedIn: ’root’
})
export class DummyService {

constructor(private http: HttpClient) { }

Infine, definiamo i vari metodi che si occuperanno delle chiamate. Al loro internoutilizzeremo
i metodi forniti dal HttpClient, i quali restituiscono un Observable che gestisce dati del tipo
restituito dal metodo del backend che si invoca (qualora non si fosse sicuri si può anche passare any
come argomento generico dell’Observable). Vediamo come esempio cinque metodi che utilizzano
le cinque chiamate principali http, operando con un generico data transfer object.

import { Injectable } from ’@angular/core’;


import { environment } from ’src/environments/environment’;
import { HttpClient } from ’@angular/common/http’;

92
import { DTO } from ’src/dtos/dto’; //questo e’ il file che contiene la
classe DTO al suo interno

@Injectable({
providedIn: ’root’
})
export class DummyService {

constructor(private http: HttpClient) { }

public getAll(): Observable<DTO[]> {


return this.http.get<DTO[]>(environment.apiEndPoint + "getAll"); //
prende come argomento l’url del back che si vuole contattare
}

public delete(id: number): Observable<any> {


return this.http.delete(environment.apiEndPoint + "delete?id=" + id);
//in questo caso l’id viene passato come parametro della request
direttamente nell’url
}

public insert(dto: DTO): Observable<DTO> {


return this.http.post<DTO>(environment.apiEndPoint + "insert",
dto); //il secondo param corrisponde al body
della request
}

public update(dto: DTO): Observable<DTO> {


return this.http.put<DTO>(environment.apiEndPoint + "update",
dto);
}

public patch(patchDTO: JSON): Observable<DTO> {


return this.http.patch<DTO>(environment.APIEndpoint + "patch",
patchDTO);
}

Poichè restituiamo un Obserable, quando chaimeremo questi metodi all’interno di una component
dovremmo utilizzare il metodo subscribe per poter accedere ai risultati. Questo è concatenabile in
maniera funzionale alla chiamata del metodo nel service. Vediamo un esempio, in cui utilizziamo
il metodo getAll per salvare tutte le entità di un database all’interno di un array.

import { Component, OnInit } from ’@angular/core’;


import { DTO } from ’src/dtos/dto’;
import { DummyService } from ’src/service/dummy.service’;

@Component({
selector: ’app-superdummy’,
templateUrl: ’./superdummy.component.html’,
styleUrls: [’./superdummy.component.css’]
})
export class superdummyComponent implements OnInit {

entities: DTO[];

constructor(private service: DummyService) { }

ngOnInit(): void {
this.service.getAll().subscribe({
next: (res: DTO[]) => this.entities = res,
complete: () => console.log(this.entities),

93
});
}

9.7 Argomenti aggiuntivi


Vediamo ora alcuni argomenti aggiuntivi, che ci permetteranno di capire più a fondo il funziona-
mento di Angular. Nello specifico, ci concentreremo su:
1. Sincronia
2. Caricamento dei dati
3. File di Configurazione

9.7.1 Sincronia
Come detto nella sezione precedente, l’utilizzo degli Observable offre notevoli vantaggi nella gestio-
ne degli eventi e nelle prestazioni, grazie all’utilizzo della programmazione asincrona, consentendo
al programma di iniziare task potenzialmente molto lunghe rimanendo comunque responsiva ad
altri eventi durante l’esecuzione, senza rallentare l’applicazione in attesa che questa finisca. Que-
sta tecnica di programmazione è in diretto contrasto con la programmazione sincrona, in cui il
programma aspetta che una task sia terminata prima di passare a quella successiva.
Nonostante i vantaggi che questo comporti in termini di prestazioni, alle volte vorremmo aspettare
che una task termini prima di passare a quella successiva; ad esempio, potremmo voler aspettare il
risultato di una chiamata http per evitare di operare con oggetti potenzialmente undefined. Per far
ciò, possiamo utilizzare il blocco next del metodo subscribe, nel quale tutte le operazioni vengono
eseguite in modo sincono. Vediamo però un metodo più generale per ovviare a questo problema,
attuabile ogniqualvolta si abbia a che fare con una funzione asincrona.
Una funzione asincrona è una qualunque funzione che opera in maniera asincrona, utilizzando una
Promise implicita per restituire il risultato. Essa viene indicato dalla keyword async. All’interno
di una tale funzione, possiamo utilizzare l’operatore await per comunicare al programma di atten-
dere l’esito di una Promise prima di passare alla riga di codice successiva. Vediamo un esempio:
creiamo artificialmente una funzione che restituisca un apromise al nostro scopo

function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve(’Finito’);
}, 2000);
});
}

async myMethod() {
console.log("Effettuo la chiamata");
var x = await resolveAfter2Seconds();
console.log(x);
}

myMethod();

//log:
// Effettuo la chiamata
//Finito

Se non avessimo utilizzato la keyword async avremmo invece ottenuto come log:
Effettuo la chiamata
[object Promise]
in quanto il programma passa subito alla riga successiva, senza aspettare che la promise venga
effettivamente ’mantenuta’.

94
9.7.2 Caricamento dei dati
In questa sezione tratteremo di come angular carichi i moduli necessari al corretto funzionamento
dell’applicazione. Un modulo può essere caricato in due modi:
1. eager: in breve, tali moduli vengono caricati prima che l’application venga lanciata.
2. lazy: al contrario, tali moduli vengono caricati solamente quando c’è il bisogno.
Il modulo principale (AppModule) viene sempre caricato in maniera eager, in quanto responsabile
del funzionamento dell’intera app. I restanti moduli vengono invece caricati a discrezione del
programmatore. Per specificare il tipo di caricamento di un modulo, abbiamo i seguenti modi:
• Un modulo è caricato in maniera eager di default, di conseguenza si deve semplicemente
dichiarare negli import dell’AppModule, come discusso in precedenza.
• Per caricare un modulo in maniera lazy, questo non deve essere dichiarato negli import di
AppModule, bensı̀ dobbiamo operare nell’AppRoutingModule (il modulo di routing principa-
le) come segue: quando dichiariamo le routes, aggiungiamo la seguente route, dove al posto
della proprietà component specifichiamo la proprietà loadChildren:

const routes: Routes = [


{
path: ’items’,
loadChildren: () => import(’./items/items.module’).then(m
=> m.ItemsModule)
}
];

In questo esempio, ’./items/items.module’ è il path relativo al modulo che vogliamo caricare


lazily e ItemsModule è la classe che viene esportata all’interno del file. In seguito, all’interno
del RoutingModule del modulo che vogliamo caricare in maniera lazy, aggiungiamo sempli-
cemente le routes per le sue componenti come faremmo normalmente.
Caricare i dati in questo modo è particolarmente utile quando le dimensioni dell’appli-
cazione si fanno relativamente grandi, in modo da non ritardare eccessivamente il lancio
dell’applicazione col caricamento preventivo di tutti i moduli.

9.7.3 File di configurazione


Quando creiamo un nuovo progetto da linea di commando, vengono installati automaticamente i
package npm di Angular e le dipendenze aggiuntive all’interno di un nuovo workspace. La cartella
principale di questo workspace contiene di default vari file di configurazione, all’interno dei quali
vengono automaticamente configurate gran parte delle impostazioni utili nello sviluppo. Vediamo
ora quali sono questi file e di cosa si occupano:
• .editorconfig: contiene le configurazioni relative all’editor di testo che utilizziamo per
scrivere il codice.
• .gitignore: specifica i file che devono essere ignorati da git.
• README.md: contiene una documentazione introduttiva relativa allo scheletro dell’app.
Deve essere utilizzato per specificare la documentazione utile all’utilizzo delle varie compo-
nenti durante lo sviluppo.
• angular.json: contiene le configurazioni di default della cli per tutto il workspace. Al suo
interno troviamo ad esempio le configurazioni per le fasi di build e serve, assieme a quelle dei
tool di testing utilizzabili dalla cli.
• package.json: configura le dipendenze dei pacchetti npm che sono disponibili a tutti i
componenti del workspace.
• package-lock.json: specifica le versioni di tutti i package installati tramite npm.
• tsconfig.json: contiene le configurazioni per il comportamento di TypeScript all’interno
del progetto. Ad esempio al suo interno possiamo specificare quanto stretto debba essere il
controllo sui tipi oppure come gestire la possibilità che un oggetto sia undefined.

95

Potrebbero piacerti anche