Maven : Description - Configuration
Maven est un outil de gestion de projet Java en général et Java EE en particulier qui comprends :
- un modèle objet pour définit un projet
- un ensemble de standards
- un cycle de vie
- un système de gestion de dépendances
Géré par la l'organisation Apache Software Foundation.
Maven s'apparente à l'outils Ant mais fournit des moyens de configurations plus simples, eux aussi basés sur le format XML.
Maven utilise un paradigme connu sous le nom de Project Object Model (POM) afin de décrire un projet logiciel, ses dépendances avec des modules externes et l'ordre à suivre pour sa production. Maven a un approche basé sur des conventions plus que sur de la configurations comme nous allons le voir par la suite.
Maven établit des conventions raisonnables sur la structure du projet :
- Sources dans src
- Code livrables dans src/main
- Les ressources dans src/main/resources
- Code tests dans src/text
- Tout ce qui est construit dans target
- Code généré dans target/generates-sources
- ...
Cette approche basée sur de la conventions permet d'avoir moins de configuration pour chaque plugin, plus d'homogénéité entre projets.
Un projet basique peut-être compilé, testé, packagé par Maven sans configuration dédié.
Exemple d'un projet basique déclarant une dépendance sur log4j, ceci est un fichier POM :
<?xml version="1.0" encoding="UTF-8"?> <project> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>foo</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> </dependencies> <build></build> </project>
Cycle de vie
- Validate : Valide que le projet est correctement défini.
- Compile : Compile les sources.
- Test : Lance les test unitaires.
- Package : Prépare la distribution du projet (archives Jar, War, Ear, ...).
- Integration-test : Lance les tests d'intégration.
- Verify : Lance des tests de validation du package crée.
- Install : Installe le package en local sur la machine pour pouvoir être réutilisé comme dépendance.
- Deploy : Déploie le package sur un serveur pour qu'il puisse être réutilisé par tout le monde.
L'ensemble des commandes (goals) sont passées de façon sequentielle dans cet ordre, certaines peuvent être évitées (par exemple les tests, via le paramètre -DskipTests, ...)
Plugins : Toujours plus
Il est très facile d'ajouter un plugin de différentes natures sans impact sur l'existant :
- D'outillage de test
- Contrôle qualité
- Génération de code
- Packaging spécifique
- ...
De très nombreux plugins sont proposés, certains sont officiels d'autres communautaires.
Plugins officiellement supportés : http://maven.apache.org/plugins/
Plugins communautaires : http://mojo.codehaus.org/
Si vous avez des besoins spécifiques l'écriture d'un plugin est facile (plus que celle d'une tâche ANT), il est peut-être écrit en Java, Groovy, BeanShell, ...
Les dépendances
Maven gère les dépendances nécessaire au projet, la transitivité des dépendances, ...
- Si mon projet dépend d'Hibernate
- Hibernate dépend d'EHcache
- Mon projet dépend d'EHcache
Maven encourage les librairies ciblées plutôt que le gros JAR qui fait tout.
Repository
Un repository est un dépôts de librairies, ces dépôts peuvent être de différentes natures :
- Dépôt local ($HOME/.m2/repository) : évite la multiplication des .jar sur le poste de dev.
- Dépôt(s) public(s) (http://repo1.maven.org/maven) : Mise à disposition rapide des librairies libres
- Dépôt privé : Gestion fine des librairies, libres ou non.
Extrapolation
Les valeurs des attributs XML peuvent être déduites d'une propriétés :
<properties> <spring.version>2.5.5</spring.version> </properties>
Profils
Les profils permettent de personnaliser, de spécialiser le build. Sur un projet d'entreprise, le build pourra être profilé pour différents environnements :
- Profil "dev"
- Profil "integrationcontinu"
- profil "release"
- profil "prod"
- ...
L'activitation d'un profil se fait via le paramètre -PXXX où XXX est le nom du profil.
Exemple : mvn clean package -Pdev
Archetype
Dès lors que vous avez créé un projet Maven répondant à vos attentes en terme de dépendances, de plugins, de profils, ... vous pouvez utiliser le pom de ce projet "coquille vide" comme base pour vos futurs développement : ce que l'on appelle un archetype.
Une fois dans le dossier de votre projet, saisir la commande suivante :
- mvn archetype:create-from-project
Le patron de notre template de projet qui sera utilisé par maven lors de l’appel au goal archetype:generate se trouve dans le répertoire src/main/resources/archetype-resources/src
Dans le répertoire src/main/resources/META-INF/maven, se trouve le fichier archetype-metadata.xml qui contient le descriptif de ce qui doit être généré
Il n’y a plus qu’à exécuter la commande mvn install pour déployer l’archetype dans le repository local
Une fois l’archetype déployé dans le repository, il n’y a plus qu’à l’invoquer via la commande :
-
mvn archetype:generate \
-DarchetypeGroupId=fr.appli \
-DarchetypeArtifactId=project-template-archetype \
-DarchetypeVersion=0.0.1-SNAPSHOT \
-DgroupId=fr.test\
-DartifactId=test-archetype
Les bonnes pratiques
- Adapter le projet à Maven, pas l'inverse
- Utiliser des plugin ciblés et simple
- Utiliser les dépendances directes
- Surveiller toutes les dépendances et les doublons
- Rester indépendant de l'environnement ... éviter les settings.xml exotiques
- Renseigner la version de Java ciblée
- Renseigner l'encodage de votre projet au sein du pom.xml pas dans votre EDI préféré.
- Renseigner les différentes versions des différences libraires que vous utilisez, évitez de renseigner la version de JAVA, Spring, ... en dur.
Exemple d'un pom utilisant Spring, Hibernate (JPA), une base MySQL pour un profil de production, une base HSQL pour l'environnement de développement.
Le pom suivant utilise certains plugins pour automatiser certains traitements comme :
- Déployer votre projet à l'aide de Maven plutôt qu'à l'aide de votre EDI
- Générer un numéro de build à chaque déploiement de votre projet
- Copier votre projet (war, jar, etc) dans un dossier spécifique.
- Lancer un script (.bat, .sh, ...) lors d'une étape spécifique du cycle de génération de votre projet (par exemple sur l'étape install). Le script pouvant prendre en argument des paramètres.
Le fichier pom.xml d'explemple étant assez complet, il peut servir de base comme archetype pour le développement de projet similaire.
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<groupId>fr.appli</groupId>
<artifactId>projet-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>projet-web</name>
<description>projet spring-ioc, spring-mvc, jpa - projet de base pour un archetype</description>
<properties>
<encoding>UTF-8</encoding>
<!-- declaration des
versions des dependances -->
<version.java>1.7</version.java>
<version.spring>4.0.1.RELEASE</version.spring>
<version.jpa>4.3.1.Final</version.jpa>
<!-- Genere un conflit
avec la version de spring
attendu 3.1.4 (cf vue
dependency), utilisée
4.0.1 : le resultat peut
etre aleatoire ... faut
tester -->
<version.spring.data>1.4.4.RELEASE</version.spring.data>
<version.hsqldb>2.3.1</version.hsqldb>
<version.mysql>5.1.29</version.mysql>
<version.dbcp>1.4</version.dbcp>
<version.jstl>1.2</version.jstl>
<version.war.plugin>2.4</version.war.plugin>
</properties>
<!-- If you have access
to scm then you can place
actual url's. Otherwise
with <revisionOnScmFailure
/> you can give some
fake URLs as follows. -->
<scm>
<connection>scm:svn:http://none</connection>
<developerConnection>scm:svn:https://none</developerConnection>
<url>scm:svn:https://none</url>
</scm>
<dependencies>
<!-- Spring -->
<!-- apres test ...
pour eviter un conflit
avec la version asm demande
par spring-data, sans
cette dependance, l'asm
rappatriee est dans une
mauvaise version -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${version.spring}</version>
</dependency>
<!-- Spring-ORM -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${version.spring}</version>
</dependency>
<!-- Spring-data (generation
auto des dao) -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${version.spring.data}</version>
</dependency>
<!-- hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${version.jpa}</version>
</dependency>
<!-- basicdatasource -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${version.dbcp}</version>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${version.jstl}</version>
</dependency>
</dependencies>
<!-- PROFILS -->
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>
true
</activeByDefault>
</activation>
<properties>
<datasource.driverClassName>
org.hsqldb.jdbcDriver
</datasource.driverClassName>
<database.url>
jdbc:hsqldb:mem:testdb
</database.url>
<database.username>sa</database.username> <!-- sa -->
<database.password></database.password> <!-- vide -->
<jpa.database>HSQL</jpa.database>
<!-- definit ici
ou au debut du pom, au
choix -->
<!-- <version.hsqldb>2.3.1</version.hsqldb> -->
</properties>
<dependencies>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${version.hsqldb}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Permet de personnaliser
la construction du war
(modif manifest, ...) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${version.war.plugin}</version>
<!-- on definit
pas de goal etc car il
sera appele lors du packaging -->
<configuration>
<webResources>
<resource>
<!-- relative
to the pom.xml directory -->
<directory>
src/main/resources/dev
</directory>
</resource>
</webResources>
<!-- dossier ou
sera déposé le war -->
<outputDirectory></outputDirectory>
<!-- chemin vers
le dossier webapp du
projet (src/main/webapp) -->
<webappDirectory></webappDirectory>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</profile>
<profile>
<id>prod</id>
<properties>
<database.url>jdbc:mysql//localhost:3306/baseprod
</database.url>
<datasource.driverClassName>org.mysql.jdbc.Driver
</datasource.driverClassName>
<database.username>user</database.username>
<database.password>14247</database.password>
<jpa.database>MYSQL</jpa.database>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${version.mysql}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Permet de personnaliser
la construction du war
(modif manifest, ...) -->
<!-- marche pas
top ce plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${version.war.plugin}</version>
<!-- on definit
pas de goal etc car il
sera appele lors du packaging -->
<configuration>
<packagingExcludes>
WEB-INF/classes/dev
</packagingExcludes>
<webResources>
<resource>
<!-- relative
to the pom.xml directory -->
<directory>
src/main/resources/prod
</directory>
<filtering>true</filtering>
<targetPath>
WEB-INF/classes
</targetPath>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</profile>
</profiles>
<build>
<!-- declare le nom
du war cree / nom du
projet -->
<!-- ${project.version}
: numero de version du
projet (ce qui est declare
dans le <version> du
project -->
<finalName>${project.name}.${buildNumber}</finalName>
<!-- goal lance par
defaut : appel mvn -->
<defaultGoal>clean package</defaultGoal>
<plugins>
<!-- Parametrage de
la compilation : version
des sources/jdk utilise -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version> <!-- voir la version
dans le core de maven
http://maven.apache.org/plugins/ -->
<configuration>
<source>${version.java}</source>
<target>${version.java}</target>
</configuration>
</plugin>
<plugin>
<!-- permet de creer
un numero de build, qui
est dispo dans la variable
${buildNumber} -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
<configuration>
<doCheck>false</doCheck> <!-- ne se connecte pas
au scm -->
<doUpdate>false</doUpdate> <!-- ne fait pas d'update -->
<!-- This ensures
that even if we are not
connected to scm than
also take the version
from local .svn file -->
<revisiononscmfailure>true</revisiononscmfailure>
<!-- Generate sequence
build number based on:
build number and timestamp -->
<!-- <format>Build:
#{0} ({1,date})</format> -->
<!-- ({1,date,yyyy-MM-dd
HH:mm:ss}) -->
<format>{0}
({1,date,yyyy-MM-dd})</format>
<items>
<!-- <item implementation="java.lang.Integer">0</item> -->
<item>buildNumber\d*</item>
<item>timestamp</item>
</items>
</configuration>
</plugin>
<!-- Ajout du plugin
permettant de piloter
tomcat : goal utilisable
par exemple : mvn tomcat7:deploy
; mvn tomcat7:undeploy -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- id du serveur
definit dans le conf/settings.xml
de maven ... <server>
<id>tomcatdev</id> <username>tomcat</username>
<password>tomcat</password>
</server> ... -->
<server>tomcatdev</server>
<!-- url de deploiement
d'applicatif -->
<url>http://localhost:8080/manager/text</url>
</configuration>
<!-- commande dexecution -->
<executions>
<execution>
<id>deploy-tomcat</id>
<!-- phase dans
le cycle de vie de maven -->
<phase>install</phase>
<!-- une fois dans
install lance le goal
: deploy du plugins tomcat.maven -->
<goals>
<!-- tache(s)
du plugin -->
<goal>undeploy</goal>
<!-- deploy le
war présent -->
<goal>deploy-only</goal> <!-- ou "deploy" mais
cela fork le cycle de
build, de vie de maven -->
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>ant-copy</id>
<!-- phase dans
le cycle de vie de maven -->
<phase>install</phase>
<!-- une fois dans
install lance le goal
: deploy du plugins antrun -->
<goals>
<!-- tache(s)
du plugin -->
<goal>run</goal>
</goals>
<configuration>
<target
name="copy file to partage"
>
<copy
file="${project.build.directory}/${project.name}.${buildNumber}.war"
tofile="c:/temp/${project.name}.${buildNumber}.war" />
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>C:\temp\script.bat</executable>
<arguments>
<argument>"${env.JAVA_HOME}"</argument>
<!-- commande
maven exploitant l'argument
argToto : mvn clean install
-DargToto=test -->
<argument>"${argToto}"</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Conclusion
J'espère que ce petit tour d'horizon de Maven vous aura aidé à en comprendre la philosophie, l'usage et sa personnalisation.
Si vous désirez aller plus loin dans l'utilisation de Maven, sa configuration, etc il existe de très bons livres en français ou en anglais : http://maven.apache.org/articles.html
Vous pouvez aussi retrouver énormément de documentation sur le site du projet directement : http://maven.apache.org/guides/index.html