Java - von am Tuesday, July 19, 2011 20:30 - 0 Kommentare

Eine (wirklich gute) Einführung in Maven

Wenn es um das Thema Maven geht scheiden sich die Geister, bzw. Entwickler in Maven-Liebhaber und Maven-Hasser. Dazwischen scheint es nichts zu geben. Für die einen ist es ein unverzichtbares Hilfsmittel, um Projekten eine einheitliche Struktur zu geben und einen neuen Entwickler im Team schnell an ein ihm unbekanntes Projekt heranzuführen. Andere schreckt die Konfiguration ab, da viel über Maven und das ihm zugrundeliegende Vorgehensmodell bekannt sein muss, um es effektiv nutzen zu können. In Ant gibt der Entwickler die einzelnen Arbeitsschritte in leicht verständlichen Befehlen selbst an (kopieren, kompilieren, packen etc.), während das in Maven in elementaren Teilen automatisch gemacht wird und man nur noch in bestimmten Stellen im Prozess, wenn nötig, weitere Schritte hinzufügt. Stichwort: Build Lifecycle, Scope und Phase.

Das folgende Tutorial soll eine Einführung in Maven geben und evtl. als Entscheidungshilfe dienen, ob es eingesetzt werden soll.

Los geht’s…

1. Als Minimalanforderung ist das J2SE 1.5 angesagt.

Wir laden Maven herunter und installieren es von http://maven.apache.org/download.html.
Für Windows empfiehlt es sich die .zip-Datei herunterzuladen (apache-maven-3.0.3-bin.zip), unter Linux/MacOS sollte man zur .tar.gz-Datei greifen (apache-maven-3.0.3-bin.tar.gz).

2. Anschließend müssen wir die Datei in einem Verzeichnis entpacken:

Es hat sich bewährt, die Versionsnummer im Verzeichnisnamen bestehen zu lassen:

Windows:
Wir entpacken die Datei unter C:\Java. Das Maven-Wurzelverzeichnis lautet damit dann C:\Java\apache-maven-3.0.3 .

Linux / Mac OS X, in der Shell:

  1. tar -xvf /Users/<USERNAME>/Download/apache-maven-3.0.3-bin.tar.gz /Users/<USERNAME>/Java/

… entpackt den Inhalt der Datei unter /Users/<USERNAME>/Java. Das Wurzelverzeichnis lautet dann /Users/<USERNAME>/Java/apache-maven-3.0.3.

Dieses Wurzelverzeichnis, M2_HOME genannt, müssen wir jetzt noch über eine Umgebungsvariable systemweit bekannt machen.

Windows XP / 7:
Wir gehen auf “Start > Ausführen” und geben “sysdm.cpl” und ENTER ein.
Im Reiter “Erweitert” klicken wir auf “Umgebungsvariablen”. Unter “Benutzervariablen für <USERNAME>” erstellen wir mit “Neu” eine neue Benutzervariable, die wir M2_HOME nennen. Als Wert der Variable geben wir C:\Java\apache-maven-3.0.3 an
Anschließend legen wir eine weitere Benutzervariable an, die wir M2 nennen. Als Wert geben wir C:\Java\apache-maven-3.0.3\bin an.
Schließlich müssen wir Maven noch in den Pfad aufnehmen, so dass wir von jedem Verzeichnis aus per Eingabe von “mvn” Maven starten können, ohne den Pfad angeben zu müssen. Dazu einfach die Systemvariable PATH bearbeiten und den Wert der Variablen um den Eintrag %M2% ergänzen. Es muss darauf geachtet werden, dass vor dem %M2% ein Semikolon steht, um die einzelnen Pfadeinträge voneinander zu trennen. Falls davor kein Semikolon steht: einfach hinzufügen und auf Ok klicken.
Wenn das alles erledigt ist prüfen wir, ob Maven jetzt korrekt konfiguriert ist. Dazu gehen wir in ein DOS-Fenster (Start > Ausführen > cmd.exe und ENTER drücken) und tippen “mvn --version” ein. Als Ergebnis erhalten wir in etwa “Apache Maven 3.0.3...“.
Achtung: ein bereits geöffnetes DOS-Fenster “sieht” die Ergänzungen der Umgebungsvariablen nicht. Es muss ein neues DOS-Fenster geöffnet werden ,damit es korrekt mit den neuen und geänderten Umgebungsvariablen initialisiert werden kann.

Linux / Mac OS X, in der Shell:
Wir öffnen und bearbeiten die Datei ~/.bashrc und erweitern, bzw. ergänzen, sie um folgende Einträge:

  1. export M2_HOME=/Users/<USERNAME>/Java/apache-maven-3.0.3
  2. export PATH=${M2_HOME}/bin:....

3. Wir bleiben in diesem DOS-Fenster und legen jetzt ein einfaches Maven-Projekt an:

Windows XP / 7:

  1. C:\> mkdir Java
  2. C:\> cd Java
  3. C:\Java>
  4. C:\Java> mkdir Projekte
  5. C:\Java> cd Projekte
  6. C:\Java\Projekte> mvn archetype:generate -DgroupId=de.theserverside -DartifactId=einmavenprojekt -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Linux / Mac OS X:

  1. cd /Users/<USERNAME>
  2. mkdir Java
  3. cd Java
  4. mkdir Projekte
  5. cd Projekte
  6. mvn archetype:generate -DgroupId=de.theserverside -DartifactId=einmavenprojekt -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Folgend eine kurze Erläuterung der Parameter:

archetype:generate
Dieser Parameter gibt an, dass ein neues Maven-Projekt angelegt werden soll.

-DgroupId=de.theserverside
Dies ist, ähnlich wie in den Java-Packagestrukturen, nur eine Info, um das Projekt innerhalb einer größeren Menge von Projekten gruppieren zu können. Es hat sich etabliert, auch hier die Syntax TLD.DOMAIN.IRGENDETWAS zu verwenden. Allerdings gibt es auch Projekte, speziell im Open-Source Umfeld, die sich nicht daran halten und nur den Namen des Projekts verwenden. Beispiel: Log4J, wo die groupId einfach nur “log4j” lautet.

-DartifactId=meinprojekt
Hiermit benennen wir das Projekt, bzw. Modul. Für gewöhnlich ist das der “offizielle” Name des Projekts, also im Falle von Log4J wäre es z.B. “log4j”.

-DarchetypeArtifactId=maven-archetype-quickstart
Der “Archetyp” des Projekts, also um welche *Art* von Projekt es sich handelt wird hiermit angegeben. Hiermit sagen wir, dass eine simple Maven-Standardverzeichnisstruktur anzulegen ist, für ein normales Java-Projekt. Wir könnten auch einen anderen Archetypen angeben, z.B. um ein Spring- oder JSF-Projekt anzulegen, und die Verzeichnisstruktur würde erweitert werden um weitere Unterverzeichnisse und rudimentär angelegte Konfigurationsdateien. Im Falle eines Webprojekts würde z.B. ein WEB-INF Verzeichnis mit einer einfachen “web.xml” angelegt werden, die wir dann nur noch um unsere eigenen Einträge ergänzen müssen.

-DinteractiveMode=false
Hiermit geben wir an, dass bei der Ausführung des Befehls nichts weiter abgefragt werden und der Befehl “am Stück” ausgeführt soll.

Als Ausgabe erhalten wir:

  1. [INFO] Scanning for projects...
  2. [INFO] Searching repository for plugin with prefix: 'archetype'.
  3. [INFO] ------------------------------------------------------------------------
  4. [INFO] Building Maven Default Project
  5. [INFO]    task-segment: [archetype:generate] (aggregator-style)
  6. [INFO] ------------------------------------------------------------------------
  7. [INFO] Preparing archetype:generate
  8. [INFO] No goals needed for project - skipping
  9. [INFO] [archetype:generate {execution: default-cli}]
  10. [INFO] Generating project in Batch mode
  11. ... weitere Meldungen...
  12. [INFO] BUILD SUCCESSFUL
  13. [INFO] ------------------------------------------------------------------------
  14. [INFO] Total time: 12 seconds
  15. [INFO] Finished at: Thu Jul 14 21:34:54 CEST 2011
  16. [INFO] Final Memory: 11M/20M
  17. [INFO] ------------------------------------------------------------------------

Zwischen dem Beginn und dem “BUILD SUCCESSFUL” finden sich auch Download-Meldungen, falls Maven eine bestimmte Funktionalität (z.B. ein Archetyp) gefehlt hat und es sich das erst nachträglich dazuinstallieren musste nachdem es aus dem Internet von einer bestimmten Adresse (genannt: Repository) heruntergeladen wurde.

4. Wir schauen uns das generierte Projekt an und finden folgende Verzeichnisstruktur:

  1. - einmavenprojekt
  2.   _ pom.xml
  3.   _ src
  4.    |_ main
  5.      |_ java
  6.        |_ de
  7.          |_ theserverside
  8.            |_ App.java
  9.  
  10.    |_ test
  11.      |_ de
  12.        |_ theserverside
  13.          |_ AppTest.java

Folgendes ist hier geschehen: Maven hat für uns ein Standardprojekt mit dem Maven-Standardverzeichnislayout (“Standard Directory Layout”) angelegt. Hier legt Maven für uns fest, wo die Sourcen liegen, wo evtl. benötigte Resourcen liegen (XML-Konfigurationsdateien etc.) und wo sich die Testklassen befinden sollen. Diese Verzeichnisstruktur ist das Herzstück Mavens, denn darüber weiß Maven wo die für die einzelnen Buildschritte zu verarbeitenden Dateien liegen. Maven wird also in der Kompilierphase immer alles kompilieren, was es unter src/main/java findet. Bei Ausführung von Testklassen wird es diese immer unter test/ suchen, kompilieren und ausführen.

Wie wir sehen hat uns Maven auch zwei Klassen angelegt: App.java und AppTest.java. Diese beinhalten über eine “Hello World”-Ausgabe und einen rudimentären JUnit-Tests hinaus nichts weiter und dienen nur dazu, zu veranschaulichen, wo welche Art von Dateien abzulegen sind.

5. Die Projektdatei “pom.xml”:
Interessant wird es hingegen bei der pom.xml, die unsere eigentliche Projektdatei darstellt. Sie darf nicht umbenannt oder verschoben werden, da Maven ansonsten, für dieses Projekt, nicht korrekt arbeitet.

Wenn wir einen Blick hineinwerfen sehen wir folgenden Inhalt:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2.  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3.   <modelVersion>4.0.0</modelVersion>
  4.   <groupId>de.theserverside</groupId>
  5.   <artifactId>einmavenprojekt</artifactId>
  6.   <packaging>jar</packaging>
  7.   <version>1.0-SNAPSHOT</version>
  8.   <name>einmavenprojekt</name>
  9.   <url>http://maven.apache.org</url>
  10.   <dependencies>
  11.     <dependency>
  12.       <groupId>junit</groupId>
  13.       <artifactId>junit</artifactId>
  14.       <version>3.8.1</version>
  15.       <scope>test</scope>
  16.     </dependency>
  17.   </dependencies>
  18. </project>

Die ersten drei Zeilen enthalten die üblichen Angaben zu XML-Dateien. In “modelVersion” wird angegeben, dass es sich mit dieser Datei um die Version 4.0.0 des Formats für Mavenprojektdateien handelt.
Bei groupId und artifactId handelt es sich um die Angaben die wir bei der Anlage des Projekts vorgenommen haben.
Mit packaging sagen wir aus, welche Art von Datei am Ende des Buildprozesses erstellt werden soll. In diesem Fall wird eine .jar-Datei angelegt. Wenn wir hier “war” eintragen würden alle für eine .war-Datei benötigten Komponenten (WEB-INF, web.xml etc.) nach dem kompilieren zusammengesammelt und in eine .war-Datei gepackt werden die wir z.B. in den Tomcat deployen.
Die version sagt aus, dass es sich zwar um die Version 1.0 der Anwendung handelt, diese aber ein SNAPSHOT ist, d.h. sich inhaltlich ständig ändern kann. Wenn wir das SNAPSHOT entfernen und wir es als “Release” herausgeben (dazu später mehr) garantieren wir anderen Entwicklern damit, dass sich der Code in der Datei nicht mehr ändert. Bei einem SNAPSHOT hingegen ist noch Vorsicht geboten.
Der name ist der bei der Anlage des Projekts vergebene Name und sollte auf etwas lesbareres geändert werden, z.B. “Mein 1. Mavenprojekt”.
Die url kann auch frei vergeben werden, hier könnten wir z.B. http://www.theserverside.de eintragen.

