Une introduction à Maven 2

Maven est un outil de construction open source populaire pour les projets Java d'entreprise, conçu pour éliminer une grande partie du travail acharné du processus de construction. Maven utilise une approche déclarative, où la structure et le contenu du projet sont décrits, plutôt que l'approche basée sur les tâches utilisée dans Ant ou dans les fichiers make traditionnels, par exemple. Cela permet d'appliquer les normes de développement à l'échelle de l'entreprise et de réduire le temps nécessaire pour écrire et gérer les scripts de build.

L'approche déclarative basée sur le cycle de vie utilisée par Maven 1 est, pour beaucoup, un changement radical par rapport aux techniques de construction plus traditionnelles, et Maven 2 va encore plus loin à cet égard. Dans cet article, je passe en revue certains des principes de base derrière Maven 2, puis je passe en revue un exemple de travail. Commençons par passer en revue les fondamentaux de Maven 2.

Le modèle objet du projet

Le cœur d'un projet Maven 2 est le modèle objet du projet (ou POM en abrégé). Il contient une description détaillée de votre projet, y compris des informations sur la gestion des versions et de la configuration, les dépendances, les ressources d'application et de test, les membres et la structure de l'équipe, et bien plus encore. Le POM prend la forme d'un fichier XML ( pom.xml ), qui est placé dans le répertoire de base de votre projet. Un simple fichier pom.xml est affiché ici:

 4.0.0 com.javaworld.hotels HotelDatabase war 1.0-SNAPSHOT Maven Quick Start Archetype //maven.apache.org   junit junit 3.8.1 test   

La structure des répertoires Maven 2

Une grande partie du pouvoir de Maven provient des pratiques standard qu'il encourage. Un développeur qui a déjà travaillé sur un projet Maven se sentira immédiatement familiarisé avec la structure et l'organisation d'un nouveau projet. Il n'est pas nécessaire de perdre du temps à réinventer les structures de répertoires, les conventions et les scripts de création Ant personnalisés pour chaque projet. Bien que vous puissiez remplacer n'importe quel emplacement de répertoire particulier à vos propres fins, vous devez vraiment respecter la structure de répertoire standard de Maven 2 autant que possible, pour plusieurs raisons:

  • Cela rend votre fichier POM plus petit et plus simple
  • Cela rend le projet plus facile à comprendre et facilite la vie du pauvre gars qui doit maintenir le projet lorsque vous partez
  • Cela facilite l'intégration des plug-ins

La structure de répertoires standard de Maven 2 est illustrée dans la figure 1. Dans le répertoire de base du projet se trouve le POM (pom.xml) et deux sous-répertoires: src pour tout le code source et cible pour les artefacts générés.

Le répertoire src a un certain nombre de sous-répertoires, dont chacun a un objectif clairement défini:

  • src / main / java: Votre code source Java va ici (assez étrangement!)
  • src / main / resources: autres ressources dont votre application a besoin
  • src / main / filters: Filtres de ressources, sous forme de fichiers de propriétés, qui peuvent être utilisés pour définir des variables connues uniquement à l'exécution
  • src / main / config: fichiers de configuration
  • src / main / webapp: le répertoire de l'application Web pour un projet WAR
  • src / test / java: tests unitaires
  • src / test / resources: ressources à utiliser pour les tests unitaires, mais ne seront pas déployées
  • src / test / filters: filtres de ressources à utiliser pour les tests unitaires, mais ne seront pas déployés
  • src / site: Fichiers utilisés pour générer le site Web du projet Maven

Cycles de vie des projets

Les cycles de vie des projets sont au cœur de Maven 2. La plupart des développeurs connaissent la notion de phases de construction telles que la compilation, le test et le déploiement. Ant a des cibles avec des noms comme ceux-là. Dans Maven 1, les plug-ins correspondants sont appelés directement. Pour compiler le code source Java, par exemple, le javaplug-in est utilisé:

$maven java:compile

Dans Maven 2, cette notion est normalisée en un ensemble de phases du cycle de vie bien connues et bien définies (voir Figure 2). Au lieu d'invoquer des plug-ins, le développeur Maven 2 appelle une phase du cycle de vie: $mvn compile.

Certaines des phases du cycle de vie Maven 2 les plus utiles sont les suivantes:

  • generate-sources: Génère tout code source supplémentaire nécessaire à l'application, ce qui est généralement réalisé à l'aide des plug-ins appropriés
  • compile: Compile le code source du projet
  • test-compile: Compile les tests unitaires du projet
  • test: Exécute les tests unitaires (généralement en utilisant JUnit) dans le répertoire src / test
  • package: Package le code compilé dans son format distribuable (JAR, WAR, etc.)
  • integration-test: Traite et déploie le package si nécessaire dans un environnement où les tests d'intégration peuvent être exécutés
  • install: Installe le package dans le référentiel local pour une utilisation en tant que dépendance dans d'autres projets sur votre machine locale
  • deploy: Fait dans un environnement d'intégration ou de version, copie le package final dans le référentiel distant pour le partager avec d'autres développeurs et projets

De nombreuses autres phases du cycle de vie sont disponibles. Voir Ressources pour plus de détails.

Ces phases illustrent les avantages des pratiques recommandées encouragées par Maven 2: une fois qu'un développeur est familiarisé avec les principales phases du cycle de vie de Maven 2, il doit se sentir à l'aise avec les phases du cycle de vie de tout projet Maven.

La phase du cycle de vie appelle les plug-ins dont elle a besoin pour effectuer le travail. L'appel d'une phase du cycle de vie appelle également automatiquement toutes les phases précédentes du cycle de vie. Étant donné que les phases du cycle de vie sont limitées en nombre, faciles à comprendre et bien organisées, il est facile de se familiariser avec le cycle de vie d'un nouveau projet Maven 2.

Dépendances transitives

L'un des points forts de Maven 2 est la gestion des dépendances transitive. Si vous avez déjà utilisé un outil comme urpmi sur une machine Linux, vous saurez ce que sont les dépendances transitives. Avec Maven 1, vous devez déclarer chaque JAR qui sera nécessaire, directement ou indirectement, par votre application. Par exemple, pouvez-vous lister les JAR requis par une application Hibernate? Avec Maven 2, vous n'avez pas à le faire. Indiquez simplement à Maven les bibliothèques dont vous avez besoin, et Maven s'occupera des bibliothèques dont vos bibliothèques ont besoin (et ainsi de suite).

Supposons que vous souhaitiez utiliser Hibernate dans votre projet. Vous ajouteriez simplement une nouvelle dépendance à la dependenciessection dans pom.xml, comme suit:

  hibernate hibernate 3.0.3 compile 

Et c'est tout! Vous n'avez pas besoin de chercher pour savoir dans quels autres JAR (et dans quelles versions) vous devez exécuter Hibernate 3.0.3; Maven le fera pour vous!

La structure XML des dépendances dans Maven 2 est similaire à celle utilisée dans Maven 1. La principale différence est la scopebalise, qui est expliquée dans la section suivante.

Étendues de dépendance

Dans une application d'entreprise réelle, vous n'aurez peut-être pas besoin d'inclure toutes les dépendances dans l'application déployée. Certains JAR ne sont nécessaires que pour les tests unitaires, tandis que d'autres seront fournis au moment de l'exécution par le serveur d'applications. En utilisant une technique appelée portée des dépendances , Maven 2 vous permet d'utiliser certains JAR uniquement lorsque vous en avez vraiment besoin et les exclut du chemin de classe lorsque vous n'en avez pas.

Maven fournit quatre étendues de dépendance:

  • compile: Une dépendance de portée de compilation est disponible dans toutes les phases. Ceci est la valeur par défault.
  • provided: Une dépendance fournie est utilisée pour compiler l'application, mais ne sera pas déployée. Vous utiliserez cette étendue lorsque vous attendez du JDK ou du serveur d'applications qu'il fournisse le JAR. Les API de servlet en sont un bon exemple.
  • runtime: Les dépendances de portée d'exécution ne sont pas nécessaires pour la compilation, uniquement pour l'exécution, comme les pilotes JDBC (Java Database Connectivity).
  • test: Les dépendances de portée de test ne sont nécessaires que pour compiler et exécuter des tests (JUnit, par exemple).

Communication de projet

La communication interne est un élément important de tout projet. Bien qu'il ne s'agisse pas d'une solution miracle, un site Web de projet technique centralisé peut grandement contribuer à améliorer la visibilité au sein de l'équipe. Avec un minimum d'effort, vous pouvez avoir un site Web de projet de qualité professionnelle opérationnel en très peu de temps.

Cela prend une toute nouvelle dimension lorsque la génération du site Maven est intégrée dans un processus de construction utilisant une intégration continue ou même des builds nocturnes automatiques. Un site Maven typique peut publier quotidiennement:

  • Informations générales sur le projet telles que les référentiels sources, le suivi des défauts, les membres de l'équipe, etc.
  • Rapports de test unitaire et de couverture de test
  • Revues de code automatiques et avec Checkstyle et PMD
  • Informations de configuration et de version
  • Dépendances
  • Javadoc
  • Code source au format HTML indexé et croisé
  • Liste des membres de l'équipe
  • Et beaucoup plus

Une fois de plus, tout développeur averti de Maven saura immédiatement où chercher pour se familiariser avec un nouveau projet Maven 2.

Un exemple pratique

Maintenant que nous avons vu quelques-unes des notions de base utilisées dans Maven 2, voyons comment cela fonctionne dans le monde réel. Le reste de ce didacticiel examine comment nous utiliserions Maven 2 sur un projet Java Enterprise Edition simple. L'application de démonstration implique un système de base de données hôtelier imaginaire (et simplifié). Pour montrer comment Maven gère les dépendances entre les projets et les composants, cette application sera créée à l'aide de deux composants (voir Figure 3):

  • Un composant de logique métier: HotelDatabase.jar
  • Un composant d'application Web: HotelWebApp.war

Vous pouvez télécharger le code source à suivre avec le didacticiel dans Ressources.

Configurez votre environnement de projet

We start by configuring your work environment. In real-world projects, you will often need to define and configure environment or user-specific parameters that should not be distributed to all users. If you are behind a firewall with a proxy, for example, you need to configure the proxy settings so that Maven can download JARs from repositories on the Web. For Maven 1 users, the build.properties and project.properties files do this job. In Maven 2, they have been replaced by a settings.xml file, which goes in the $HOME/.m2 directory. Here is an example:

     http scott tiger 8080 my.proxy.url    

Create a new project with the archetype plug-in

The next step is to create a new Maven 2 project template for the business logic component. Maven 2 provides the archetype plug-in, which builds an empty Maven 2-compatible project directory structure. This plug-in proves convenient for getting a basic project environment up and running quickly. The default archetype model will produce a JAR library project. Several other artifact types are available for other specific project types, including Web applications, Maven plug-ins, and others.

Run the following command to set up your HotelDatabase.jar project:

mvn archetype:create -DgroupId=com.javaworld.hotels - DartifactId=HotelDatabase -Dpackagename=com.javaworld.hotels

Now you have a brand new Maven 2 project directory structure. Switch to the HotelDatabase directory to continue the tutorial.

Implementing the business logic

Maintenant, nous implémentons la logique métier. La Hotelclasse est un simple JavaBean. La HotelModelclasse implémente deux services: la findAvailableCities()méthode, qui répertorie les villes disponibles, et la findHotelsByCity()méthode, qui répertorie tous les hôtels d'une ville donnée. Une implémentation simple et basée sur la mémoire de la HotelModelclasse est présentée ici:

package com.javaworld.hotels.model;

import java.util.ArrayList; import java.util.List;

import com.javaworld.hotels.businessobjects.Hotel;

public class HotelModel {

/** * The list of all known cities in the database. */ private static String[] cities = { "Paris", "London", }; /** * The list of all hotels in the database. */ private static Hotel[] hotels = { new Hotel("Hotel Latin","Quartier latin","Paris",3), new Hotel("Hotel Etoile","Place de l'Etoile","Paris",4), new Hotel("Hotel Vendome","Place Vendome","Paris",5), new Hotel("Hotel Hilton","Trafalgar Square","London",4), new Hotel("Hotel Ibis","The City","London",3), }; /** * Returns the hotels in a given city. * @param city the name of the city * @return a list of Hotel objects */ public List findHotelsByCity(String city){ List hotelsFound = new ArrayList(); for(Hotel hotel : hotels) { if (hotel.getCity().equalsIgnoreCase(city)) { hotelsFound.add(hotel); } } return hotelsFound; } /** * Returns the list of cities in the database which have a hotel. * @return a list of city names */ public String[] findAvailableCities() { return cities; } }