Maven ist ein build Tool und Framework für java, welches es sich zum Ziel gemacht hat dem Anwender möglichst viele Schritte abzunehmen. In der allmächtigen pom.xml werden alle relevanten Optionen konfiguriert und ansonsten mit Konventionen in Form von Verzeichnissturktur und Dateinamen gearbeitet.
Das Framework erhält durch seine Pluginfähigkeit eine gute Flexiblität und ermöglicht viele Aufgaben durch ein wenig Konfiguration zu lösen.
Ich will hier eine maven Version meines vorherigen Apache ant Beispiels zeigen, die zum gleichen Ergebnis führt. Zudem gibt es einen kleinen Unit-Test, da das Framework deren Einbindung direkt mitbringt.
(Die resource files und merge Datei liegen bewusst in einem Verzeichnis um herauszufinden wie man die Filter verwendet.)
Code
Verzeichnisstruktur
Die Grundstruktur von Maven ist dabei standardmäßig wie folgt:
pom.xml src/main src/resources src/test src/test/resources
Im Beispiel:
./pom.xml ./src ./src/main ./src/main/java ./src/main/java/de ./src/main/java/de/benjaminpeter ./src/main/java/de/benjaminpeter/print ./src/main/java/de/benjaminpeter/print/LoadFile.java ./src/main/java/de/benjaminpeter/print/Print.java ./src/main/resources ./src/main/resources/resources ./src/main/resources/resources/inputA ./src/main/resources/resources/inputB ./src/main/resources/resources/merge ./src/main/resources/resources/file ./src/test ./src/test/java ./src/test/java/de ./src/test/java/de/benjaminpeter ./src/test/java/de/benjaminpeter/print ./src/test/java/de/benjaminpeter/print/LoadFileTest.java ./src/test/resources ./src/test/resources/resources ./src/test/resources/resources/file
Java src (analog zum ant Beispiel)
import java.io.*;
import java.lang.*;
public class LoadFile {
public String load() throws FileNotFoundException, UnsupportedEncodingException, IOException {
// File file = new File("resources/file");
// FileInputStream fis = new FileInputStream(file);
// Datei ist nun im jar, also muss sie anders geladen werden
ClassLoader cl = this.getClass().getClassLoader();
InputStream fis = cl.getResourceAsStream("resources/file");
// import groovy ... file.text ... :-/
BufferedReader in = new BufferedReader(new InputStreamReader(fis));
String str = "";
String ret = "";
while ((str = in.readLine()) != null) ret += str + '\n';
return ret.substring(0, ret.length() - 1);
}
}
import java.lang.*;
public class Print {
public static void main(String[] args) {
System.out.println("Hello.");
try {
System.out.println("Content:\n"+
new LoadFile().load());
}
catch (Exception e) {
System.err.println("Error: "+ e.getMessage());
}
}
}
Ressourcen
File: resources/file A: Build @buildNumber@ If the writeList method doesn't catch the checked exceptions that can occur within it, the writeList method must specify that it can throw these exceptions. Let's modify the B: original writeList method to specify the exceptions it can throw instead of catching them. To remind you, here's the original version of the writeList method that won't compile. File: resources/inputA A: Build @buildNumber@ If the writeList method doesn't catch the checked exceptions that can occur within it, the writeList method must specify that it can throw these exceptions. Let's modify the File: resources/inputB B: original writeList method to specify the exceptions it can throw instead of catching them. To remind you, here's the original version of the writeList method that won't compile. File: resources/merge #!/bin/sh cat $1 $2 > $3
Hierbei musste ich @date@ zu @buildNumber@ ändern, da es ohne größeren Aufwand nicht möglich properties in anderen properties zu verwenden. (Falls es jemand besser weiß immer her damit, finde das durchaus wichtig)
pom.xml
<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/maven-v4_0_0.xsd">
<!-- Basisinformationen zum Projekt -->
<modelVersion>4.0.0</modelVersion>
<groupId>de.benjaminpeter.print</groupId>
<artifactId>Print</artifactId>
<packaging>jar</packaging>
<!-- Version des Projekts in maven, der Name des
Jars kann noch einmal extra definiert werden! -->
<version>0.0.1</version>
<name>Print</name>
<url>http://maven.apache.org</url>
<!-- Default dependency fuer Unit-Tests, bibliothek
wird automatisch herunter geladen -->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<!-- scope:
Gibt an, dass diese Abhaengigkeit nur
zum Testen benötigt wird. Das heißt sie wird
auch nur beim Ausführen der Tests
heruntergeladen und ist im
finalen jar/war nicht enthalten. -->
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- Binde die resource "resources/file" ein, dies
wird dynamisch erzeugt wie wir später sehen. -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>resources/file</include>
</includes>
<!-- Zudem soll hier das
Filtering aktiviert werden,
das heißt wir lassen maven einen
Platzhalter @buildNumber@ in der
Datei ersetzen. -->
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<!--
Binde ein Plugin ein um die Build Nummer
variabler erstellen zu können. Hiermit machen
wir die Variable buildNumber vom aktuellen
Datum abhängig.
Auch das Plugin wird von maven bei Bedarf
dynamisch herunter geladen. -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<configuration>
<format>{0,date,yyyyMMdd}</format>
<items>
<item>timestamp</item>
</items>
</configuration>
<!-- Diese Block besagt, dass das Plugin in
der validierungsphase (ganz am Anfang)
ausgeführt werden soll. create ist dabei
eine von mehreren Funktionen des Plugins
die dabei aufgerufen werden soll. -->
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Mit dem jar Plugin können wir
die Erzeugung des jar Files etwas
anpassen. So setzen wir hier die
MainClass fest um ein einfach
ausführbares jar zu bekommen. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>Print</mainClass>
<packageName></packageName>
</manifest>
<manifestEntries>
<mode>development</mode>
<url>${pom.url}</url>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- exec Plugin, dies ermöglicht es alle
Schweinereien zu machen die man
sonst nicht hinbekommt.
Wird hier auch zur Verfizierungsphase
ausgeführt und ruft den Befehl "merge"
aus dem resources Verzeichnis auf
um die Datei "file" zu erzeugen.
An dieser Stelle könnte man sicher auch
ein Ant Plugin einbinden um komplexere
Abläufe zu realisieren und Abhängigkeiten
sowie bereits angelegte Dateien erkennen
zu können. -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>./merge</executable>
<workingDirectory>${basedir}/src/main/resources/resources</workingDirectory>
<arguments>
<argument>inputA</argument>
<argument>inputB</argument>
<argument>file</argument>
</arguments>
</configuration>
</plugin>
</plugins>
<!-- Setze den Namen der jar Datei so, dass
auch unsere im Plugin
erzeugte buildNumber eingesetzt wird. -->
<finalName>${project.artifactId}-${buildNumber}</finalName>
</build>
<properties>
<!-- Funktioniert leider nicht :-/ -->
<date>${buildNumber}</date>
</properties>
</project>
Ausführung
Build
$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Print
[INFO] task-segment: [install]
[INFO] ------------------------------------------------------------------------
Downloading: http://repo1.maven.org/maven2/net/java/dev/jna/jna/3.0.5/jna-3.0.5.pom
Downloading: http://repo1.maven.org/maven2/net/java/dev/jna/jna/3.0.5/jna-3.0.5.pom
[INFO] [buildnumber:create {execution: default}]
[INFO] Storing buildNumber: 20091030 at timestamp: 1256918122611
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 2 source files to /home/dedeibel/proggn/java/test/mvn-example/Print/target/classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Compiling 1 source file to /home/dedeibel/proggn/java/test/mvn-example/Print/target/test-classes
[INFO] [surefire:test]
[INFO] Surefire report directory: /home/dedeibel/proggn/java/test/mvn-example/Print/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running LoadFileTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.031 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] [jar:jar]
[INFO] Building jar: /home/dedeibel/proggn/java/test/mvn-example/Print/target/Print-20091030.jar
[INFO] [exec:exec {execution: default}]
[INFO] [install:install]
[INFO] Installing /home/dedeibel/proggn/java/test/mvn-example/Print/target/Print-20091030.jar to /home/dedeibel/.m2/repository/de/benjaminpeter/print/Print/0.0.1/Print-0.0.1.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7 seconds
[INFO] Finished at: Fri Oct 30 16:55:26 CET 2009
[INFO] Final Memory: 31M/290M
[INFO] ------------------------------------------------------------------------
Programm
$ java -jar target/Print-20091030.jar Hello. Content: A: Build 20091030 If the writeList method doesn't catch the checked exceptions that can occur within it, the writeList method must specify that it can throw these exceptions. Let's modify the B: original writeList method to specify the exceptions it can throw instead of catching them. To remind you, here's the original version of the writeList method that won't compile.
Fazit
Den direkten Vergleich zwischen ant und maven fand ich sehr interessant. Im kleinen Beispiel kann man die Stärken von maven jedoch nur erahnen. So hätte es bei ant wieder einer extra Einbindung der Unit-Tests bedurft, die aber im Grunde für jedes Projekt gleich ist wenn man sich an die Konventionen hält. Weiterhin musste kein Finger krum gemacht werden um eine aktuelle Version der junit Abhängigkeit zu bekommen; ein großer Vorteil bei einem Umzug auf ein anderes System, sofort werden die benötigten Abhängigkeiten ohne Aufwand nachgezogen.
Der Teufel steckt im Detail, während das Anpassen des Dateinamens und Ausführen des merge Skripts bei ant ein Kinderspiel sind, da man hier direkt auf die Abhängigkeiten und jar Erstellung Einfluss hat, scheint der Weg bei Maven für alles ein Plugin installieren zu müssen doch etwas umständlich. Letztendlich ist es aber eine Gute Sache, da es Plugins für die Unterschiedlichsten Aufgaben gibt und diese einem wie maven selbst möglichst viel Arbeit abnehmen. Allgemein sollte man sich vielleicht weniger mit Details aufhalten und gerade bei Verwendung von Frameworks Kompromisse eingehen und sich an die Gegebenheiten gewöhnen. Bei großen Projekten ist es auch so schwer genug die Übersicht zu behalten.
Quellen
Apache maven
Maven getting started
Die Vernachlässigung von tollen Namespaces für meine Klassen sie mir verziehen.
[...] Minutes Linux, Code, Leben « QBasic on Linux Apache Maven Beispiel » Apache Ant Beispiel 30. Oktober 2009 Apache ant ist ein build Tool, hauptsächlich [...]