6. Dependency Management
Interessant wird es bei den dependencies, das herausragende und wichtigste Merkmal bei Maven. Hier geben wir an, wann welche Libraries in unserem Projekt benötigt werden. In diesem Fall geben wir an, dass unser Code junit in der Version 3.8.1 verwendet. Allerdings ist als scope der Wert test angegeben. Damit sagen wir aus, dass diese Library nur benötigt wird, wenn wir Maven instruieren, die Testklassen auszuführen. Wenn das .jar zum deployen gebaut wird soll die JUnit-Library nicht mit in die erzeugte Datei aufgenommen werden. Alternativ wäre es im Falle einer Webanwendung (package-Angabe “war“) beispielsweise auch unsinnig, die JUnit-Library mit einzubinden, da Tests für gewöhnlich immer in der Konsole gestartet werden, vor dem Deployment, und nichts in der fertig deployten Anwendung, sprich: in der .war-Datei, zu suchen hat.

Als Maven-Neuling denkt man sich jetzt, dass diese Dependency-Angabe ja nun sehr aussagekräftig und interessant ist, aber was macht Maven damit und wo holt es JUnit her? Die Antwort ist ganz einfach: JUnit wird aus dem Internet heruntergeladen. Es gibt mehrere offizielle “Repositories”, beispielsweise http://repo1.maven.org/, wo sich riesige Sammlungen bekannter Libraries in ihren verschiedenen Versionen befinden. Alle mehr oder weniger bekannten OpenSource-Projekte sind dort vertreten und werden auch regelmäßig aktualisiert. Identifiziert werden sie über ihre “Maven-Coordinates”, das sind, um das Thema abzurunden, die Angaben groupId, artifactId und version.

7. Das lokale Repository
Jetzt stellt man sich zurecht die Frage, ob die Dependencies jedes Mal von neuem heruntergeladen werden. Wenn ich drei Projekte auf meinem Rechner anlege hieße das ja auch, dass er für jedes der Projekte JUnit 3.8.1 herunterlädt. Diese Trafficlast wäre von den offiziellen Repositories gar nicht zu tragen und daher sorgt Maven auch hier vor. Unter dem Benutzerverzeichnis, unter Windows wäre das C:\Dokumente und Einstellungen\<USERNAME> und auf dem Mac ~/, wird ein Verzeichnis .m2 angelegt, welches nicht nur eine benutzerspezifische Konfigurationsdatei settings.xml enthält, sondern auch ein lokales Repository. Dort wird, identisch zu der Struktur in den offiziellen Repositories und damit auch gemäß der Maven-Coordinates der jeweiligen Library, alles abgelegt was heruntergeladen wurde. Das schließt auch Maven-eigene Plugins ein. Wenn also eine neue Dependency in einer pom.xml entdeckt wird und diese nicht im lokalen Repository liegt wird sie aus dem Internet heruntergeladen und im lokalen Repository, zur systemweiten Verwendung für alle Maven-Projekte, abgelegt.

8. Jetzt wird gebaut…
Nach diesem Überblick wollen wir unser Projekt “bauen”, also kompilieren und paketieren, lassen. In der Konsole geben wir dazu im Wurzelverzeichnis unseres Projekts folgendes ein:

  1. mvn clean package

