Apache Maven Beispiel

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.

Am Anfang ist hier der getting started Guide von maven ein guter Anhaltspunkt http://maven.apache.org/guides/getting-started/index.html.

Mit folgendem Befehl legt man ein neues, leeres Projekt an. Weitere Infos werden anschließend in der pom.xml konfiguriert, dessen finale Version ich weiter unten aufgelistet und mit Kommentaren versehen habe.

mvn archetype:create \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DgroupId=de.benjaminpeter \
-DartifactId=print

Code

Verzeichnisstruktur

Die Grundstruktur von Maven wurde mit dem create Befehl angelegt und ist dabei allgemein wie folgt:

pom.xml
src/main
src/resources
src/test
src/test/resources

Im konkreten Beispiel mit allen Klassen und Ressourcen:

./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.

One Response to Apache Maven Beispiel

  1. Pingback: Apache Ant Beispiel « My Minutes

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: