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