Als Ausgabe erhalten wir:

  1. [INFO] Scanning for projects...
  2. [INFO] ------------------------------------------------------------------------
  3. [INFO] Building meinprojekt
  4. [INFO]    task-segment: [package]
  5. [INFO] ------------------------------------------------------------------------
  6. [INFO] [clean:clean {execution: default-clean}]
  7. [INFO] Deleting directory C:\Java\meinprojekt\target
  8. [INFO] [resources:resources {execution: default-resources}]
  9. [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
  10. [INFO] skip non existing resourceDirectory C:\Java\meinprojekt\src\main\resources
  11. [INFO] [compiler:compile {execution: default-compile}]
  12. [INFO] Compiling 1 source file to C:\Java\meinprojekt\target\classes
  13. [INFO] [resources:testResources {execution: default-testResources}]
  14. [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
  15. [INFO] skip non existing resourceDirectory C:\Java\meinprojekt\src\test\resources
  16. [INFO] [compiler:testCompile {execution: default-testCompile}]
  17. [INFO] Compiling 1 source file to C:\Java\meinprojekt\target\test-classes
  18. [INFO] [surefire:test {execution: default-test}]
  19. [INFO] Surefire report directory: C:\Java\meinprojekt\target\surefire-reports
  20.  
  21. -------------------------------------------------------
  22.  T E S T S
  23. -------------------------------------------------------
  24. Running de.theserverside.AppTest
  25. Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 sec
  26.  
  27. Results :
  28.  
  29. Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
  30.  
  31. [INFO] [jar:jar {execution: default-jar}]
  32. [INFO] Building jar: C:\Java\meinprojekt\target\meinprojekt-1.0-SNAPSHOT.jar
  33. [INFO] ------------------------------------------------------------------------
  34. [INFO] BUILD SUCCESSFUL
  35. [INFO] ------------------------------------------------------------------------
  36. [INFO] Total time: 7 seconds
  37. [INFO] Finished at: Tue Jul 19 18:17:14 CEST 2011
  38. [INFO] Final Memory: 8M/17M
  39. [INFO] ------------------------------------------------------------------------

Aus den Ausgabemeldungen sieht man, dass Maven, u.A., alle eventuell vorher erzeugten Kompilate und Buildartefakte löscht, das Projekt kompiliert, die Testklassen kompiliert, die Tests startet und das Ganze als .jar-Datei packt und unter C:\Java\meinprojekt\target\meinprojekt-1.0-SNAPSHOT.jar ablegt.

Diese verschiedenen Schritte folgen festgelegten “Phasen” im Maven-Buildprozess und wir können uns an den verschiedenen Stellen, über Einträge in der pom.xml “einhaken”. Dazu eventuell später mehr in einem Folgeartikel, aber für einen Einstieg in den Build Lifecycle empfiehlt sich erst einmal der folgende Link aus der Maven Dokumentation Introduction to the Build Lifecycle.

Die .jar-Datei die wir erhalten haben, meinprojekt-1.0-SNAPSHOT.jar, könnten wir über Maven-Befehle auch wieder in unser Repository, oder ein Unternehmensrepository, deployen und dort anderen Entwicklern zur Verfügung stellen. Hätten wir uns eine .war-Datei generieren lassen könnten wir diese z.B. direkt in den Tomcat deployen, oder Maven diese deployen lassen, und die Anwendung sofort testen. Maven ist in diesen Punkten sehr stark erweiterbar und man kann als Entwickler in jede Phase des Buildprozesses eingreifen und diesen anpassen.

9. Und was ist mit der IDE?
Natürlich soll diese nicht zu kurz kommen und auch hier lässt Maven uns nicht im Stich. Einfach in der Konsole folgendes eingeben:

  1. mvn eclipse:eclipse

… und wir erhalten eine .project und die dazu passende .classpath-Datei, die wir einfach im Eclipse importieren. Analog gibt es auch den Befehl, in Maven-Sprache “target”, mvn idea:idea für IntelliJ IDEA Projektdateien.

Weiterführende Literatur
“Maven ist ein Biest”, das muss man ganz klar sagen. Die Abneigung speist sich zum größten Teil aus Unwissenheit über den Build Lifecycle. Ohne diesen zu verstehen gestaltet sich eine Anpassung abweichend vom Maven-Standardvorgehen als sehr schwierig. Ist das einmal gezähmt möchte man Maven nicht mehr missen und Ant nicht mehr anfassen müssen.

Für die Zähmung unverzichtbar war für mich Apache Maven 2 Effective Implementation von Brett Porter und Maria Odea Ching. Das Buch ist zwar auf Englisch und behandelt Maven 2, es ist aber sehr verständlich geschrieben und praktisch alles was sich dort findet gilt auch für die aktuelle dritte Version von Maven. Ein weiterer Vorteil ist, dass das Buildmanagement als geschlossene Einheit behandelt und auch auf Continuum und Archiva detailliert eingegangen wird.

Das deutschsprachige Buch Maven 3: Konfigurationsmanagement mit Java von Martin Spiller, welches sich mit Maven 3 beschäftigt, ist sicherlich auch einen Blick wert. Da es noch nicht erschienen ist (Stand: 19.07.2011) kenne ich es noch nicht, es ist aber das meines Wissens erste deutschsprachige Buch zu Maven 3 und ich werde bald einen Blick riskieren.

Be Sociable, Share!


Kommentare

Kommentieren

Weitere Empfehlungen: