Apache Maven è un tool per la gestione di progetti e build automation, utilizzato principalmente per progetti Java, il cui obiettivo è: semplificare, uniformare e automatizzare il processo di build di sistemi complessi.
In questa presentazione / guida verranno illustrati i problemi e le criticità dei tool di build automation tradizionali: make e Apache Ant, vedremo poi come installare e configurare Maven, le caratteristiche, gli obiettivi e i punti di forza del tool, le fasi del ciclo di vita, i plugin e i goal, le dipendenze, gli scope e la risoluzione di eventuali conflitti, i repository, i plugin "esterni" e i progetti multi-modulo.
La presentazione è ricca di esempi pratici.
Apache Maven - Gestione di progetti Java e build automation
1. Autore: dott. Tiziano Serritella (Docente/Formatore Java e Sviluppatore Full Stack)
Email: tiziano.serritella@gmail.com
Questo lavoro è concesso in uso secondo i termini di una licenza Creative Commons
(vedi ultima pagina)
2. Le fasi di sviluppo di un software sono contornate da una serie di attività
accessorie:
Compilazione
Packaging (formato da esportare: jar, war, ear)
Testing
Deployment (installazione e configurazione sui sistemi per i quali è stato
implementato)
Documentazione
I tool di build automation come Maven sono pensati per automatizzare
tutte queste fasi
2
3. Consideriamo un’operazione fondamentale: la
compilazione
In Java richiede di effettuare tipicamente varie
operazioni:
Impostare il classpath
Compilare i vari package separatamente
Il tutto deve essere ripetuto frequentemente
Sarebbe opportuno automatizzare il processo…
3
4. Infatti, ben presto si rese necessario l’utilizzo di tool per
l’automazione del project-building, dei quali il più
conosciuto è sicuramente il “make”:
Utility sviluppata su S.O. della famiglia UNIX che
automatizza il processo di creazione di file che dipendono
da altri file, risolvendo dipendenze e invocando programmi
esterni per il lavoro necessario
4
5. E’ complesso da utilizzare (sintassi abbastanza
complicata)
E’ dipendente dalla piattaforma (i makefile
sono difficilmente portabili)
Non è estendibile (non è possibile aggiungere
nuove operazioni)
5
6. Tool di automazione, estendibile, scalabile e basato
su una sintassi più semplice
Simile a make ma scritto in Java e principalmente
orientato allo sviluppo in Java
E’ portabile, comandi strettamente dipendenti
dalla piattaforma sono eseguiti richiamando comandi
standard indipendenti dalla stessa, definiti in un file
tipicamente denominato build.xml
Prima versione rilasciata nel 2000
6
7. Ogni build file definisce un project composto da target in cui sono
elencati i task, ovvero le istruzioni da eseguire
I target possono avere delle dipendenze da altri target (nell’esempio, il
target jar dipende dal target compile)
7
8. Per fare un esempio, il comando:
rm –rf classes
viene semplicemente eseguito da:
<delete dir="classes"/> del target clean
Ant viene eseguito da riga di comando e tipicamente utilizzato nel
seguente modo:
ant –buildfile <build_file>.xml –D<proprieta1>=<valore1> –
D<proprieta2>=<valore2> <target1> <target2>
8
9. In breve tempo, il tool è diventato lo
strumento di creazione più popolare per i
progetti Java
Ha una curva di apprendimento molto
bassa
Si basa sull'idea di programmazione
procedurale
9
10. Non dispone di convenzioni formali, sarà lo
sviluppatore stesso a fornire informazioni circa
la struttura del progetto, nel file build.xml
E’ principalmente uno strumento di costruzione
Essendo procedurale è necessario fornire un
ordine su cosa fare e quando
Non esiste un ciclo di vita
I suoi script non sono riutilizzabili
10
11. Strumento completo per la gestione di progetti il cui
obiettivo è:
“Semplificare, uniformare e automatizzare il
processo di build di sistemi complessi tramite la
creazione di un modello di progetto e una struttura
di file system standard”
Prima versione rilasciata nel 2004
11
12. Ha una convenzione per inserire i sorgenti, i
compilati e così via (struttura di progetto
standard)
E’ dichiarativo, tutto va definito nel file
pom.xml (POM)
È principalmente uno strumento di gestione del
progetto
C'è un ciclo di vita
I suoi plugin sono riutilizzabili
12
13. Gestione e download automatico delle
librerie necessarie al progetto con
risoluzione delle eventuali dipendenze
transitive
Facilità di ampliarne le funzionalità iniziali
tramite l’utilizzo di plugin
Esportazione automatizzata del pacchetto
Creazione automatica di un semplice sito di
documentazione del progetto
13
14. Innanzitutto, assicuriamoci che il JDK sia installato e che
la variabile "JAVA_HOME" sia stata aggiunta come variabile di
sistema di Windows
In caso contrario, creiamo una nuova variabile denominata
“JAVA_HOME” e facciamola puntare alla directory di installazione
del JDK:
14
15. Aggiungiamo il percorso puntato dalla variabile
JAVA_HOME al “Path”, in modo che i comandi di
Java siano accessibili in ogni punto del sistema
operativo:
15
16. Scarichiamo Maven in formato Binary zip archive
(bin.zip) dalla sezione download del sito ufficiale:
https://maven.apache.org/download.cgi e
decomprimiamolo in una cartella sul disco locale:
16
17. Aggiungiamo M2_HOME come variabile di sistema e facciamola puntare alla
cartella in cui è stato estratto Maven:
N.B. MAVEN_HOME è per la versione 1 del tool, M2_HOME è per la versione
2 e successive, a noi basterà settare solo quest’ultima
17
18. Aggiorniamo la variabile di sistema Path, aggiungendo il
percorso relativo alla cartella bin (%M2_HOME%bin), in
modo da poter eseguire un comando Maven ovunque:
18
19. Se abbiamo impostato tutto in maniera
corretta, apriamo il prompt dei comandi e
lanciamo il seguente comando:
mvn –version
dovrebbe apparirci un messaggio indicante la
versione di Maven utilizzata, più ulteriori
dettagli
19
20. pom.xml: file di configurazione contenente
tutte le informazioni sul progetto
(dipendenze, test, documentazione, ecc…)
Goal: singola funzione che può essere
eseguita sul progetto, l’equivalente Maven
dei task Ant
Plugin: goal riutilizzabili in tutti i progetti
Repository: directory strutturata destinata
alla gestione delle librerie (locale o remoto)
20
21. E’ composto da un tag root <project> contentente i seguenti tag:
<modelVersion> che dichiara a quale versione di POM questo progetto è
conforme
<groupId> identifica il progetto in modo univoco, un buon modo è usare
la struttura del progetto, ad esempio:
nomedominioprimolivello.nomeorganizzazione.nomeprogetto
<artifactId> è il nome del pacchetto senza versione
<version> indica la versione del progetto
<packaging> dichiara di che tipo è il pacchetto (jar, war o ear) che
andremo poi ad esportare in una cartella denominata “target”
21
22. groupId, artifactId, version e packaging
identificano univocamente un progetto
N.B. In questo esempio il packaging non è
specificato, per cui assumerà “jar” a valore di
default
22
23. Il tool permette di creare agevolmente un
progetto a partire da un template, denominato
archetype, che stabilisce la struttura base delle
cartelle e dei file che devono essere creati
Questi template sono molto comodi, è raro che
uno sviluppatore inizi a scriversi il POM da zero
Volendo creare un semplice progetto, sarà
possibile utilizzare l’archetype: maven-
archetype-quickstart
23
24. Creiamo una cartella, dopodiché copiamo il
seguente comando:
mvn archetype:generate -
DgroupId={nomedominioprimolivello.nomeorga
nizzazione.nomeprogetto} -
DartifactId={NomeProgetto} -
DarchetypeArtifactId=maven-archetype-
quickstart -Dversion=1.0
sostituendo le voci contenute tra { } (eliminando
queste ultime) con i relativi valori, incolliamolo
nel prompt dei comandi e confermiamo
24
25. A questo punto, apriamo il nostro IDE, in questo
caso Eclipse e clicchiamo su File -> Import ->
Existing Maven Projects:
25
26. Alla voce Root Directory inseriamo la directory del progetto
appena creato e confermiamo:
26
27. In alternativa è possibile creare direttamente un
progetto Maven da Eclipse senza passare per la
riga di comando:
27
29. Ora, dalla vista “Package Explorer” di Eclipse potremo
visualizzare la struttura della nostra cartella di progetto:
Di default, utilizzando questo template verrà creata anche
una classe per il test di JUnit (AppTest.java)
29
30. Il contenuto del POM è stato generato in
automatico dall’archetype:
Inoltre, sempre automaticamente è stata anche
aggiunta la dipendenza a JUnit e alla relativa
versione
30
31. Allo stesso modo, sarà possibile importare tutte le librerie che ci serviranno
(quando un progetto necessita di una libreria si dice che ha una dipendenza)
Quindi, colleghiamoci al sito: https://mvnrepository.com/
Cerchiamo la libreria di nostro interesse, ad esempio Log4j, quindi
clicchiamo su Apache Log4j e selezioniamo la versione della stessa:
31
32. Selezioniamo la vista relativa a Maven e copiamo il blocco di
codice contenuto al suo interno:
incollandolo all’interno del tag dependencies del nostro POM
32
34. Salviamo e attendiamo che la libreria venga
scaricata nel repository locale (approfondiremo
l’argomento in seguito) e aggiunta al progetto
Qualora la stessa fosse già stata utilizzata in
precedenza e quindi già disponibile nel repository
locale, non verrà scaricata nuovamente
Adesso sarà possibile utilizzare la libreria
richiamando una qualsiasi classe della stessa, dopo
aver risolto le eventuali import
34
35. Il ciclo di vita di Maven è costituito da varie fasi:
Validate (Verifica che il progetto sia corretto e che tutte le informazioni necessarie siano
disponibili)
Compile (Compila il codice sorgente del progetto)
Test (Il codice viene testato)
Package (Il codice compilato viene esportato nel suo formato distribuibile)
Verify (Esegue qualsiasi controllo sui risultati dei test di integrazione per garantire che i criteri di
qualità siano soddisfatti)
Install (Installa il pacchetto nel repository locale, questo vuol dire che verrà creata una directory:
.m2repositoryittizianoserritellamavenesempioMavenEsempio1.0 sotto la directory utente
contenente il file pom.xml del progetto appena creato e il suo formato distribuibile:
MavenEsempio-1.0.jar)
Deploy (Copia il pacchetto nel repository remoto per la condivisione con altri sviluppatori e
progetti)
35
36. Ciascuna fase prevede l’esecuzione di un set di determinate coppe: plugin-
goal (Un goal è una singola funzione che può essere eseguita sul progetto,
collegata ad una specifica fase, mentre i plugin sono goal riutilizzabili in
tutti i progetti), queste vengono eseguite in sequenza per completare
il ciclo di vita predefinito
Infatti, lanciando il seguente comando, corrispondente alla fase di
impacchettamento:
mvn package
verranno richiamate in sequenza le seguenti fasi e i relativi goal:
Validate
Compile
Test
Package
36
37. Dalla vista “Effective POM” sarà possibile visualizzare il POM completo
Notiamo i plugin già disponibili e i vari goal specifici collegati ad ogni
fase del ciclo di vita di Maven:
37
38. Tra i plugin built-in disponibili, i principali sono i seguenti:
clean: permette di cancellare i compilati dal progetto
compiler: permette di compilare i sorgenti
deploy: permette di depositare il pacchetto generato nel
repository remoto
install: permette di depositare il pacchetto generato nel
repository locale
site: permette di generare la documentazione del progetto
archetype: permette di generare la struttura di un progetto a
partire da un template
38
39. Come abbiamo potuto notare il plugin di
compilazione, compiler, prevede una fase (e un
goal) per la compilazione del codice sorgente:
compile
e un altra per la compilazione dei casi di test:
test-compile
39
40. Ciascun goal, a sua volta, riceve in ingresso dei
parametri che possono essere facoltativi o
obbligatori
Altri plugin possono essere utilizzati qualora sia
necessario estendere ulteriormente le capacità di
Maven a causa di particolari esigenze (in seguito
vedremo un esempio utilizzando Tomcat Maven
Plugin per il deploy di un WAR su Apache Tomcat)
40
41. Dal prompt dei comandi, all’altezza della cartella di progetto
lanciamo il comando mvn compile, in alternativa, da Eclipse
clicchiamo con il tasto destro del mouse sul nostro progetto,
selezioniamo Run As -> Maven build… e alla voce Goals scriviamo
compile e clicchiamo su Run:
41
42. Allo stesso modo, sarà possibile compilare le
classi di test specificando questa volta il
comando test-compile
Per cancellare invece i compilati basterà
invocare il comando clean
N.B. i compilati sono contenuti all’interno della
cartella classes a sua volta contenuta nella
cartella target del nostro progetto (non visibile
dalla vista “Package Explorer” di Eclipse)
42
43. Volendo, sarà anche possibile esportare il nostro progetto nel
formato scelto. Per cui, sempre dalla nostra cartella di
progetto, digitiamo il comando: mvn package, o più
semplicemente da Eclipse:
43
44. Maven, quindi, creerà sotto la cartella target il nostro
JAR:
N.B. il numero 1.0 associato al JAR indica il numero di
versione precedentemente indicato nel POM
44
45. A questo punto, prima di accennare i repository è
doveroso fare una precisazione
Per ogni dipendenza inserita nel progetto è anche
possibile definirne uno scope:
Si tratta in sostanza dell’ambito di visibilità della
stessa
45
46. Esistono 5 ambiti di visibilità principali:
compile (quella di default): le dipendenze sono disponibili in tutti i
classpath del progetto, inoltre queste dipendenze vengono propagate a
tutti i progetti dipendenti
provided: è simile a compile, ma prevede che a runtime le dipendenze
siano rese disponibili dall’ambiente di esecuzione (per esempio le JavaEE
APIs per un’applicazione enterprise potrebbero benissimamente essere
fornite direttamente dal web container, es: Apache Tomcat)
runtime: le dipendenze sono richieste solo in fase di esecuzione e non
sono necessarie in fase di compilazione
test: le dipendenze sono richieste solo per la compilazione e l’esecuzione
dei test
system: le dipendenze non vengono recuperate tramite repository, ma
ne vengono esplicitamente dichiarate le posizioni locali (Ne è
sconsigliato l’utilizzo, è da preferire un repository Maven)
46
47. Una dipendenza, a sua volta, può anche avere altre
dipendenze, in questo caso si parla di dipendenza
transitiva: questo vuol dire che nel momento in cui
andremo ad inserire nel nostro progetto una certa
dipendenza (es: commons-logging-1.1), questa si
porterà dietro altre dipendenze che verranno
automaticamente risolte (scaricate) da Maven, nel
nostro caso: Log4j-1.2.12, servlet-api-2.3 e altre
ancora
47
48. Maven riesce a risolvere automaticamente tutte le dipendenze
necessarie in maniera trasparente
Dal repository remoto possiamo notare tutte le dipendenze richieste
in fase di compilazione da commons-logging-1.1, ovvero quelle che
si porta dietro:
48
49. Volendo usare la versione 1.2.13 di Log4j al
posto della 1.2.12, basterà semplicemente
aggiungere la dipendenza al POM indicando la
specifica versione della stessa:
Tra le Maven Dependency ora comparirà
soltanto quest’ultima e non la precedente
49
50. Questo perché entra in gioco un meccanismo
chiamato “mediazione di dipendenza”, Il suo
compito è decidere quale delle due versioni di
Log4j debba essere utilizzata:
50
51. Il funzionamento è banale e facilmente verificabile, in
sostanza, Maven utilizzerà la versione più vicina: se
due versioni della dipendenza richiesta sono alla stessa
profondità nell'albero delle dipendenze, verrà utilizzata
la prima dipendenza dichiarata, pertanto la versione
1.2.13 di Log4j
N.B. volendo fare una prova per togliersi ogni dubbio,
proviamo ad inserire la dipendenza di Log4j 1.2.9 (più
vecchia rispetto a quella usata da commons-logging-
1.1, ovvero la 1.2.12), come possiamo facilmente
notare, la versione usata sarà quindi la 1.2.9, pertanto
il fatto che una versione sia più recente di un altra non
influisce minimamente nel processo decisionale
51
52. Tutte le dipendenze utilizzate fino ad ora, sono
contenute in alcune directory strutturate destinate
alla gestione delle librerie, denominate repository
Abbiamo due tipi di repository: locale e remoto
Di default, utilizzando Windows, il repository
locale si troverà sotto la cartella utenti:
C:Users${user}.m2repository
52
53. Nel momento in cui a Maven verrà richiesto di
importare delle librerie necessarie al progetto,
esso le andrà a cercare in primo luogo nel
repository locale .m2 della directory utente:
(es: C:Users${user}.m2repository)
Se non le troverà in locale, Maven le andrà a
cercare in un repository remoto e le
immagazzinerà nel repository locale, pertanto
il tool ha bisogno di una connessione ad
Internet
53
54. Il repository remoto di default in cui Maven andrà
a cercare le eventuali dipendenze si trova a questo
indirizzo:
http://repo1.maven.org/maven2/
54
55. Qualora fosse necessario, sarà possibile specificare
altri repository dove cercare eventuali
dipendenze. Per integrare un repository remoto
all’interno del nostro progetto sarà sufficiente
aggiungere nel POM la sezione <repositories>:
55
56. Inoltre, sarà possibile aggiungere funzionalità a Maven stesso, per questo
scopo vengono utilizzati dei Plugin “esterni” (in questo caso vedremo il
Tomcat Maven Plugin per il deploy di un WAR su Apache Tomcat)
Pertanto, creiamo un’applicazione web usando l’archetype webapp,
ripetendo il solito procedimento da Eclipse:
56
57. A questo punto, potrebbe presentarsi qualche errore:
In effetti manca la dipendenza relativa a servlet-api, che andremo
prontamente ad aggiungere:
57
58. Nell’esempio viene utilizzata la versione 7 di Apache Tomcat, per cui aggiungeremo il
relativo plugin:
N.B. I tag username e password vanno valorizzati con i valori definiti sotto la voce
user del file tomcat-users.xml di Apache Tomcat
La nostra wep application sarà quindi disponibile all’url:
http://localhost:8080/MavenEsempioJ2EE/
58
59. Sul sito della Apache, precisamente a questo indirizzo:
http://tomcat.apache.org/maven-plugin-2.2/tomcat7-maven-
plugin/ plugin-info.html sono specificati tutti i goal messi a
disposizione dal plugin, pertanto avremo:
tomcat7:deploy per effettuare il deploy del WAR su Tomcat
tomcat7:redeploy per effettuare il redeploy del WAR su Tomcat
tomcat7:run per eseguire il progetto corrente utilizzando un
server Tomcat “embedded”
tomcat7:undeploy per effettuare l’undeploy del WAR da Tomcat
E tanti altri ancora…
59
60. Un progetto Maven ha inoltre la possibilità di
aggregare più moduli (progetto multi-modulo)
Questi moduli sono progetti dichiarati all‘interno
del POM ed eseguiti come un gruppo
Pertanto sarà possibile compilare con un solo
comando tutti i moduli collegati al progetto
genitore (eseguendo mvn compile direttamente da
quest’ultimo)
60
61. Ma andiamo a vedere subito un esempio
Riutilizziamo uno dei due progetti precedenti oppure entrambi (il
primo creato con l’archetype maven-archetype-quickstart mentre
il secondo utilizzando l’archetype maven-archetype-webapp)
A questo punto, creiamo un terzo progetto che lo/li conterrà,
“skippando” la selezione dell’archetype e selezionando pom come
formato esportabile:
61
63. 63
Nel POM di uno dei due progetti precedenti (o di entrambi)
aggiungiamo il contenuto del tag parent, in cui inseriremo il
percorso e i dettagli relativi al progetto “genitore”:
64. 64
Nel progetto “genitore”, invece, aggiungiamo il tag modules e il relativo (o
i relativi) contenuto:
A questo punto, sempre dal progetto “genitore”, selezioniamo Run As ->
Maven build… e compiliamo:
65. 65
In ogni caso, da Eclipse avremmo potuto effettuare
velocemente queste operazioni, creando direttamente uno o
più progetti “modulo”:
specificando successivamente il progetto “genitore”
66. 66
Adesso, per poter usare una classe definita nel progetto creato con
l’archetype maven-archetype-quickstart nel progetto creato con
l’archetype maven-archetype-webapp, basterà semplicemente aggiungere
la relativa dipendenza al POM di quest’ultimo:
67. 67
Inoltre, sempre dal progetto web, aggiungiamo la cartella “java” sotto
src/main e da lì creiamo i nostri package, le classi e richiamiamo le classi
definite nel primo progetto
La struttura dovrebbe presentarsi in questo modo:
68. 68
Ma a questo punto perché potrebbe essere utile suddividere
un progetto in più moduli?
Perché ogni modulo potrebbe essere accessibile e
utilizzabile da altri separatamente, infatti ciascun modulo
potrebbe avere un numero di versione differente, ma
questo dipenderà dal progetto in questione
In effetti, sarà sempre possibile compilare o effettuare
qualsiasi operazione sia sul singolo progetto che su quello
genitore
N.B. Aggiungendo una dipendenza al POM del progetto
“genitore”, questa sarà accessibile e utilizzabile in tutti i
progetti “modulo”: in questo modo, ad esempio, potremo
specificare JUnit una volta soltanto
69. Utilizzando Maven potremo:
Automatizzare tutte le fasi del processo di
costruzione
Gestire le dipendenze con efficacia
Creare lo scheletro del progetto tramite dei template
Grazie all’elevato grado di standardizzazione globale
si avrà un progetto con una struttura più riusabile e
sicuramente più comprensibile da terzi
69
70. Quest'opera è stata rilasciata con licenza Creative
Commons
Attribuzione - Non commerciale - Non opere derivate
4.0 Internazionale (CC BY-NC-ND 4.0)
Per leggere una copia della licenza visita il sito web
http://creativecommons.org/licenses/by-nc-nd/4.0/
L’attribuzione all’autore deve essere fatta citando il
nome e il cognome dello stesso: dott. Tiziano
Serritella
70