Maven 2

+ andere TechDocs
+ maven.apache.org
+


Maven ist (ähnlich wie Ant) ein leistungsfähiges Werkzeug, um viele in der Softwareentwicklung immer wieder anfallende Prozeduren zu automatisieren und zu vereinfachen. Es wird manchmal als "Build Management System" bezeichnet und ist Teil vom "Software Configuration Management (SCM)".

Während Ant eher kommandoorientiert arbeitet, ist Maven eher strategisch orientiert, realisiert mehr Abstraktionen, wird deklarativer gesteuert, berücksichtigt Abhängigkeiten besser und ist besonders für aufwändigere Multiprojekte geeignet.

Im Folgenden wird nur auf Maven 2.x eingegangen. Informationen zum Vorgänger Maven 1 finden Sie hier.



Inhalt

  1. Vergleich mit Ant
  2. Einige wichtige Begriffe zu Maven
  3. Installation von Maven
  4. Maven-Hello-World-Projekt
  5. Maven-Webapp-Projekt mit Jetty
  6. Maven-Properties-Projekt mit Ressourcen-Filterung
  7. Erweiterung um Maven-Profile
  8. Java-Codegenerierung aus einem XSD-Schema mit JAXB
  9. SOAP-Webservice mit JAX-WS
  10. Ausführbare Jar-Datei inklusive Abhängigkeiten mit dem Assembly Plugin
  11. Einfaches Multiprojekt
  12. Multiprojekt mit Plugin-Management und Dependency-Management
  13. Multiprojekt mit Corporate POM
  14. Site Report um Project Reports erweitern
  15. Eigenes Maven-Plugin (Mojo)
    Mojo mit Parameter, Mojo-PluginContext, Mojo-JUnit-Test
  16. Parallelisierte Testausführung mit TestNG
  17. Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit
  18. Automatisierter Integrationstest mit Jetty und JWebUnit
  19. Automatisierter Integrationstest mit Fit
  20. Automatisierter HTML-Akzeptanztest mit Jetty und Fit
  21. Webapp mit Wicket
  22. Automatisierter Integrationstest mit Cargo für JBoss, WebLogic und Tomcat
  23. Java-EE-Anwendungen (Servlet, JSP, JSF, JPA, EJB3)
  24. OSGi-Bundle mit dem Maven-Bundle-Plugin
  25. Maven-Repository


Vergleich mit Ant

Sowohl Ant als auch Maven sind leistungsfähige Werkzeuge, um viele in der Softwareentwicklung immer wieder anfallende Prozeduren zu automatisieren und zu vereinfachen. Beide haben ihre bevorzugten Einsatzbereiche.

Vorteile von Ant:

Vorteile von Maven:



Einige wichtige Begriffe zu Maven

Maven
Maven ist ein Tool für das Projektmanagement von Softwareentwicklungsprojekten. Dabei werden Patterns und Best Practices eingesetzt, um die Produktivität und Wiederverwendbarkeit zu erhöhen. Im Fokus stehen Builds, Dokumentation, Reporting, Abhängigkeiten, SCM (Software Configuration Management), Releases und Distribution. Siehe hierzu auch: What is Maven?
Doku zu Maven
Doku zu Maven finden Sie zum Beispiel unter:
http://maven.apache.org/guides
http://maven.apache.org/guides/getting-started
http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
http://maven.apache.org/plugins
http://maven.apache.org/apache-maven.pdf
maven-definitive-guide_de.pdf
BetterBuildsWithMaven.pdf
Buch: Martin Spiller, Maven 2
Buch: Kai Uwe Bachmann, Maven 2
Build Lifecycle
Maven beinhaltet als wichtiges Konzept genau definierte Build Lifecycles.
Die drei Standard-Lifecycle sind: clean, default und site.
Build Lifecycles bestehen aus Lifecycle-Phasen.
Lifecycle-Phasen
Build Lifecycles sind unterteilt in Phasen.
Wichtige Phasen sind beispielsweise: compile, test, package, integration-test, install und deploy.
Die Phasen werden in einer bestimmten Reihenfolge durchlaufen. Wird im Kommandozeilenfenster zum Beispiel das Kommando "mvn package" eingegeben, dann werden alle vorhergehenden Phasen (z.B. u.a. compile und test) und die angegebene Phase (hier package) ausgeführt, aber nicht die nachfolgenden Phasen.
In den Lifecycle-Phasen werden jeweils bestimmte Plugin-Goals ausgeführt.
Maven-Plugin
Maven-Plugins sind Bibliotheken, die thematisch zusammengehörende Goals implementieren.
Wichtige Plugins sind zum Beispiel: archetype, compiler, surefire, jar, war, install, deploy, site, dependency, eclipse und jetty.
Eine Liste vieler weiterer Maven-Plugins finden Sie hier.
Hinweise zur Plugin-Konfiguration finden Sie hier.
Goal
Goals sind von Maven-Plugins angebotene ausführbare Kommandos.
Goals können bestimmten Lifecycle-Phasen zugeordnet werden und werden dann automatisch zum richtigen Zeitpunkt aufgerufen (z.B. "compiler:compile").
Viele Goals können auch direkt über die Kommandozeile aufgerufen werden. Dabei wird getrennt durch einen Doppelpunkt (":") der Plugin-Name und der Goal-Name angegeben, zum Beispiel so: "mvn archetype:generate".
Maven-Goals sind vergleichbar mit Ant-Tasks.
Archetype und Standard Directory Layout
Ein Maven-Archetype ist ein Projekt-Template. Über das archetype-Plugin können Standard-Directory-Layouts und Projektvorlagen für verschiedene Projekttypen erstellt werden (z.B. quickstart, webapp und mojo). Dabei wird auch eine vorläufige pom.xml angelegt.
Artefakt
Mit Artefakt sind im Zusammenhang mit Maven Arbeitsergebnisse gemeint. Maven-Projekte haben in der Regel ein Hauptergebnis-Artefakt, oft eine jar-, war- oder ear-Datei. Aber auch andere Ergebnisse wie Projektdokumentationen können als Artefakt bezeichnet werden.
POM und pom.xml
POM bedeutet "Project Object Model" und wird repräsentiert durch eine XML-Datei, üblicherweise die pom.xml, die als zentrale Projektbeschreibungs- und -steuerungsdatei Meta-Daten zum Projekt enthält, unterteilt in die fünf Bereiche Koordinaten, Projektbeziehungen, Projektinformationen, Projekteinstellungen und Projektumgebung.
Wichtige Einträge sind zum Beispiel: groupId, artifactId, version, packaging, build, dependencies, profiles und properties.
Die möglichen Elemente sind hier aufgeführt.
Weitere Informationen finden Sie hier und hier.
Koordinaten
Mit "Koordinaten" werden die fünf ein Artefakt identifizierenden Informationsbestandteile bezeichnet. Oft sind vor allem die drei wichtigsten gemeint: groupId, artifactId und version. Zu den Koordinaten gehören aber auch der optionale classifier und der durch das packaging definierten type, da auch hierüber verschiedene Artefakte differenziert werden.
groupId
Die groupId ist eine Gruppierungsbezeichnung (ähnlich den Java-Package-Namen). Normalerweise wird der umgekehrte Domainname der Firma verwendet, eventuell ergänzt um den Abteilungs- und/oder Projektnamen, zum Beispiel "de.meinefirma.meinprojekt". Im Maven-Repository repräsentieren die durch Punkte (".") getrennte Bestandteile Unterverzeichnisse, ähnlich wie bei Package-Namen in Sourcecodeverzeichnissen.
artifactId
Die artifactId ist der Name des Hauptergebnis-Artefakts dieses Projekts. Der vollständige Dateiname des Hauptergebnis-Artefakts wird meistens gebildet aus: "<artifactId>-<version>.<type>", also zum Beispiel "junit-3.8.1.jar" oder "MeineApp-1.0-SNAPSHOT.jar". Falls ein classifier definiert ist, wird auch dieser noch dem Dateinamen hinzugefügt (hinter der Versionsbezeichnung), zum Beispiel so: "testng-5.9-jdk15.jar" und "MeineEjb-1.0-client.jar".
version
Die version des Projekts, typischerweise im Format: "<Major>.<Minor>.<Bugfix>-<Qualifier>-<Buildnumber>" (wobei die vorderen Teile bei Versionsvergleichen numerisch gewertet werden (1.10 ist aktueller als 1.9) und die hinteren Teile optional sind). Lautet der Qualifier "SNAPSHOT", bedeutet dies, dass sich das Projekt noch in Entwicklung befindet und die Versionsnummer nicht bei jedem Build hochgezählt wird (was bei Versionsvergleichen eine Rolle spielt).
Es können auch Versionsbereiche angegeben werden, beispielsweise:
4.7 bedeutet: Version 4.7,
[3.8.1,) bedeutet: alle Versionen ab mindestens 3.8.1,
[3.8.1,4.7) bedeutet: alle Versionen ab 3.8.1 bis ausschließlich 4.7,
[3.8.1,4.7] bedeutet: alle Versionen ab 3.8.1 bis einschließlich 4.7
classifier
Einige Ergebnis-Artefakte können in unterschiedlichen Ausführungen vorliegen, die im Dateinamen durch den classifier unterschieden werden. Zum Beispiel das Assembly Plugin erstellt ein zusätzliches durch einen classifier unterschiedenes Ergebnis-Artefakt (z.B. MeineApp-1.0-jar-with-dependencies.jar). TestNG gibt es für verschiedene Java-Versionen (z.B. testng-5.9-jdk15.jar). Oder beim Build eines EJB-Moduls wird eine MeineEjb-1.0.jar für das Deployment zum App-Server und per generateClient eine MeineEjb-1.0-client.jar für die Client-Applikation erstellt.
packaging
Das packaging definiert den Typ des Hauptergebnis-Artefakts, zum Beispiel jar, war, ear, pom oder maven-plugin.
profile
Ein profile definiert Voreinstellungen. Über Umgebungsbedingungen, Kommandozeilenoptionen oder andere Parameter können Profile aktiviert werden.
Siehe auch die Beispiele Erweiterung um Maven-Profile und Integrationstest mit Cargo sowie Introduction to Build Profiles.
property
Ein property definiert ein Name/Wert-Paar. Siehe auch das Beispiel Maven-Properties-Projekt.
dependency
Abhängigkeiten werden als dependency eingetragen (transitive Abhängigkeiten brauchen nicht eingetragen zu werden). Siehe auch Dependency Mechanism und das Beispiel zum Dependency-Management.
Die eingetragenen Artefakte werden beim Build im Repository benötigt.
Über das scope-Attribut wird gesteuert, für welche Buildprozessphase die Abhängigkeit benötigt wird.
scope
Bei dependency-Einträgen kann über das scope-Attribut eine Art Sichtbarkeit definiert werden, nämlich in welchen Phasen des Buildprozesses die Abhängigkeit benötigt wird. Nur in den spezifizierten Phasen werden die eingetragenen Module dem CLASSPATH hinzugefügt. Die wichtigsten Ausprägungen sind: compile (Compilierphase), provided (ähnlich wie compile, aber Artefakt wird zur Laufzeit von Laufzeitumgebung (z.B. JEE-Container) bereitgestellt), runtime (Laufzeit) und test (Tests).
Repository
Es wird unterschieden zwischen dem lokalen Maven-Repository und dem Remote-Repository.
Das lokale Maven-Repository dient hauptsächlich als Cache-Zwischenspeicher für das Remote-Repository und als Austauschverzeichnis für lokal installierte Artefakte.
Vom Remote-Repository (z.B. http://repo1.maven.org/maven2) werden benötigte und noch nicht im lokalen Repository vorhandene Artefakte geladen.
Es können auch zusätzliche Repositories definiert werden, wie das Beispiel zu JAXB2 zeigt.
Siehe auch Introduction to Repositories.
Site
Mit Site ist eine Art Website gemeint, also eine Zusammenstellung von Webseiten, die als Projektdokumentation dient.
Siehe auch das Beispiel Site Report um Project Reports erweitern.
Mojo
In Java programmierte Maven-Plugins bestehen aus Mojos. Ein Mojo ("Maven (plain) old Java Object") ist eine Java-Klasse die das Interface org.apache.maven.plugin.Mojo implementiert (oder org.apache.maven.plugin.AbstractMojo erweitert) und damit ein Plugin-Goal realisiert.
Siehe auch das Beispiel Eigenes Maven-Plugin sowie guide-java-plugin-development, maven-plugin-api und mojo-api-specification.
Eindeutigkeit
Maven stellt einen großen Fortschritt für die Eindeutigkeit und Reproduzierbarkeit von Build-Ergebnissen dar. Aber auch Maven hat Lücken: Wenn in der pom.xml für Plugins keine Versionsnummer angegeben wird, können bei verschiedenen Builds (anderer Zeitpunkt oder anderer Rechner) unterschiedliche Ergebisse entstehen, je nachdem, welche Plugin-Version sich entweder zufällig im jeweiligen lokalen Repository befindet oder in der jeweiligen Maven-Super-POM vordefiniert ist (bedenken Sie, dass in der Maven-Super-POM nur die wichtigsten Plugins vordefiniert sind).
mvn-Kommandos

Die Aufruf-Syntax der im Kommandozeilenfenster aufrufbaren mvn-Kommandos lautet:
"mvn [options] [<goal(s)>] [<phase(s)>]".
Es können also übergeben werden (sowohl einzeln als auch kombiniert):

Einige wichtige oder häufig benutzte Maven-Kommandos sind:

KommandoBedeutung
mvn -v Anzeige der Version von Maven
mvn -h Hilfe zu den Kommandozeilenoptionen von Maven
mvn help:help Hilfe zur aktuellen Build-Umgebung
mvn help:describe -Dplugin=... Hilfe zu bestimmten Plugins (und mit -Ddetail auch zu Goals)
mvn help:effective-settings Anzeige der aktuellen projektübergreifenden Settings-Einstellungen
mvn help:effective-pom Anzeige der aktuell resultierenden projektbezogenen POM
mvn help:active-profiles Anzeige der aktiven Profile (aber ohne geerbte Profile)
mvn help:all-profiles Anzeige aller Profile (aber ohne geerbte Profile)
mvn help:evaluate Auflösung von Maven-Ausdrücken, z.B. ${settings.localRepository}
mvn dependency:tree -Dverbose Übersicht zum Abhängigkeitsbaum; weitere Goals: analyze, build-classpath, copy-dependencies, go-offline
mvn clean Löschen aller erzeugten Artefakte und des target-Verzeichnisses
mvn compile Kompilierung
mvn test Ausführen der Komponententests (z.B. JUnit-Tests)
mvn package Build und Erzeugung der Ergebnis-Artefakte
mvn integration-test Ausführen der Integrationstests
mvn install Kopieren des Ergebnis-Artefakts ins lokale Maven-Repository
mvn deploy Kopieren des Ergebnis-Artefakts ins Remote Repository
mvn site Erzeugen der Projektdokumentation
mvn archetype:generate Projektstruktur erzeugen
mvn eclipse:eclipse Für Eclipse benötigte Projektbeschreibungsdateien erzeugen oder updaten
mvn jetty:run Startet den Jetty-Server und führt das Webapp-Projekt aus


Installation von Maven

Maven-Basisinstallation

  1. Installieren Sie ein aktuelles Java SE JDK.
  2. Downloaden Sie Maven 2.x von http://maven.apache.org (z.B. apache-maven-2.2.1-bin.zip).
  3. Entzippen Sie die Maven-.zip-Datei in ein beliebiges Verzeichnis, zum Beispiel nach C:\Java. Es entsteht ein Maven-Unterverzeichnis, beispielsweise C:\Java\apache-maven-2.2.1.
  4. Setzen Sie folgende Umgebungsvariablen (Environment-Variablen) (passen Sie die Pfadangaben an Ihre Java- und Maven-Verzeichnisse an) (unter Windows XP: 'WindowsTaste + PauseTaste' | 'Erweitert' | 'Umgebungsvariablen'; unter Windows Vista: 'WindowsTaste + PauseTaste' | 'Erweiterte Systemeinstellungen' | 'Erweitert' | 'Umgebungsvariablen'; unter Linux: siehe linux.htm#Umgebungsvariablen):

    Benutzervariablen setzen:
    JAVA_HOMEC:\Program Files\Java\jdk1.6
    M2_HOMEC:\Java\apache-maven-2.2.1
    MAVEN_OPTS-Xms256m -Xmx512m
    PATH%JAVA_HOME%\bin;%M2_HOME%\bin

    Bitte beachten Sie, dass Sie nicht die PATH-Systemvariable um die "%...%\bin"-Pfade erweitern, sondern eine PATH-Benutzervariable anlegen (sonst funktioniert die "%...%"-Syntax nicht).

  5. Jetzt müssen folgende Kommandos ein erfolgreiches Ergebnis liefern. Öffnen Sie (mit 'Windows-Taste' + 'R' und 'cmd') ein neues Kommandozeilenfenster (damit die Änderung der Umgebungsvariablen wirksam wird) und geben Sie ein:

    set

    set JAVA_HOME

    set M2_HOME

    path

    java -version

    javac -version

    javac -help

    mvn -v

    mvn -h

  6. Weitere Installationshinweise finden Sie hier.

Konfiguration

  1. Fehlende Bibliotheken und Plug-ins lädt Maven automatisch zum Beispiel von http://repo1.maven.org/maven2. Weitere Artefakte finden Sie zum Beispiel unter http://www.ibiblio.org/maven und https://maven-repository.dev.java.net/repository sowie über http://mavenrepository.com und http://repository.sonatype.org. Beachten Sie auch die Hinweise zu Sun JARs.

  2. Wenn Sie die Voreinstellung nicht ändern, wird als lokales Maven-Repository-Verzeichnis je nach Betriebssystem zum Beispiel C:\Users\%USERNAME%\.m2\repository, C:\Documents and Settings\%USERNAME%\.m2\repository, <user-home>/.m2/repository oder ~/.m2/repository verwendet.

  3. Für alle Projekte geltende Voreinstellungen (z.B. Zugangsdaten zu CVS- oder Subversion-Servern) werden in settings.xml-Dateien definiert:
    %M2_HOME%\conf\settings.xml für systemweite Einstellungen,
    %USERPROFILE%\.m2\settings.xml für benutzerspezifische Einstellungen.
    Projektabhängige Einstellungen befinden sich in den jeweiligen pom.xml-Dateien.

  4. Falls Sie spezielle Konfigurationen benötigen, wie zum Beispiel HTTP-Proxy-Server, Repository-Manager oder Server-Authentifizierung, lesen Sie bitte Configuring Maven, Settings Reference, Technical Settings Descriptor und Password Encryption.

Maven in Firmen

  1. In Firmen sind in der systemweiten settings.xml meistens zumindest Einstellungen zum localRepository und eines mirrors erforderlich:

    <settings>
      <localRepository>C:\Maven\m2\repository</localRepository>
      <mirrors>
        <mirror>
          <id>MeineMirrorId</id>
          <mirrorOf>central</mirrorOf>
          <url>http://firma.repository.server:port/repo/path</url>
        </mirror>
      </mirrors>
    </settings>
    

    Das localRepository-Verzeichnis sollte in Firmen nicht im <user-home>-Verzeichnis liegen, weil in Firmen häufig das <user-home>-Verzeichnis bei jedem PC-Herunterfahren gesichert und beim Starten wiederhergestellt wird, was unnötig Backup-Speicherplatz kosten und diese Vorgänge verlangsamen würde.

    Ein mirror-Server muss oft definiert werden, damit Artefakte nicht vom Internet, sondern von einem firmeninternen Server geladen werden, weil in Firmen eventuell restriktivere Sicherheitsvorschriften gelten oder weil vielleicht nur bestimmte Libs und Versionen zur Verwendung freigegeben sind.

    Sehen Sie sich die eingestellten Settings an über:

    mvn help:effective-settings

    Weiteres zur settings.xml und zum Umgang mit Firmen-Proxies und Repository-Manager finden Sie weiter unten unter Maven-Repository.

Maven mit Eclipse

  1. Wenn Sie Eclipse verwenden wollen, sollten Sie das lokale Maven-Repository-Verzeichnis über die Eclipse-Variable "M2_REPO" bekanntgeben. Dies geht am einfachsten über das Maven-Goal eclipse:configure-workspace über folgenden Kommandozeilenbefehl (bitte Eclipse-Workspace-Pfad "D:\MeinWorkspace" anpassen):

    mvn -Declipse.workspace=D:\MeinWorkspace eclipse:configure-workspace

    Alternativ können Sie in Eclipse über 'Window' | 'Preferences' | '[+] Java' | '[+] Build Path' | 'Classpath Variables' | 'New...' eingeben (bitte Pfad anpassen):

    Eclipse Classpath Variables:
    M2_REPOC:\Users\User\.m2\repository

    Anschließend müssen Sie Eclipse neu starten.

  2. Um in Eclipse ein Projektmodul aus CVS oder Subversion zu laden (oder upzudaten), verfahren Sie wie beschrieben unter CVS bzw. Subversion.
  3. Bei neuen Maven-Projekten oder nach Updates auf bestehenden Maven-Projekten müssen Sie als Erstes die Eclipse-Projektdateien erstellen bzw. aktualisieren:

    mvn eclipse:eclipse

    Falls Sie nachträglich Konfigurationsänderungen durchführen, kann folgendes Kommando "aufräumen":

    mvn eclipse:clean eclipse:eclipse

  4. Falls das Projekt noch nicht in Eclipse existiert: Importieren Sie es über: 'File' | 'Import...' | 'Existing Projects into Workspace' | 'Select root directory'.
  5. Wichtig: Nach dem "mvn eclipse:eclipse"-Kommando und auch nach jeder anderen Änderung, die Sie außerhalb von Eclipse vornehmen, müssen Sie in Eclipse im 'Package Explorer' die Projektmodule markieren und mit 'F5' einen 'Refresh' ausführen.

  6. Weiteres zu Eclipse mit Maven finden Sie hier.



Maven-Hello-World-Projekt

  1. Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace), erzeugen Sie mit dem Maven-archetype-Plugin eine einfache Standard-Directory-Struktur und sehen Sie sich diese an (das mvn-Kommando in einer Zeile):

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnHelloApp

    cd \MeinWorkspace\MvnHelloApp

    tree /F

    Sie erhalten:

    [D:\MeinWorkspace\MvnHelloApp]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [de]
     |   |           '- [meinefirma]
     |   |               '- [meinprojekt]
     |   |                   '- App.java
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- AppTest.java
     '- pom.xml
    
  2. Um den Build durchzuführen und das entstandene Programm auszuführen, geben Sie im Kommandozeilenfenster ein:

    cd \MeinWorkspace\MvnHelloApp

    mvn package

    java -cp target/MvnHelloApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    Sie erhalten:

    Hello World!

  3. Sehen Sie nach, was im target-Verzeichnis entstanden ist und was in der resultierenden Jar-Datei enthalten ist:

    cd \MeinWorkspace\MvnHelloApp

    tree /F

    jar tvf target/MvnHelloApp-1.0-SNAPSHOT.jar

  4. Fügen Sie in App.java etwas Sinnvolles ein und testen Sie dies in AppTest.java. Folgeaufrufe von "mvn package" gehen wesentlich schneller, weil jetzt die benötigten Maven-Plugins im lokalen Maven-Repository vorhanden sind.



Maven-Webapp-Projekt mit Jetty

  1. Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine Webapp-Projektstruktur (das mvn-Kommando in einer Zeile):

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJettyWebApp

    cd \MeinWorkspace\MvnJettyWebApp

    tree /F

    Sie erhalten:

    [D:\MeinWorkspace\MvnJettyWebApp]
     |- [src]
     |   '- [main]
     |       |- [resources]
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- index.jsp
     '- pom.xml
    
  2. Ersetzen Sie den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnJettyWebApp</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
      <name>MvnJettyWebApp Maven Webapp</name>
      <url>http://www.meinefirma.de</url>
      <build>
        <finalName>${artifactId}</finalName>
        <plugins>
          <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    
  3. Ausführung des Projekts:

    cd \MeinWorkspace\MvnJettyWebApp

    mvn jetty:run

    Warten bis "Started Jetty Server" erscheint und dann im Webbrowser aufrufen:

    http://localhost:8080/MvnJettyWebApp

  4. Beenden Sie Jetty mit "Strg + C".

  5. Sehen Sie sich die Hilfstexte zu dem genannten und anderen Goals des Jetty-Plugins an:

    mvn help:describe -Dplugin=org.mortbay.jetty:maven-jetty-plugin

    Sehen Sie sich Konfigurationsoptionen des Jetty-Plugins an:
    http://jetty.mortbay.org/jetty/maven-plugin/run-mojo.html.



Maven-Properties-Projekt mit Ressourcen-Filterung

Dieses Beispiel zeigt Zweierlei:

In diesem Programmierbeispiel werden die Property-Werte lediglich in der Java-Anwendung ausgelesen und angezeigt. Mit Property-Werten könnte aber auch der Build-Prozess gesteuert werden (z.B. über Profile).

  1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine neue Projektstruktur:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnPropApp

  2. Ersetzen Sie in MvnPropApp\src\main\java\de\meinefirma\meinprojekt den Inhalt von App.java durch:

    package de.meinefirma.meinprojekt;
    
    import java.io.IOException;
    import java.util.Properties;
    
    public class App
    {
       public static void main( String[] args )
       {
          System.out.println( (new App()).run() );
       }
    
       public String run()
       {
          Properties props = new Properties();
          try {
             // Properties einlesen:
             props.load( getClass().getResourceAsStream( "/filtered/MeineProps.properties" ) );
          } catch( IOException ex ) {
             throw new RuntimeException( ex.getCause() );
          }
          // Properties zu String wandeln:
          String s = props.toString();
          if( s.length() > 2 ) s = s.substring( 1, s.length() - 1 );
          return s.replace( ", ", "\n" );
       }
    }
    
  3. Ersetzen Sie in MvnPropApp\src\test\java\de\meinefirma\meinprojekt den Inhalt von AppTest.java durch:

    package de.meinefirma.meinprojekt;
    
    import java.io.IOException;
    import java.util.Properties;
    import junit.framework.TestCase;
    
    public class AppTest extends TestCase
    {
       public void testApp()
       {
          Properties propsApp = new Properties(), propsTst = new Properties();
          try {
             propsApp.load( getClass().getResourceAsStream( "/filtered/MeineProps.properties" ) );
             propsTst.load( getClass().getResourceAsStream( "/MeineTestProps.properties" ) );
          } catch( IOException ex ) {
             throw new RuntimeException( ex.getCause() );
          }
          assertEquals( "MeinPomProp vor Merge",  "PropAusPom",       propsApp.get( "MeinPomProp" ) );
          propsApp.putAll( propsTst );
          assertEquals( "MeinPomProp nach Merge", "PropAusTestProps", propsApp.get( "MeinPomProp" ) );
       }
    }
    

    Bitte beachten Sie, dass Sie im Test sowohl auf die Ressourcen unter src\main\resources als auch unter src\test\resources zugreifen können.
    Wenn Sie wie im Beispiel gezeigt einen Merge durchführen, können Sie Properties für den Testfall überschreiben.

  4. Ersetzen Sie den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnPropApp</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>MvnPropApp</name>
      <url>http://www.meinefirma.de</url>
      <build>
        <filters>
          <filter>src/main/filters/filter.properties</filter>
        </filters>
        <resources>
          <resource>
            <filtering>true</filtering>
            <directory>src/main/resources</directory>
            <includes>
              <include>**/filtered/*</include>
            </includes>
          </resource>
          <resource>
            <filtering>false</filtering>
            <directory>src/main/resources</directory>
            <excludes>
              <exclude>**/filtered/*</exclude>
            </excludes>
          </resource>
        </resources>
      </build>
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.7</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
      <properties>
        <MeinProp>PropAusPom</MeinProp>
        <MeinPomProp>PropAusPom</MeinPomProp>
      </properties>
    </project>
    

    Beachten Sie bitte den <build>- und den <properties>-Block:
    Im <properties>-Block werden zwei Property-Werte definiert.
    Im <build>-Block wird eine zusätzlich zu berücksichtigende Filter-Datei definiert (<filter>src/main/filters/filter.properties) und es wird Filterung aktiviert (<filtering>true), aber nicht für alle Ressourcen, sondern nur für die im filtered-Unterverzeichnis. Wählen Sie für Filterung besser nicht das gesamte Ressourcen-Verzeichnis: Vielleicht wollen Sie später Dateien hinzufügen, die nicht gefiltert werden dürfen (z.B. Bilddateien).

  5. Erzeugen Sie unterhalb von MvnPropApp\src\main ein Unterverzeichnis mit dem Namen filters.
    Erzeugen Sie in diesem Unterverzeichnis die Datei filter.properties mit folgendem Inhalt:

    MeinProp=PropAusFilter
    MeinFilterProp=PropAusFilter
    
  6. Erzeugen Sie unterhalb von MvnPropApp\src\main ein Unterverzeichnis mit dem Namen resources und darunter eines mit dem Namen filtered.
    Erzeugen Sie in letzterem die Datei MeineProps.properties mit folgendem Inhalt:

    MeinPropsProp=PropAusMeineProps
    MeinCmdProp=${MeinCmdProp}
    MeinPomProp=${MeinPomProp}
    MeinFilterProp=${MeinFilterProp}
    MeinProp=${MeinProp}
    pom.name=${pom.name}
    pom.version=${pom.version}
    pom.build.finalName=${pom.build.finalName}
    pom.url=${pom.url}
    settings.localRepository=${settings.localRepository}
    basedir=${basedir}
    os.arch=${os.arch}
    os.name=${os.name}
    java.version=${java.version}
    user.home=${user.home}
    
  7. Erzeugen Sie unterhalb von MvnPropApp\src\test ein Unterverzeichnis mit dem Namen resources und erzeugen Sie darin die Datei MeineTestProps.properties mit folgendem Inhalt:

    MeinPomProp=PropAusTestProps
    
  8. Lassen Sie sich die Verzeichnisstruktur anzeigen:

    cd \MeinWorkspace\MvnPropApp

    tree /F

    -->

    [D:\MeinWorkspace\MvnPropApp]
     |- [src]
     |   |- [main]
     |   |   |- [filters]
     |   |   |   '- filter.properties
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               '- App.java
     |   |   '- [resources]
     |   |       '- [filtered]
     |   |           '- MeineProps.properties
     |   '- [test]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- AppTest.java
     |       '- [resources]
     |           '- MeineTestProps.properties
     '- pom.xml
    
  9. Um den Build durchzuführen und das entstandene Programm inklusive eines Kommandozeilenparameters ("-D...") auszuführen, geben Sie im Kommandozeilenfenster ein:

    cd \MeinWorkspace\MvnPropApp

    mvn "-DMeinCmdProp=PropVonCmd" clean package

    java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    Sie erhalten eine Ausgabe ähnlich zu:

    MeinPropsProp=PropAusMeineProps
    MeinCmdProp=PropVonCmd
    MeinPomProp=PropAusPom
    MeinFilterProp=PropAusFilter
    MeinProp=PropAusPom
    pom.name=MvnPropApp
    pom.version=1.0-SNAPSHOT
    pom.build.finalName=MvnPropApp-1.0-SNAPSHOT
    pom.url=http://www.meinefirma.de
    settings.localRepository=${settings.localRepository}
    basedir=D:\MeinWorkspace\MvnPropApp
    os.arch=x86
    os.name=Windows Vista
    java.version=1.6.0_16
    user.home=C:\Users\User
    

    Die Property-Werte stammen aus unterschiedlichen Quellen:

    Die Filterung von Variablen funktioniert nicht immer genau so wie man vermuten könnte: Im Beispiel wurde etwa ${settings.localRepository} nicht aufgelöst, weil dieser Wert in keiner settings.xml definiert war. Trotzdem ist dieser Wert natürlich verfügbar, wie Sie über "mvn help:evaluate" leicht ermitteln können.

  10. Um nur die JUnit-Tests auszuführen, geben Sie im Kommandozeilenfenster ein:

    cd \MeinWorkspace\MvnPropApp

    mvn clean test

    Falls es Probleme beim Test gibt, sehen Sie sich die Dateien im MvnPropApp\target\surefire-reports-Verzeichnis an:

    start target\surefire-reports\de.meinefirma.meinprojekt.AppTest.txt

    start target\surefire-reports\TEST-de.meinefirma.meinprojekt.AppTest.xml

    Die XML-Datei enthält auch relevante Environment-Variablen und Konfigurationen
    (z.B. <property name="localRepository" value="C:\Users\User\.m2\repository"/>).

  11. Sehen Sie nach, was in der resultierenden Jar-Datei enthalten ist:

    cd \MeinWorkspace\MvnPropApp

    mvn "-DMeinCmdProp=PropVonCmd" package

    jar tvf target/MvnPropApp-1.0-SNAPSHOT.jar

    -->

    de/meinefirma/meinprojekt/App.class
    filtered/MeineProps.properties
    META-INF/MANIFEST.MF
    META-INF/maven/de.meinefirma.meinprojekt/MvnPropApp/pom.properties
    META-INF/maven/de.meinefirma.meinprojekt/MvnPropApp/pom.xml
    

    Bitte beachten Sie: Die filter.properties-Datei wurde zwar beim Filtern berücksichtigt (${MeinFilterProp} wurde umgewandelt in PropAusFilter), aber sie wird nicht dem Ergebnis-Artefakt hinzugefügt.



Erweiterung um Maven-Profile

Ein profile definiert Voreinstellungen. Über Umgebungsbedingungen, Kommandozeilenoptionen oder andere Parameter können Profile aktiviert werden. Eine Einführung finden Sie unter Introduction to Build Profiles. Ein anderes Profile verwendendes Beispiel finden Sie unter Integrationstest mit Cargo.

Außer in der pom.xml können Profile auch in einer settings.xml und/oder einer profiles.xml definiert werden. Dort definierte Profile beeinflussen hauptsächlich:

Am meisten Einflussmöglichkeiten gibt es, wenn das Profil in der pom.xml definiert wird. Dann sind beispielsweise zusätzlich beeinflussbar:

Die Auswahl und Aktivierung (activation) eines (oder mehrerer) Profile kann über verschiedene Auslöser erfolgen:

Normalerweise werden die verschiedenen Profile eines Projekts eher in nur einer der möglichen Dateien gespeichert. Nur um verschiedene Möglichkeiten zu demonstrieren, plaziert das folgende Beispiel seine zwei Profile in zwei unterschiedlichen Dateien (profiles.xml und pom.xml).

Von den vielen Möglichkeiten kann das folgende Beispiel nur wenig demonstrieren: Die Profile-Aktivierung erfolgt über -P-Kommandos oder alternativ über Properties, die per Umgebungsvariable oder Kommandozeilenparameter gesetzt werden. Und als Effekt werden lediglich die eingestellten Properties ausgegeben.

Damit die steuernde Property auch per Umgebungsvariable gesetzt werden kann, muss sie mit einem vorangestellten "env." beginnen und ansonsten in Großbuchstaben geschrieben sein (im Beispiel lautet sie: "env.UMGEBUNG"). Als Umgebungsvariable wird sie ohne das "env." gesetzt (siehe Beispiel unten).

  1. Voraussetzung ist, dass Sie das obige Beispiel Maven-Properties-Projekt (im Folgenden "MvnPropApp" genannt) erfolgreich durchgeführt haben.

  2. Erzeugen Sie im MvnPropApp-Projektverzeichnis die Datei profiles.xml mit folgendem Inhalt:

    <profilesXml>
      <profiles>
        <profile>
          <id>Entw</id>
          <activation>
            <property>
              <name>env.UMGEBUNG</name>
              <value>Entwicklung</value>
            </property>
          </activation>
          <properties>
            <appsrv.url>meine.Url.zum.lokalen.Appserver</appsrv.url>
            <appsrv.usr>mein lokaler AppServer-User</appsrv.usr>
            <appsrv.pwd>geheim</appsrv.pwd>
            <db.url>meine.Url.zur.lokalen.Datenbank</db.url>
            <db.usr>mein lokaler DB-User</db.usr>
            <db.pwd>supergeheim</db.pwd>
          </properties>
        </profile>
      </profiles>
    </profilesXml>
    
  3. Ergänzen Sie im MvnPropApp-Projektverzeichnis in der Datei pom.xml zwischen </properties> und </project> Folgendes:

      <profiles>
        <profile>
          <id>IntTest</id>
          <activation>
            <property>
              <name>env.UMGEBUNG</name>
              <value>Integrationstest</value>
            </property>
          </activation>
          <properties>
            <appsrv.url>Url.zum.IntTest.Appserver</appsrv.url>
            <appsrv.usr>IntTest-AppServer-User</appsrv.usr>
            <appsrv.pwd>geheim</appsrv.pwd>
            <db.url>Url.zur.IntTest.Datenbank</db.url>
            <db.usr>IntTest-DB-User</db.usr>
            <db.pwd>supergeheim</db.pwd>
          </properties>
        </profile>
      </profiles>
    
  4. Ergänzen Sie in der Properties-Datei MvnPropApp\src\main\resources\filtered\MeineProps.properties Folgendes:

    env.UMGEBUNG=${env.UMGEBUNG}
    appsrv.url=${appsrv.url}
    appsrv.usr=${appsrv.usr}
    appsrv.pwd=${appsrv.pwd}
    db.url=${db.url}
    db.usr=${db.usr}
    db.pwd=${db.pwd}
    
  5. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in das MvnPropApp-Projektverzeichnis und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace\MvnPropApp

    mvn clean package

    java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    --> Die appsrv...- und db...-Properties sind nicht gesetzt.

    set UMGEBUNG=Entwicklung

    mvn clean package

    java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    --> Die appsrv...- und db...-Properties sind für "Entwicklung" gesetzt.

    mvn "-Denv.UMGEBUNG=Integrationstest" clean package

    java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    --> Die appsrv...- und db...-Properties sind für "Integrationstest" gesetzt
          (die set-Umgebungsvariable wird durch den Kommandozeilenparameter überschrieben).

    Sie erhalten eine Ausgabe wie oben gezeigt, aber je nach gesetztem env.UMGEBUNG-Property-Wert ergänzt um beispielsweise:

    env.UMGEBUNG=Integrationstest
    appsrv.url=Url.zum.IntTest.Appserver
    appsrv.usr=IntTest-AppServer-User
    appsrv.pwd=geheim
    db.url=Url.zur.IntTest.Datenbank
    db.usr=IntTest-DB-User
    db.pwd=supergeheim
    
  6. Alternativ können Sie die verschiedenen Profile auch über die -P-Option aktivieren:

    mvn -P Entw clean package

    java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    --> Die appsrv...- und db...-Properties sind für "Entwicklung" gesetzt.

    mvn -P IntTest clean package

    java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    --> Die appsrv...- und db...-Properties sind für "Integrationstest" gesetzt
          (die set-Umgebungsvariable wird überschrieben).

  7. Falls Sie viele Profile haben und die Übersicht verlieren, können Sie sich mit dem Maven Help Plugin die aktivierten anzeigen lassen:

    mvn help:active-profiles

    The following profiles are active:
     - Entw (source: profiles.xml)
    

    mvn help:active-profiles "-Denv.UMGEBUNG=Integrationstest"

    The following profiles are active:
     - IntTest (source: pom)
    

    Das Maven Help Plugin bietet noch weitere hilfreiche Kommandos, zum Beispiel:

    mvn help:effective-pom "-Denv.UMGEBUNG=Entwicklung"

    mvn help:effective-pom "-Denv.UMGEBUNG=Integrationstest"



Java-Codegenerierung aus einem XSD-Schema mit JAXB

  1. Das folgende Beispiel demonstriert Verschiedenes:

  2. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine neue Projektstruktur:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJaxbApp

    cd \MeinWorkspace\MvnJaxbApp

    tree /F

  3. Ersetzen Sie den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnJaxbApp</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>MvnJaxbApp</name>
      <build>
        <plugins>
          <plugin>
            <groupId>org.jvnet.jaxb2.maven2</groupId>
            <artifactId>maven-jaxb2-plugin</artifactId>
            <version>0.7.1</version>
            <executions>
              <execution>
                <goals>
                  <goal>generate</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>1.5</source>
              <target>1.5</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>com.sun.xml.bind</groupId>
          <artifactId>jaxb-impl</artifactId>
          <version>2.1.12</version>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.7</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
      <repositories>
        <repository>
          <id>maven2-repository.dev.java.net</id>
          <name>Java.net Maven 2 Repository</name>
          <url>http://download.java.net/maven/2</url>
        </repository>
        <repository>
          <id>maven-repository.dev.java.net</id>
          <name>Java.net Maven 1 Repository (legacy)</name>
          <url>http://download.java.net/maven/1</url>
          <layout>legacy</layout>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>maven2-repository.dev.java.net</id>
          <url>http://download.java.net/maven/2</url>
        </pluginRepository>
        <pluginRepository>
          <id>maven-repository.dev.java.net</id>
          <url>http://download.java.net/maven/1</url>
          <layout>legacy</layout>
        </pluginRepository>
      </pluginRepositories>
    </project>
    
  4. Ersetzen Sie den Inhalt von MvnJaxbApp\src\test\java\de\meinefirma\meinprojekt\AppTest.java durch:

    package de.meinefirma.meinprojekt;
    
    import generated.Buch;
    import generated.Buecherliste;
    import java.math.BigDecimal;
    import org.junit.Test;
    import static org.junit.Assert.assertEquals;
    
    public class AppTest
    {
       @Test public void testApp()
       {
          Buch bu = new Buch();
          bu.setTitel( "Mein Java-Buch" );
          bu.setJahr( 2009 );
          bu.setPreis( new BigDecimal( "47.11" ) );
          bu.setKommentar( "Super!" );
    
          Buecherliste bl = new Buecherliste();
          bl.setKategorie( "Java-Bücher" );
          bl.getListe().add( bu );
    
          assertEquals( "Buecherliste", 1, bl.getListe().size() );
       }
    }
    
  5. Erzeugen Sie unterhalb von MvnJaxbApp\src\main ein Unterverzeichnis mit dem Namen resources und erzeugen Sie darin die XSD-Datei buecherliste.xsd mit folgendem Inhalt:

    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    
      <xsd:complexType name="Buecherliste">
        <xsd:sequence>
          <xsd:element name="kategorie" type="xsd:string" />
          <xsd:element name="liste"     type="Buch" maxOccurs="unbounded" />
        </xsd:sequence>
      </xsd:complexType>
    
      <xsd:complexType name="Buch">
        <xsd:sequence>
          <xsd:element name="titel"     type="xsd:string"  />
          <xsd:element name="jahr"      type="xsd:int"     />
          <xsd:element name="preis"     type="xsd:decimal" />
          <xsd:element name="kommentar" type="xsd:string"  />
        </xsd:sequence>
        <xsd:attribute name="id"        type="xsd:long"    />
      </xsd:complexType>
    
    </xsd:schema>
    
  6. Sehen Sie sich mit tree /F die Verzeichnisstruktur an:

    [D:\MeinWorkspace\MvnJaxbApp]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               '- App.java
     |   |   '- [resources]
     |   |       '- buecherliste.xsd
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- AppTest.java
     '- pom.xml
    
  7. Bauen Sie das Projekt und sehen Sie sich das Ergebnis im target-Verzeichnis an:

    cd \MeinWorkspace\MvnJaxbApp

    mvn package

    tree /F

    Sie erhalten:

    [D:\MeinWorkspace\MvnJaxbApp]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               '- App.java
     |   |   '- [resources]
     |   |       '- buecherliste.xsd
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- AppTest.java
     |- [target]
     |   |- ...
     |   |- [generated-sources]
     |   |   '- [xjc]
     |   |       |- [generated]
     |   |       |   |- Buch.java
     |   |       |   |- Buecherliste.java
     |   |       |   '- ObjectFactory.java
     |   |       '- ...        
     |   '- ...
     '- pom.xml
    

    Aus der XSD-Schema-Definition buecherliste.xsd im src\main\resources-Verzeichnis wurden drei Java-Klassen im target\generated-sources\xjc\generated-Verzeichnis generiert: Buch.java, Buecherliste.java und ObjectFactory.java. Sehen Sie sich diese drei Klassen an und beachten Sie, wie in der Buecherliste die Liste deklariert ist (über maxOccurs="unbounded").
    Wie Sie diese Klassen ganz normal in Ihren Programmen verwenden können, zeigt der JUnit-Test in AppTest.java.



SOAP-Webservice mit JAX-WS

Das folgende Beispiel zeigt die Implementierung eines SOAP-Webservices mit JAX-WS inklusive Server und Client, allerdings nur in einer Quick-and-Dirty-Minimalversion.

Wenn Sie mehr Features von JAX-WS nutzen wollen, sollten Sie sich das JAX-WS Maven-Plugin ansehen.

Wenn Sie die Webservice-Kommunikation beobachten wollen, empfiehlt sich soapUI.

  1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine neue Projektstruktur:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJaxWs

    cd \MeinWorkspace\MvnJaxWs

  2. Ersetzen Sie den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnJaxWs</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>MvnJaxWs</name>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>1.6</source>
              <target>1.6</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>javax.xml.ws</groupId>
          <artifactId>jaxws-api</artifactId>
          <version>2.1</version>
        </dependency>
      </dependencies>
    </project>
    

    Seit Java SE 6 können Sie den <dependency>...jaxws-api...-Abschnitt auch weglassen.

  3. Löschen Sie im Verzeichnis MvnJaxWs\src das gesamte test-Unterverzeichnis.

  4. Löschen Sie im Verzeichnis MvnJaxWs\src\main\java\de\meinefirma\meinprojekt die App.java.

  5. Legen Sie im Verzeichnis MvnJaxWs\src\main\java\de\meinefirma\meinprojekt die folgenden vier Java-Dateien an.

    HalloWelt.java:

    package de.meinefirma.meinprojekt;
    
    import javax.jws.*;
    
    @WebService
    public interface HalloWelt
    {
       public String hallo( @WebParam( name = "wer" ) String wer );
    }
    

    HalloWeltImpl.java:

    package de.meinefirma.meinprojekt;
    
    import javax.jws.WebService;
    
    @WebService( endpointInterface="de.meinefirma.meinprojekt.HalloWelt" )
    public class HalloWeltImpl implements HalloWelt
    {
       public String hallo( String wer )
       {
          return "Hallo " + wer;
       }
    }
    

    TestServer.java:

    package de.meinefirma.meinprojekt;
    
    import javax.xml.ws.Endpoint;
    
    public class TestServer
    {
       public static void main( final String[] args )
       {
          String url = ( args.length > 0 ) ? args[0] : "http://localhost:8080/test";
          Endpoint.publish( url, new HalloWeltImpl() );
       }
    }
    

    TestClient.java:

    package de.meinefirma.meinprojekt;
    
    import java.net.URL;
    import javax.xml.namespace.QName;
    import javax.xml.ws.Service;
    
    public class TestClient
    {
       public static void main( final String[] args ) throws Throwable
       {
          String url = ( args.length > 0 ) ? args[0] : "http://localhost:8080/test";
          Service service = Service.create(
                new URL( url + "?wsdl" ),
                new QName( "http://meinprojekt.meinefirma.de/", "HalloWeltImplService" ) );
          HalloWelt halloWelt = service.getPort( HalloWelt.class );
          System.out.println( "\n" + halloWelt.hallo( args.length > 1 ? args[1] : "" ) );
       }
    }
    
  6. Die simplifizierte Projektstruktur sieht so aus:

    cd \MeinWorkspace\MvnJaxWs

    tree /F

    [D:\MeinWorkspace\MvnJaxWs]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       |- HalloWelt.java
     |                       |- HalloWeltImpl.java
     |                       |- TestClient.java
     |                       '- TestServer.java
     '- pom.xml
    
  7. Öffnen Sie ein Kommandozeilenfenster, bauen Sie das Projekt und starten Sie den Server mit dem Webservice:

    cd \MeinWorkspace\MvnJaxWs

    mvn clean package

    java -cp target/MvnJaxWs-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.TestServer http://localhost:8080/test

  8. Öffnen Sie ein zweites Kommandozeilenfenster und starten Sie den Webservice-Client:

    cd \MeinWorkspace\MvnJaxWs

    java -cp target/MvnJaxWs-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.TestClient http://localhost:8080/test ich

    Sie erhalten:

    Hallo ich
    
  9. Sehen Sie sich die generierte WSDL-Datei über http://localhost:8080/test?wsdl und die generierte Schema-XSD-Datei über http://localhost:8080/test?xsd=1 an.

  10. Sie können auch andere URLs beim TestServer- und TestClient-Aufruf übergeben, beispielsweise http://localhost:4711/xyz.

  11. Beenden Sie den Webservice-Server mit "Strg + C".

  12. Sehen Sie sich die Webservice-Kommunikation mit soapUI an (siehe soapUI-Screenshot).



Ausführbare Jar-Datei inklusive Abhängigkeiten mit dem Assembly Plugin

Im folgenden Beispiel besteht die Anwendung aus einer Klasse, die eine zusätzliche .jar-Bibliothek benötigt. Die kompilierte Klasse, die benötigte .jar-Lib und eine geeignete MANIFEST.MF werden mit dem Assembly Plugin in ein ausführbares Jar gepackt.

  1. Starten Sie ein neues Projekt:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJarMitLibs

    cd \MeinWorkspace\MvnJarMitLibs

  2. Ersetzen Sie im MvnJarMitLibs\src\main\java\de\meinefirma\meinprojekt-Verzeichnis den Inhalt von App.java durch:

    package de.meinefirma.meinprojekt;
    
    import org.apache.log4j.Logger;
    
    public class App
    {
       private static Logger logger = Logger.getRootLogger();
    
       public static void main( String[] args )
       {
          logger.info( "---- Hallo Logger! ----" );
       }
    }
    

    Die Anwendung verwendet den Log4j-Logger. Falls Sie Log4j nicht kennen: Sehen Sie sich java-log4j.htm an.

  3. Ersetzen Sie im MvnJarMitLibs\src\test\java\de\meinefirma\meinprojekt-Verzeichnis den Inhalt von AppTest.java durch:

    package de.meinefirma.meinprojekt;
    
    import junit.framework.TestCase;
    
    public class AppTest extends TestCase
    {
       public void testApp()
       {
          App.main( null );
       }
    }
    
  4. Erzeugen Sie im MvnJarMitLibs\src\main-Verzeichnis das Unterverzeichnis resources und darin folgende Log4j-Konfigurationsdatei log4j.properties:

    log4j.rootLogger=DEBUG, MeinConsoleAppender
    log4j.appender.MeinConsoleAppender=org.apache.log4j.ConsoleAppender
    log4j.appender.MeinConsoleAppender.layout=org.apache.log4j.PatternLayout
    log4j.appender.MeinConsoleAppender.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c: %m%n
    
  5. Die Abhängigkeit zur log4j-1.2.14.jar muss in die pom.xml eingetragen werden. Ersetzen Sie im MvnJarMitLibs-Projektverzeichnis den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnJarMitLibs</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>MvnJarMitLibs</name>
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <artifactId>maven-assembly-plugin</artifactId>
              <configuration>
                <descriptorRefs>
                  <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                  <manifest>
                    <mainClass>de.meinefirma.meinprojekt.App</mainClass>
                  </manifest>
                </archive>
              </configuration>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
      <dependencies>
        <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.14</version>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.7</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </project>
    

    Um die .jar-Ergebnisdatei inklusive benötigter Zusätze erstellen zu können, wird das maven-assembly-plugin verwendet:
    Da es nicht während des normalen Build-Vorgangs ausgeführt wird, sondern explizit, genügt eine Konfiguration in build.pluginManagement (statt build.plugins).
    <descriptorRef>jar-with-dependencies</descriptorRef> ist ein vordefinierter "prefabricated assembly descriptor" zur Integration von Abhängigkeiten, wie zum Beispiel .jar-Libs. Weitere vordefinierte Assembly-Descriptoren sind: bin, src und project. Alternativ können Sie auch einen selbst erstellten "custom assembly descriptor" verwenden, wie unter Configuration and Usage beschrieben ist.
    <mainClass>de.meinefirma.meinprojekt.App</mainClass> definiert die auszuführende Klasse, welche die main()-Methode enthält.

  6. Sehen Sie sich mit tree /F die Verzeichnisstruktur an:

    [D:\MeinWorkspace\MvnJarMitLibs]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               '- App.java
     |   |   '- [resources]
     |   |       '- log4j.properties
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- AppTest.java
     '- pom.xml
    
  7. Führen Sie den JUnit-Test aus:

    mvn test

    Sie erhalten:

    2009-09-15 11:22:33,444 INFO  [main] root: ---- Hallo Logger! ----
    

    Im Test ist die log4j-1.2.14.jar im Classpath und der Log4j-Logger funktioniert.

  8. Führen Sie den Build-Prozess aus, sehen Sie sich an, was in der erstellten Jar-Datei enthalten ist und versuchen Sie die Anwendung zu starten:

    mvn package

    jar tvf target/MvnJarMitLibs-1.0-SNAPSHOT.jar

    java -cp target/MvnJarMitLibs-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    Weil in der erstellten Jar-Datei die log4j-1.2.14.jar-Lib fehlt, erhalten Sie eine Exception:

    Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/...
    
  9. Um eine ausführbare Jar-Datei zu erhalten, sind zwei Erweiterungen notwendig:
    - Libs und andere Abhängigkeiten müssen in die Jar-Datei integriert werden.
    - Eine META-INF/MANIFEST.MF muss hinzugefügt werden inklusive eines Main-Class-Eintrags.
    Dies leistet das maven-assembly-plugin:

    mvn package

    mvn assembly:single

    dir target\MvnJarMitLibs*.jar

    jar tvf target\MvnJarMitLibs-1.0-SNAPSHOT-jar-with-dependencies.jar

    --> Die org/apache/log4j/...-Klassen sind enthalten.

    java -jar target\MvnJarMitLibs-1.0-SNAPSHOT-jar-with-dependencies.jar

    --> Die Jar-Datei kann ausgeführt werden und Sie erhalten das korrekte Ergebnis:

    2009-09-15 11:22:34,555 INFO  [main] root: ---- Hallo Logger! ----
    
  10. Das maven-assembly-plugin hat in einigen Versionen eine Besonderheit (z.B. in der Version 2.2-beta-2): Wenn Sie statt der beiden angegebenen hintereinander auszuführenden Kommandos "mvn package" und "mvn assembly:single" eines der beiden folgenden einzelnen Kommandos "mvn package assembly:single" oder "mvn assembly:assembly" probieren, werden Sie feststellen, dass sich einige Dateien doppelt in der Jar-Ergebnisdatei befinden (z.B. App.class und log4j.properties).

  11. Sehen Sie sich die Hilfstexte zu den Goals des Assembly-Plugins an:

    mvn help:describe -Dplugin=org.apache.maven.plugins:maven-assembly-plugin



Einfaches Multiprojekt

Größere Projekte bestehen aus vielen Teilprojekten. Um alle Teilprojekte gemeinsam kompilieren, bauen und verwalten zu können, werden sie zu Multiprojekten zusammengefasst. Dies wird im Folgenden demonstriert.

  1. Voraussetzung ist, dass Sie die beiden obigen Beispiele Maven-Webapp-Projekt (im Folgenden "MvnJettyWebApp" genannt) und Maven-Properties-Projekt (im Folgenden "MvnPropApp" genannt) (egal ob mit oder ohne Erweiterung um Maven-Profile) erfolgreich durchgeführt haben.
    (Alternativ können Sie auch das Multiprojekt downloaden.)

  2. Erstellen Sie für das neue Multiprojekt das neue Verzeichnis MvnMultiProj1 und kopieren Sie die beiden Projekte MvnPropApp und MvnJettyWebApp in das neue MvnMultiProj1-Verzeichnis:

    cd \MeinWorkspace

    md MvnMultiProj1

    cd MvnMultiProj1

    xcopy ..\MvnPropApp MvnPropApp\ /S

    xcopy ..\MvnJettyWebApp MvnJettyWebApp\ /S

    tree /F

    Wenn Sie die target-Verzeichnisse löschen, sieht das Ergebnis so aus:

    [D:\MeinWorkspace\MvnMultiProj1]
     |- [MvnJettyWebApp]
     |   |- [src]
     |   |   '- [main]
     |   |       |- [resources]
     |   |       '- [webapp]
     |   |           |- [WEB-INF]
     |   |           |   '- web.xml
     |   |           '- index.jsp
     |   '- pom.xml
     '- [MvnPropApp]
         |- [src]
         |   |- [main]
         |   |   |- [filters]
         |   |   |   '- filter.properties
         |   |   |- [java]
         |   |   |   '- [de]
         |   |   |       '- [meinefirma]
         |   |   |           '- [meinprojekt]
         |   |   |               '- App.java
         |   |   '- [resources]
         |   |       '- [filtered]
         |   |           '- MeineProps.properties
         |   '- [test]
         |       |- [java]
         |       |   '- [de]
         |       |       '- [meinefirma]
         |       |           '- [meinprojekt]
         |       |               '- AppTest.java
         |       '- [resources]
         |           '- MeineTestProps.properties
         |- pom.xml
         '- profiles.xml [nur bei Erweiterung um Maven-Profile]
    
  3. Wechseln Sie in das kopierte MvnPropApp-Verzeichnis und überprüfen Sie, dass das Projekt weiterhin funktioniert:

    cd \MeinWorkspace\MvnMultiProj1\MvnPropApp

    mvn "-DMeinCmdProp=PropVonCmd" clean package

    java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App

    Wechseln Sie in das kopierte MvnJettyWebApp-Verzeichnis und überprüfen Sie, dass auch dieses Projekt weiterhin funktioniert:

    cd \MeinWorkspace\MvnMultiProj1\MvnJettyWebApp

    mvn jetty:run

    start http://localhost:8080/MvnJettyWebApp

    Beenden Sie Jetty mit "Strg + C".

  4. Bislang sind die beiden Projekte MvnPropApp und MvnJettyWebApp unabhängig voneinander.
    Multiprojekte machen insbesondere bei voneinander abhängigen Projekten Sinn.
    Deshalb ändern wir MvnJettyWebApp so, dass es MvnPropApp benötigt.

    Ersetzen Sie im Verzeichnis MvnMultiProj1\MvnJettyWebApp\src\main\webapp den Inhalt der Datei index.jsp durch:

    <%@ page import="java.text.SimpleDateFormat" %>
    <%@ page import="java.util.Date" %>
    
    <html>
    <head><title>MvnMultiProj-JSP</title></head>
    <body>
    <h2>MvnMultiProj-JSP</h2>
    <%= request.getRemoteHost() %><br>
    <%= (new SimpleDateFormat("yyyy-MM-dd, HH:mm:ss")).format(new Date()) + " h" %><br><br>
    <%= (new de.meinefirma.meinprojekt.App()).run().replace( "\n", "<br>\n" ) %>
    </body>
    </html>
    
  5. Die Abhängigkeit zu MvnPropApp müssen Sie in der pom.xml von MvnJettyWebApp eintragen. Ergänzen Sie im MvnMultiProj1\MvnJettyWebApp-Verzeichnis die pom.xml um folgenden <dependencies>-Block:

      <dependencies>
        <dependency>
          <groupId>de.meinefirma.meinprojekt</groupId>
          <artifactId>MvnPropApp</artifactId>
          <version>1.0-SNAPSHOT</version>
        </dependency>
      </dependencies>
    
  6. Wenn Sie jetzt MvnJettyWebApp ausführen, erhalten Sie eine Fehlermeldung:

    cd \MeinWorkspace\MvnMultiProj1\MvnJettyWebApp

    mvn jetty:run

    -->

    Failed to resolve artifact.
    Missing:
    de.meinefirma.meinprojekt:MvnPropApp:jar:1.0-SNAPSHOT
    

    (Falls Sie die Fehlermeldung nicht erhalten, haben Sie wahrscheinlich das Kommando "mvn install" schon mal getestet. Bitte löschen Sie in diesem Fall den Inhalt des Verzeichnisses ${localRepository}\de\meinefirma\meinprojekt.)

  7. Wir könnten jetzt im MvnPropApp-Projekt das Kommando "mvn install" ausführen. Dann würde das MvnPropApp-.jar-Artefakt in das lokale Maven-Repository kopiert und es würde vom MvnJettyWebApp-Projekt gefunden werden.
    Diese Schritte müssten aber bei Änderungen jeweils wiederholt werden, was spätestens dann nicht mehr sinnvoll durchführbar und verwaltbar ist, wenn Sie nicht nur zwei, sondern viel mehr Teilprojekte haben.
    Deshalb erstellen wir ein übergeordnetes Multiprojekt.
    Erstellen Sie im übergeordneten MvnMultiProj1-Verzeichnis eine neue pom.xml, in der alle Teilprojekte eingetragen werden (im Beispiel MvnPropApp und MvnJettyWebApp):

    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnMultiProj1</artifactId>
      <version>1.0</version>
      <packaging>pom</packaging>
      <name>MvnMultiProj1</name>
      <modules>
        <module>MvnJettyWebApp</module>
        <module>MvnPropApp</module>
      </modules>
    </project>
    
  8. Die drei Projekte mit ihren jeweiligen pom.xml sind jetzt so angeordnet:

    [D:\MeinWorkspace\MvnMultiProj1]
     |- [MvnJettyWebApp]
     |   |- [src]
     |   |   '- ...
     |   '- pom.xml
     |- [MvnPropApp]
     |   |- [src]
     |   |   '- ...
     |   |- pom.xml
     |   '- profiles.xml [nur bei Erweiterung um Maven-Profile]
     '- pom.xml
    
  9. Jetzt können Sie mit folgenden Kommandos alle Teilprojekte (egal wie viele) kompilieren, alle Artefakte erzeugen und die Anwendung starten:

    cd \MeinWorkspace\MvnMultiProj1

    mvn install

    cd \MeinWorkspace\MvnMultiProj1\MvnJettyWebApp

    mvn jetty:run

    start http://localhost:8080/MvnJettyWebApp

    Die Webseite des MvnJettyWebApp-Projekts zeigt das Ergebnis des MvnPropApp-Projekts an.

  10. Falls Unterprojekte (wie MvnPropApp und MvnJettyWebApp) vom übergeordneten Basisprojekt (MvnMultiProj1) Einstellungen erben sollen, können Sie das übergeordnete Basisprojekt als <parent> in den pom.xml-Dateien der Unterprojekte eintragen:

      <parent>
        <groupId>de.meinefirma.meinprojekt</groupId>
        <artifactId>MvnMultiProj1</artifactId>
        <version>1.0</version>
      </parent>
    

    Dies wird im folgenden Beispiel demonstriert.



Multiprojekt mit Plugin-Management und Dependency-Management

Bei vielen Unterprojekten sollten gemeinsame Einstellungen (z.B. Java-Versionen und Versionen von Libs) möglichst nicht in jeder pom.xml jedes einzelnen Unterprojekts vorgenommen werden, sondern möglichst zentral erfolgen. Dies hat zwei Vorteile:
- Die pom.xml der einzelnen Unterprojekte sind kürzer und fehlerfreier.
- Änderungen können an einer Stelle für alle Unterprojekte durchgeführt werden.

Bitte beachten Sie, dass durch die im Folgenden vorgestellten <pluginManagement>- und <dependencyManagement>-Blöcke noch keine Abhängigkeiten erzeugt werden, sondern vorerst lediglich Deklarationen und Konfigurationen erfolgen (z.B. zur Versionsnummer, zum Scope und <configuration>-Einstellungen).

Ein noch etwas ausgefeilteres Multiprojektbeispiel finden Sie unter Java EE mit EJB3.

  1. Voraussetzung für das folgende Beispiel ist, dass Sie die Beispiele JAX-WS (im Folgenden "MvnJaxWs" genannt) und Einfaches Multiprojekt (im Folgenden "MvnMultiProj1" genannt) erfolgreich durchgeführt haben.
    (Alternativ können Sie auch das Multiprojekt downloaden.)

  2. Erzeugen Sie ein neues Projektverzeichnis MvnMultiProj2 und kopieren Sie dorthin die Projekte:

    cd \MeinWorkspace

    md MvnMultiProj2

    cd MvnMultiProj2

    xcopy ..\MvnMultiProj1\*.* /S

    xcopy ..\MvnJaxWs\*.* MvnJaxWs\ /S

  3. Das neue Projekt MvnMultiProj2 und die drei Unterprojekte sind jetzt so angeordnet:

    [D:\MeinWorkspace\MvnMultiProj2]
     |- [MvnJaxWs]
     |   |- [src]
     |   |   '- ...
     |   '- pom.xml
     |- [MvnJettyWebApp]
     |   |- [src]
     |   |   '- ...
     |   '- pom.xml
     |- [MvnPropApp]
     |   |- [src]
     |   |   '- ...
     |   '- pom.xml
     '- pom.xml
    
  4. Ersetzen Sie im übergeordneten MvnMultiProj2-Verzeichnis die pom.xml durch:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnMultiProj2</artifactId>
      <version>1.0</version>
      <packaging>pom</packaging>
      <name>MvnMultiProj2</name>
      <modules>
        <module>MvnJaxWs</module>
        <module>MvnJettyWebApp</module>
        <module>MvnPropApp</module>
      </modules>
    </project>
    
  5. Bauen Sie alle vier Projekte:

    cd \MeinWorkspace\MvnMultiProj2

    mvn clean install

    In der Reactor Summary wird zu allen vier Projekten SUCCESS gemeldet.

  6. Entfernen Sie im MvnJaxWs-Unterprojekt in der pom.xml den "<build>...</build>"-Block und rufen Sie im MvnMultiProj2-Verzeichnis erneut "mvn clean install" auf:
    Sie erhalten wie erwartet die Fehlermeldung:
    [ERROR] BUILD FAILURE
    Compilation failure
    ...\MvnJaxWs\...\HalloWeltImpl.java: annotations are not supported in -source 1.3

  7. Entfernen Sie im MvnPropApp-Unterprojekt in der pom.xml die beiden Zeilen "<version>4.7</version>" und "<scope>test</scope>" und rufen Sie im MvnMultiProj2-Verzeichnis erneut "mvn clean install" auf:
    Diesmal erhalten Sie wie erwartet die Fehlermeldung:
    [ERROR] FATAL ERROR
    Error building POM
    ...\MvnPropApp\pom.xml
    'dependencies.dependency.version' is missing for junit:junit:jar

  8. Ersetzen Sie im übergeordneten MvnMultiProj2-Verzeichnis die pom.xml durch:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnMultiProj2</artifactId>
      <version>1.0</version>
      <packaging>pom</packaging>
      <name>MvnMultiProj2</name>
      <modules>
        <module>MvnJaxWs</module>
        <module>MvnJettyWebApp</module>
        <module>MvnPropApp</module>
      </modules>
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                <source>1.6</source>
                <target>1.6</target>
              </configuration>
            </plugin>
          </plugins>
        </pluginManagement> 
      </build>
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
          </dependency>
        </dependencies>
      </dependencyManagement>
    </project>
    
  9. Fügen Sie im MvnJaxWs-Unterprojekt in der pom.xml folgendenmaßen einen "<parent>...</parent>"-Block hinzu (bei der Gelegenheit können Sie auch den <dependency>...jaxws-api...-Abschnitt entfernen):

    <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">
      <modelVersion>4.0.0</modelVersion>
      <parent>
        <groupId>de.meinefirma.meinprojekt</groupId>
        <artifactId>MvnMultiProj2</artifactId>
        <version>1.0</version>
      </parent>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnJaxWs</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>MvnJaxWs</name>
    </project>
    
  10. Fügen Sie im MvnPropApp-Unterprojekt ebenso in der pom.xml zwischen <modelVersion> und <groupId> den "<parent>...</parent>"-Block hinzu:

      <parent>
        <groupId>de.meinefirma.meinprojekt</groupId>
        <artifactId>MvnMultiProj2</artifactId>
        <version>1.0</version>
      </parent>
    
  11. Bauen Sie wieder alle vier Projekte:

    cd \MeinWorkspace\MvnMultiProj2

    mvn clean install

    In der Reactor Summary wird wieder zu allen vier Projekten SUCCESS gemeldet.



Multiprojekt mit Corporate POM

Im letzten Beispiel wurde gezeigt, wie Sie bei vielen Unterprojekten gemeinsame Einstellungen (z.B. Java-Versionen und Versionen von Libs) zentral in einem Parent-Multiprojekt verwalten.
Falls Sie mehrere Projekte (oder Multiprojekte) haben, sollten Sie noch einen Schritt weiter gehen, und auch die gemeinsamen Einstellungen der Projekte zentral verwalten, nämlich in einer "Corporate POM" (oder "Firmen-POM", "Master POM", "Top POM").

Bitte verwechseln Sie die hier gemeinte selbsterstellte übergeordnete Corporate POM nicht mit der von Maven immer als Grundlage verwendeten Super POM pom-4.0.0.xml, die Sie in der maven-2.2.1-uber.jar finden.

  1. Voraussetzung ist, dass Sie das Beispiel Multiprojekt mit Plugin-Management und Dependency-Management (im Folgenden "MvnMultiProj2" genannt) erfolgreich durchgeführt haben.
    (Alternativ können Sie auch das Multiprojekt downloaden.)

  2. Erzeugen Sie ein neues Projektverzeichnis MvnMultiProj3 und kopieren Sie dorthin die Projekte:

    cd \MeinWorkspace

    md MvnMultiProj3

    cd MvnMultiProj3

    xcopy ..\MvnMultiProj2\*.* /S

  3. Ersetzen Sie sowohl in der MvnJaxWs\pom.xml als auch in der MvnPropApp\pom.xml die Zeile

        <artifactId>MvnMultiProj2</artifactId>
    

    durch

        <artifactId>MvnMultiProj3</artifactId>
    
  4. Entfernen Sie aus der pom.xml des Multiprojekts MvnMultiProj3 die <build><pluginManagement>- und <dependencyManagement>-Blöcke und fügen Sie einen <parent>-Block ein:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <parent>
        <groupId>de.meinefirma.meinprojekt</groupId>
        <artifactId>MvnCorpPom</artifactId>
        <version>1.0</version>
      </parent>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnMultiProj3</artifactId>
      <version>1.0</version>
      <packaging>pom</packaging>
      <name>MvnMultiProj3</name>
      <modules>
        <module>MvnJaxWs</module>
        <module>MvnJettyWebApp</module>
        <module>MvnPropApp</module>
      </modules>
    </project>
    
  5. Legen Sie das neue Corporate-POM-Projekt MvnCorpPom an:

    cd \MeinWorkspace

    md MvnCorpPom

    cd MvnCorpPom

  6. Erstellen Sie im MvnCorpPom-Verzeichnis folgende pom.xml:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnCorpPom</artifactId>
      <version>1.0</version>
      <packaging>pom</packaging>
      <name>MvnCorpPom</name>
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                <source>1.6</source>
                <target>1.6</target>
              </configuration>
            </plugin>
          </plugins>
        </pluginManagement> 
      </build>
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
          </dependency>
        </dependencies>
      </dependencyManagement>
    </project>
    
  7. Bauen Sie die Corporate POM und installieren Sie sie im lokalen Maven-Repository:

    cd \MeinWorkspace\MvnCorpPom

    mvn install

  8. Wechseln Sie in das Multiprojekt und bauen Sie es inklusive aller Unterprojekte:

    cd \MeinWorkspace\MvnMultiProj3

    mvn clean install

    In der Reactor Summary wird wieder zu allen vier Projekten SUCCESS gemeldet.

  9. Die Corporate POM können Sie erweitern und in beliebige weitere andere Projekte einbinden.



Site Report um Project Reports erweitern

  1. Es gibt viele lohnenswerte Maven-Plugins, die automatisch Informationen zu Ihrem Projekt aufbereiten. Im Folgenden wird die Einbindung einiger solcher Maven-Plugins beispielhaft gezeigt.
    Einige Plugins entfalten ihre volle Stärke allerdings erst, wenn sie genauer konfiguriert werden.
    Bei den bisherigen Mini-Demos machen Project Reports kaum Sinn, aber vielleicht haben Sie ein größeres Projekt mit mehr Sourcedateien.

  2. Tragen Sie in dem im Folgenden gezeigten <properties>-Abschnitt Ihr korrektes sourceEncoding ein, also das Encoding, mit dem Sie Ihre Sourcedateien speichern (unter Linux meistens UTF-8, unter Windows meistens Cp1252, unter Eclipse konfigurierbar). Binden Sie die folgenden <properties>- und <reporting>-Blöcke in einem beliebigen Projekt in die pom.xml ein:

      ...
      <properties>
        <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
      </properties>
      <reporting>
        <plugins>
          <!-- Falls Versionskontrollsystem konfiguriert ist:
          <plugin>
            <artifactId>maven-changelog-plugin</artifactId>
          </plugin>
          -->
          <plugin>
            <artifactId>maven-checkstyle-plugin</artifactId>
            <configuration>
              <configLocation>config/maven_checks.xml</configLocation>
              <includeTestSourceDirectory>true</includeTestSourceDirectory>
              <excludes>**/generated/*.java</excludes>
            </configuration>
          </plugin>
          <plugin>
            <artifactId>maven-dependency-plugin</artifactId>
          </plugin>
          <plugin>
            <artifactId>maven-javadoc-plugin</artifactId>
          </plugin>
          <plugin>
            <artifactId>maven-jxr-plugin</artifactId>
          </plugin>
          <plugin>
            <artifactId>maven-surefire-report-plugin</artifactId>
            <configuration>
              <!-- Bei großen Projekten auf false setzen: -->
              <showSuccess>true</showSuccess>
            </configuration>
          </plugin>
          <plugin>
            <artifactId>maven-pmd-plugin</artifactId>
            <configuration>
              <targetJdk>1.6</targetJdk>
              <format>xml</format>
              <linkXref>true</linkXref>
              <sourceEncoding>ISO-8859-1</sourceEncoding>
              <minimumTokens>100</minimumTokens>
              <excludes>
                <exclude>**/generated/*.java</exclude>
              </excludes>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>cobertura-maven-plugin</artifactId>
          </plugin>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>findbugs-maven-plugin</artifactId>
          </plugin>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>taglist-maven-plugin</artifactId>
            <configuration>
              <tags>
                <tag>fixme</tag>
                <tag>FixMe</tag>
                <tag>FIXME</tag>
                <tag>@todo</tag>
                <tag>todo</tag>
                <tag>TODO</tag>
                <tag>xxx</tag>
                <tag>@deprecated</tag>
              </tags>
            </configuration>
          </plugin>
        </plugins>
      </reporting>
      ...
    
  3. Rufen Sie auf:

    mvn site

  4. Sehen Sie sich an:

    <Maven-Projekt>\target\site\index.html

    und:

    <Maven-Projekt>\target\site\project-reports.html

  5. Sie erhalten folgende Ergebnisse:

    Checkstyle Coding-Standards maven-checkstyle-plugin
    Cobertura Test Coverage Testabdeckung ermitteln cobertura-maven-plugin
    CPD Report Code-Dubletten finden (Copy/Paste Detector) maven-pmd-plugin
    Dependency Analysis Auflistung der Dependencies und unused/undeclared-Überprüfung maven-dependency-plugin
    FindBugs Report Potentielle Fehler im Sourcecode per Bytecode-Analyse finden findbugs-maven-plugin
    JavaDocs Javadoc-Dokumentation maven-javadoc-plugin
    PMD Report Potentielle Probleme im Sourcecode finden maven-pmd-plugin
    Source Xref HTML-basierende Verlinkung des Java-Sourcecodes maven-jxr-plugin
    Surefire Report Testergebnisse maven-surefire-report-plugin
    Tag List Bestimmte im Sourcecode enthaltene Tags auflisten (z.B. TODO, FIXME) taglist-maven-plugin
  6. Checkstyle, PMD und FindBugs scheinen sich auf den ersten Blick zu ähneln. Alle drei suchen nach bekannten Fehlermustern (Bug Patterns). Da sie aber verschieden funktionieren, finden sie unterschiedliche Fehler. Zum Beispiel analysieren Checkstyle und PMD den Sourcecode, während FindBugs den erzeugten Bytecode untersucht und so NullPointer, nicht geschlossene Datenströme und fehlerhafte Synchronisierungen aufspürt.

  7. Sehen Sie sich die Doku zu den einzelnen Maven-Plugins an.
    Fügen Sie weitere Maven-Plugins hinzu.



Eigenes Maven-Plugin (Mojo)

In Java programmierte Maven-Plugins bestehen aus Mojos. Ein Mojo ("Maven (plain) old Java Object") ist eine Java-Klasse die das Interface org.apache.maven.plugin.Mojo implementiert (oder org.apache.maven.plugin.AbstractMojo erweitert) und damit ein Plugin-Goal realisiert.
Siehe auch guide-java-plugin-development, maven-plugin-api und mojo-api-specification.

Mojo mit Parameter

  1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie ein Mojo-Projekt:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-mojo -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTimestampPlugin

    cd \MeinWorkspace\MvnTimestampPlugin

  2. Sehen Sie sich die generierte pom.xml an und beachten Sie insbesondere das <packaging> und die <dependency>:

      ...
      <packaging>maven-plugin</packaging>
      ...
      <dependencies>
        <dependency>
          <groupId>org.apache.maven</groupId>
          <artifactId>maven-plugin-api</artifactId>
      ...
    
  3. Löschen Sie im Verzeichnis MvnTimestampPlugin\src\main\java\de\meinefirma\meinprojekt die MyMojo.java und legen Sie stattdessen in diesem Verzeichnis die Mojo-Datei TimestampMojo.java mit folgendem Inhalt an:

    package de.meinefirma.meinprojekt;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import org.apache.maven.plugin.AbstractMojo;
    
    /**
     * @goal timestamp
     */
    public class TimestampMojo extends AbstractMojo
    {
       /** @parameter */ String prefix = "Timestamp:";
       /** @parameter */ String datetimePattern = "yyyy-MM-dd HH:mm:ss,S";
    
       public void execute()
       {
          getLog().info( prefix + " " +
             (new SimpleDateFormat( datetimePattern ).format( new Date() )) );
       }
    }
    

    Bitte beachten Sie die für das Maven-Plugin wichtigen Angaben @goal... und @parameter....
    Sie können das Goal über @phase auch an eine bestimmte Lifecycle-Phase binden.
    Weiteres zu @phase, @goal, @parameter und weiteren Annotationen finden Sie unter AbstractMojo, Mojo API Specification und Guide to Developing Java Plugins.

  4. Die Projektstruktur sieht jetzt so aus:

    cd \MeinWorkspace\MvnTimestampPlugin

    tree /F

    [D:\MeinWorkspace\MvnTimestampPlugin]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- TimestampMojo.java
     '- pom.xml
    
  5. Die allgemeine Syntax, um ein Goal auf der Kommandozeile auszuführen, lautet: "mvn groupID:artifactID:[version:]goal" (der Versionsteil kann weggelassen werden).
    Bauen Sie das Projekt und führen Sie für einen ersten Test das Goal per Kommandozeile aus:

    cd \MeinWorkspace\MvnTimestampPlugin

    mvn clean install

    mvn de.meinefirma.meinprojekt:MvnTimestampPlugin:timestamp

    Sie erhalten:

    [INFO] [MvnTimestampPlugin:timestamp {execution: default-cli}]
    [INFO] Timestamp: 2009-09-09 11:22:30,456
    
  6. Interessant wird es jedoch erst, wenn Sie das neue Maven-Plugin in anderen Projekten verwenden und beispielsweise mit Lifecycle-Phasen verknüpfen. Erweitern Sie die pom.xml irgendeines beliebigen Projekts (z.B. MvnJaxbApp) im <build><plugins>...-Block um folgendes <plugin>-Element:

          <plugin>
            <groupId>de.meinefirma.meinprojekt</groupId>
            <artifactId>MvnTimestampPlugin</artifactId>
            <version>1.0-SNAPSHOT</version>
            <configuration>
              <prefix>----</prefix>
              <datetimePattern>HH:mm:ss</datetimePattern>
            </configuration>
            <executions>
              <execution>
                <id>nach clean</id>
                <phase>clean</phase>
                <goals>
                  <goal>timestamp</goal>
                </goals>
              </execution>
              <execution>
                <id>nach compile</id>
                <phase>compile</phase>
                <goals>
                  <goal>timestamp</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
    

    Außer der Verknüpfung mit den beiden Lifecycle-Phasen clean und compile findet auch die Konfiguration der beiden Parameter prefix und datetimePattern statt.

    Führen Sie jetzt im Projektverzeichnis dieses anderen Projekts aus:

    cd \MeinWorkspace\MvnJaxbApp   [anpassen!]

    mvn clean package

    Sie erhalten:

    [INFO] ...
    [INFO] [clean:clean {execution: default-clean}]
    [INFO] ...
    [INFO] [MvnTimestampPlugin:timestamp {execution: nach clean}]
    [INFO] ---- 11:22:33
    [INFO] ...
    [INFO] [compiler:compile {execution: default-compile}]
    [INFO] ...
    [INFO] [MvnTimestampPlugin:timestamp {execution: nach compile}]
    [INFO] ---- 11:22:34
    [INFO] ...
    

Mojo-PluginContext

  1. Wir wollen das Plugin-Mojo so erweitern, dass es nicht nur die aktuelle Zeit anzeigt, sondern zusätzlich die Dauer vom letzten Aufruf bis zu diesem Aufruf berechnet. Dazu ist eine Kommunikation zwischen den verschiedenen Plugin-Aufrufen notwendig. Eine solche Kommunikation (auch zwischen verschiedenen Plugins) ist über die "PluginContext"-Map möglich.
    Ersetzen Sie im Verzeichnis MvnTimestampPlugin\src\main\java\de\meinefirma\meinprojekt den Inhalt der TimestampMojo.java durch Folgendes:

    package de.meinefirma.meinprojekt;
    
    import java.text.SimpleDateFormat;
    import java.util.*;
    import org.apache.maven.plugin.AbstractMojo;
    
    /**
     * @goal timestamp
     */
    public class TimestampMojo extends AbstractMojo
    {
       /** @parameter */ String prefix = "Timestamp:";
       /** @parameter */ String datetimePattern = "yyyy-MM-dd HH:mm:ss,S";
    
       public void execute()
       {
          final String CTX_TIME_KEY = "TimestampMojo-Time";
          Date  date = new Date();
          Map   ctx = getPluginContext();
          Long  timeZuletzt = (Long) ctx.get( CTX_TIME_KEY );
          ctx.put( CTX_TIME_KEY, new Long( date.getTime() ) );
          String dauer = ( timeZuletzt == null ) ? "" :
                ", Dauer: " + (date.getTime() - timeZuletzt.longValue()) + " ms";
          getLog().info( prefix + " " +
                (new SimpleDateFormat( datetimePattern ).format( date )) + dauer );
       }
    }
    
  2. Bauen Sie das Timestamp-Plugin neu:

    cd \MeinWorkspace\MvnTimestampPlugin

    mvn clean install

    Führen Sie wieder im Projektverzeichnis des anderen Projekts aus:

    cd \MeinWorkspace\MvnJaxbApp   [anpassen!]

    mvn clean package

    Diesmal ist die Ausgabe erweitert um die Anzeige der "Dauer":

    [INFO] ...
    [INFO] [clean:clean {execution: default-clean}]
    [INFO] ...
    [INFO] [MvnTimestampPlugin:timestamp {execution: nach clean}]
    [INFO] ---- 11:22:33
    [INFO] ...
    [INFO] [compiler:compile {execution: default-compile}]
    [INFO] ...
    [INFO] [MvnTimestampPlugin:timestamp {execution: nach compile}]
    [INFO] ---- 11:22:34, Dauer: 1234 ms
    

Mojo-JUnit-Test

  1. Es gibt verschiedene Verfahren, um Maven-Plugins automatisiert testen zu können. Einige sind beschrieben unter Introduction / Testing Styles.

  2. Um das maven-plugin-testing-harness-Plugin verwenden zu können, erweitern Sie die pom.xml des MvnTimestampPlugins im <dependencies>...-Block um folgendes <dependency>-Element:

        <dependency>
          <groupId>org.apache.maven.shared</groupId>
          <artifactId>maven-plugin-testing-harness</artifactId>
          <version>1.1</version>
          <scope>test</scope>
        </dependency>
    
  3. Für den Test wird nur eine einfache abgespeckte POM benötigt. Erzeugen Sie im MvnTimestampPlugin\src-Verzeichnis die Unterverzeichnisse test\resources und im resources-Verzeichnis die Datei test-pom.xml mit folgendem Inhalt:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>test</groupId>
      <artifactId>Test</artifactId>
      <build>
        <plugins>
          <plugin>
            <groupId>de.meinefirma.meinprojekt</groupId>
            <artifactId>MvnTimestampPlugin</artifactId>
            <configuration>
              <prefix>----</prefix>
              <datetimePattern>HH:mm:ss</datetimePattern>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </project>
    
  4. Erzeugen Sie im MvnTimestampPlugin\src\test-Verzeichnis die Unterverzeichnisse java\de\meinefirma\meinprojekt und im untersten Verzeichnis (meinprojekt) die Datei TimestampMojoTest.java mit folgendem Inhalt:

    package de.meinefirma.meinprojekt;
    
    import java.io.File;
    import java.util.*;
    import org.apache.maven.plugin.logging.SystemStreamLog;
    import org.apache.maven.plugin.testing.AbstractMojoTestCase;
    
    public class TimestampMojoTest extends AbstractMojoTestCase
    {
       public void testTimestampMojo() throws Exception
       {
          Map pluginContext = new HashMap();
          String log1 = executeMojo( pluginContext );
          String log2 = executeMojo( pluginContext );
          assertTrue(  log1.length() < log2.length() );
          assertTrue( !log1.contains( "Dauer" ) );
          assertTrue(  log2.contains( "Dauer" ) );
       }
    
       private String executeMojo( Map pluginContext ) throws Exception
       {
          String        testPom = getBasedir() + "/src/test/resources/test-pom.xml";
          String        artifactId = "MvnTimestampPlugin";
          StringBuffer  log  = new StringBuffer();
          TimestampMojo mojo = new TimestampMojo();
          configureMojo( mojo, artifactId, new File( testPom ) );
          mojo.setPluginContext( pluginContext );
          mojo.setLog( new TestLog( log ) );
          mojo.execute();
          String prefix = (String) getVariableValueFromObject( mojo, "prefix" );
          assertNotNull( prefix );
          assertEquals( prefix, log.substring( 0, prefix.length() ) );
          return log.toString();
       }
    }
    
    class TestLog extends SystemStreamLog
    {
       StringBuffer log;
    
       public TestLog( StringBuffer log )
       {
          this.log = log;
       }
    
       // @Override
       public void info( CharSequence content )
       {
          log.append( content );
       }
    }
    

    Die Variable testPom muss den Pfad zu einer geeigneten POM-Datei enthalten, in welcher das Plugin MvnTimestampPlugin eingetragen ist.

    TimestampMojoTest erweitert AbstractMojoTestCase, damit die Methoden getBasedir(), configureMojo() und getVariableValueFromObject() zur Verfügung stehen.

    Bitte beachten Sie die Injizierung der Context-Map über setPluginContext() und des Testloggers über setLog(). Letzteres wird benötigt, um die log-Ausgaben abzufangen.

    executeMojo() wird zweimal aufgerufen: Beim ersten Mal wird nur der Timestamp geloggt. Beim zweiten Mal wird auch die Dauer geloggt, was durch "assertTrue( log2.contains( "Dauer" ) )" überprüft wird.

    Bitte beachten Sie, dass TestLog nur eine einzige der vielen Log-Methoden überschreibt, was für diesen Test reicht, aber für andere Tests eventuell zu wenig sein kann.

  5. Ihre Verzeichnisstruktur sieht jetzt so aus:

    cd \MeinWorkspace\MvnTimestampPlugin

    tree /F

    -->

    [D:\MeinWorkspace\MvnTimestampPlugin]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [de]
     |   |           '- [meinefirma]
     |   |               '- [meinprojekt]
     |   |                   '- TimestampMojo.java
     |   '- [test]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- TimestampMojoTest.java
     |       '- [resources]
     |           '- test-pom.xml
     '- pom.xml
    
  6. Führen Sie den JUnit-Test aus:

    cd \MeinWorkspace\MvnTimestampPlugin

    mvn test

  7. Falls Sie mit Eclipse arbeiten: Wegen der geänderten Konfiguration müssen Sie die Eclipse-Projektdateien neu erstellen (anschließend das Projekt in Eclipse mit F5 refreshen):

    mvn eclipse:clean eclipse:eclipse

    Dann können Sie den Test auch in Eclipse ausführen.

Mojo-Hilfstexte

  1. Erweitern Sie die pom.xml um erläuternde Hilfstexte, damit folgendes Kommando Hilfe bieten kann:

    mvn help:describe -Dplugin=de.meinefirma.meinprojekt:MvnTimestampPlugin -Ddetail



Parallelisierte Testausführung mit TestNG

Ein möglicher Nachfolger für JUnit könnte TestNG werden. Hierzu gibt es auch Unterstützung durch das bereits bekannte maven-surefire-plugin, womit im Folgenden die Parallelisierung von Tests demonstriert wird.

  1. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Projekt:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTestNG

    cd \MeinWorkspace\MvnTestNG

    tree /F

    [D:\MeinWorkspace\MvnTestNG]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [de]
     |   |           '- [meinefirma]
     |   |               '- [meinprojekt]
     |   |                   '- App.java
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- AppTest.java
     '- pom.xml
    
  2. Ersetzen Sie den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnTestNG</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>MvnTestNG</name>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.4.3</version>
            <configuration>
              <parallel>methods</parallel>
              <threadCount>10</threadCount>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>1.6</source>
              <target>1.6</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>org.testng</groupId>
          <artifactId>testng</artifactId>
          <version>5.9</version>
          <classifier>jdk15</classifier>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </project>
    
  3. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis den Inhalt der App.java durch:

    package de.meinefirma.meinprojekt;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class App
    {
       public static void main( String[] args ) throws InterruptedException
       {
          final SimpleDateFormat HH_MM_SS_S = new SimpleDateFormat( "HH:mm:ss.S" );
          String s = ( args != null && args.length > 0 ) ? args[0] : ".";
          System.out.println( "\n" + HH_MM_SS_S.format( new Date() ) + " " + Thread.currentThread().getName() + " Start" );
          for( int i = 0; i < 10; i++ ) {
             Thread.sleep( 100 );
             System.out.print( s );
             Thread.sleep( 100 );
          }
          System.out.println( "\n" + HH_MM_SS_S.format( new Date() ) + " " + Thread.currentThread().getName() + " Ende" );
       }
    }
    
  4. Löschen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis die AppTest.java und legen Sie in diesem Verzeichnis folgende Testklassen an:

    App1Test.java

    package de.meinefirma.meinprojekt;
    
    import org.testng.annotations.Test;
    
    public class App1Test
    {
       @Test public void testApp() throws InterruptedException
       {
          App.main( new String[] { "1" } );
       }
    }
    

    App2Test.java

    package de.meinefirma.meinprojekt;
    
    import org.testng.annotations.Test;
    
    public class App2Test
    {
       @Test public void testApp() throws InterruptedException
       {
          App.main( new String[] { "2" } );
       }
    }
    

    App3Test.java

    package de.meinefirma.meinprojekt;
    
    import org.testng.annotations.Test;
    
    public class App3Test
    {
       @Test public void testApp3() throws InterruptedException
       {
          App.main( new String[] { "3" } );
       }
    
       @Test public void testApp4() throws InterruptedException
       {
          App.main( new String[] { "4" } );
       }
    
       @Test public void testApp5() throws InterruptedException
       {
          App.main( new String[] { "5" } );
       }
    }
    
  5. Ihr Projekteverzeichnis sieht jetzt so aus:

    [D:\MeinWorkspace\MvnTestNG]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [de]
     |   |           '- [meinefirma]
     |   |               '- [meinprojekt]
     |   |                   '- App.java
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- App1Test.java
     |                       '- App2Test.java
     |                       '- App3Test.java
     '- pom.xml
    
  6. Führen Sie die fünf Testmethoden in den drei Testklassen aus:

    cd \MeinWorkspace\MvnTestNG

    mvn test

    Sie erhalten beispielsweise:

    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    
    11:22:33.0 pool-1-thread-1 Start
    11:22:33.0 pool-1-thread-4 Start
    11:22:33.0 pool-1-thread-2 Start
    11:22:33.0 pool-1-thread-5 Start
    11:22:33.0 pool-1-thread-3 Start
    31245134521342513245312541354231524135423154231254
    11:22:35.0 pool-1-thread-1 Ende
    11:22:35.0 pool-1-thread-5 Ende
    11:22:35.0 pool-1-thread-3 Ende
    11:22:35.0 pool-1-thread-2 Ende
    11:22:35.0 pool-1-thread-4 Ende
    
    Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.1 sec
    
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
    

    Wie Sie gut erkennen können, werden die fünf Testmethoden in fünf parallelen Threads ausgeführt.
    Bitte beachten Sie: Durch die Einstellung <parallel>methods</parallel> werden nicht nur Klassen, sondern auch die Methoden innerhalb der Klasse App3Test.java parallel ausgeführt.

  7. Ersetzen Sie testweise in der pom.xml die Zeile

              <parallel>methods</parallel>
    

    durch

              <parallel>false</parallel>
    

    und führen Sie die Tests erneut aus: Während bei ersterer paralleler Testausführung als Gesamttestzeit ("Time elapsed") ein Wert knap über 2 Sekunden gemeldet wurde, wird diesmal mit serieller Ausführung ein Wert über 10 Sekunden gemeldet, also fast das fünffache.



Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit

Das folgende Beispiel enthält zwei verschiedene Testarten, die beide automatisiert ausgeführt werden können:

Erläuterungen zu den verschiedenen Testarten "Komponententest" und "Integrationstest" finden Sie unter java-fit.htm#Testebenen.

Zum besseren Verständnis und um es einfach zu halten sind in diesem Beispiel Komponententests und Integrationstests in einem einzigen Maven-Projekt vereint. In realen Projekten ist es meistens wesentlich besser, die Integrationstests in einem getrennten Maven-Projekt zu realisieren. Dann können Sie die Abhängigkeiten besser kontrollieren und den Ausführungszeitpunkt der Integrationstests leichter bestimmen.

Die Verwendung von sowohl HtmlUnit als auch HttpUnit zusammen in einem Projekt ist normalerweise nicht sinnvoll, da beide Testframeworks ähnliche Ziele abdecken. Insbesondere im folgenden Beispiel ist der einzige Sinn, beides demonstrieren zu können.

  1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine Webapp-Projektstruktur:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnHtmlIntTest

    cd \MeinWorkspace\MvnHtmlIntTest

    tree /F

    Sie erhalten:

    [D:\MeinWorkspace\MvnHtmlIntTest]
     |- [src]
     |   '- [main]
     |       |- [resources]
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- index.jsp
     '- pom.xml
    
  2. Ersetzen Sie den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnHtmlIntTest</artifactId>
      <packaging>war</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>MvnHtmlIntTest: Webapp mit Integrationstest</name>
      <url>http://www.meinefirma.de</url>
      <build>
        <finalName>${artifactId}</finalName>
        <plugins>
          <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
            <configuration>
              <scanIntervalSeconds>10</scanIntervalSeconds>
              <stopPort>9999</stopPort>
              <stopKey>geheim</stopKey>
            </configuration>
            <executions>
              <execution>
                <id>start-jetty</id>
                <phase>pre-integration-test</phase>
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <scanIntervalSeconds>0</scanIntervalSeconds>
                  <daemon>true</daemon>
                </configuration>
              </execution>
              <execution>
                <id>stop-jetty</id>
                <phase>post-integration-test</phase>
                <goals>
                  <goal>stop</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-surefire-plugin</artifactId>
             <configuration>
                <excludes>
                   <exclude>**/integrationstest/*Test.java</exclude>
                </excludes>
             </configuration>
             <executions>
                <execution>
                   <id>integration-tests</id>
                   <phase>integration-test</phase>
                   <goals>
                      <goal>test</goal>
                   </goals>
                   <configuration>
                      <skip>false</skip>
                      <excludes>
                         <exclude>none</exclude>
                      </excludes>
                      <includes>
                         <include>**/integrationstest/*Test.java</include>
                      </includes>
                   </configuration>
                </execution>
             </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>1.6</source>
              <target>1.6</target>
            </configuration>
          </plugin>
         </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>htmlunit</groupId>
          <artifactId>htmlunit</artifactId>
          <version>1.14</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>httpunit</groupId>
          <artifactId>httpunit</artifactId>
          <version>1.6.2</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.7</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </project>
    

    Erläuterungen zu den Konfigurationsoptionen des Jetty-Plugins finden Sie unter http://jetty.mortbay.org/jetty/maven-plugin/run-mojo.html (sehen Sie sich insbesondere scanIntervalSeconds und daemon an).

    Erläuterungen zu den Optionen des Surefire-Test-Plugins finden Sie unter http://maven.apache.org/plugins/maven-surefire-plugin/ und maven-surefire-plugin/howto.html.

  3. Ersetzen Sie im MvnHtmlIntTest\src\main\webapp-Verzeichnis den Inhalt der index.jsp durch:

    <html>
    <head><title>Startseite</title></head>
    <body>
    <h2>Startseite</h2>
    <p><a name="BerechnungsFormular" href="BerechnungsFormular.jsp">Berechnungsformular</a></p>
    </body>
    </html>
    
  4. Fügen Sie im MvnHtmlIntTest\src\main\webapp-Verzeichnis folgende BerechnungsFormular.jsp hinzu:

    <%@ page import="de.meinefirma.meinprojekt.MeineBean" %>
    <html>
    <head><title>Webseite mit Berechnungsformular</title></head>
    <body>
    <h2>Webseite mit Berechnungsformular</h2>
    <% String wert1 = request.getParameter( "wert1" );
       String wert2 = request.getParameter( "wert2" );
       if( wert1 == null ) wert1 = "";
       if( wert2 == null ) wert2 = "";
       String ergebnis = (new MeineBean()).berechne( wert1, wert2 ); %>
    <form name="meinFormular" method="post"><pre>
    Wert1 <input type="text" name="wert1" value='<%= wert1 %>' size=10 maxlength=10><br>
    Wert2 <input type="text" name="wert2" value='<%= wert2 %>' size=10 maxlength=10><br>
          <input type="submit" name="berechne" value="berechne"> <input type="text" name="ergebnis" value='<%= ergebnis %>' size=10 readonly><br>
    </pre></form>
    </body>
    </html>
    
  5. Erzeugen Sie unter MvnHtmlIntTest\src\main die Unterverzeichnisse java\de\meinefirma\meinprojekt und darin die JavaBean MeineBean.java:

    package de.meinefirma.meinprojekt;
    
    public class MeineBean
    {
       public String berechne( String d1, String d2 )
       {
          try {
             return "" + (Double.parseDouble( d1 ) + Double.parseDouble( d2 ));
          } catch( Exception ex ) {
             return "";
          }
       }
    }
    
  6. Erzeugen Sie unter MvnHtmlIntTest\src die Unterverzeichnisse test\java\de\meinefirma\meinprojekt und darin den Komponenten-JUnit-Test MeineBeanTest.java:

    package de.meinefirma.meinprojekt;
    
    import static org.junit.Assert.*;
    import org.junit.Test;
    
    public class MeineBeanTest
    {
       @Test public void testMeineBean()
       {
          MeineBean meineBean = new MeineBean();
          assertEquals( "3.0", meineBean.berechne( "1", "2" ) );
       }
    }
    
  7. Erzeugen Sie unter MvnHtmlIntTest\src\test\java das Unterverzeichnis integrationstest und darin den HtmlUnit-Integrationstest HtmlUnitTest.java:

    package integrationstest;
    
    import static org.junit.Assert.assertEquals;
    import org.junit.Test;
    import com.gargoylesoftware.htmlunit.WebClient;
    import com.gargoylesoftware.htmlunit.html.*;
    
    public class HtmlUnitTest
    {
       @Test public void test() throws Exception
       {
          test( "1", "2", "3.0" );
       }
    
       public String test( String wert1, String wert2, String ergebnis ) throws Exception
       {
          WebClient webClient = new WebClient();
          HtmlPage  page = (HtmlPage) webClient.getPage( "http://localhost:8080/MvnHtmlIntTest/" );
          assertEquals( "Titel: ", "Startseite", page.getTitleText() );
          page = (HtmlPage) page.getAnchorByName( "BerechnungsFormular" ).click();
          assertEquals( "Titel: ", "Webseite mit Berechnungsformular", page.getTitleText() );
          HtmlForm form = page.getFormByName( "meinFormular" );
          form.getInputByName( "wert1" ).setValueAttribute( wert1 );
          form.getInputByName( "wert2" ).setValueAttribute( wert2 );
          page = (HtmlPage) form.getInputByName( "berechne" ).click();
          String e = page.getFormByName( "meinFormular" ).getInputByName( "ergebnis" ).getValueAttribute();
          if( ergebnis != null ) assertEquals( "Ergebnis: ", ergebnis, e );
          return e;
       }
    }
    
  8. Erzeugen Sie im MvnHtmlIntTest\src\test\java\integrationstest-Verzeichnis den HttpUnit-Integrationstest HttpUnitTest.java:

    package integrationstest;
    
    import static org.junit.Assert.assertEquals;
    import org.junit.Test;
    import com.meterware.httpunit.*;
    
    public class HttpUnitTest
    {
       @Test public void test() throws Exception
       {
          test( "1", "2", "3.0" );
       }
    
       public String test( String wert1, String wert2, String ergebnis ) throws Exception
       {
          WebConversation conversation = new WebConversation();
          WebRequest      request = new GetMethodWebRequest( "http://localhost:8080/MvnHtmlIntTest/" );
          WebResponse     response = conversation.getResponse( request );
          assertEquals( "ReturnCode: ", 200, response.getResponseCode() );
          assertEquals( "ReturnMsg: ", "OK", response.getResponseMessage() );
          assertEquals( "Titel: ", "Startseite", response.getTitle() );
          response = response.getLinkWithName( "BerechnungsFormular" ).click();
          assertEquals( "Titel: ", "Webseite mit Berechnungsformular", response.getTitle() );
          WebForm form = response.getFormWithName( "meinFormular" );
          form.setParameter( "wert1", wert1 );
          form.setParameter( "wert2", wert2 );
          form.getSubmitButton( "berechne" ).click();
          response = conversation.getCurrentPage();
          form = response.getFormWithName( "meinFormular" );
          String e = form.getParameterValue( "ergebnis" );
          if( ergebnis != null ) assertEquals( "Ergebnis: ", ergebnis, e );
          return e;
       }
    }
    

    Die Abfrage if( ergebnis != null ) erscheint im Moment noch etwas unsinnig: Sie wird erst später in MeinFixture im HTML-Akzeptanztest mit Fit benötigt.

  9. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [D:\MeinWorkspace\MvnHtmlIntTest]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               '- MeineBean.java
     |   |   |- [resources]
     |   |   '- [webapp]
     |   |       |- [WEB-INF]
     |   |       |   '- web.xml
     |   |       |- BerechnungsFormular.jsp
     |   |       '- index.jsp
     |   '- [test]
     |       '- [java]
     |           |- [de]
     |           |   '- [meinefirma]
     |           |       '- [meinprojekt]
     |           |           '- MeineBeanTest.java
     |           '- [integrationstest]
     |               |- HtmlUnitTest.java
     |               '- HttpUnitTest.java
     '- pom.xml
    
  10. Manueller Test im Webbrowser:

    cd \MeinWorkspace\MvnHtmlIntTest

    mvn jetty:run

    Warten Sie bis "Started Jetty Server" erscheint und rufen Sie dann im Webbrowser auf:

    http://localhost:8080/MvnHtmlIntTest

    Klicken Sie auf den Berechnungsformular-Link, tragen Sie auf der folgenden Webseite zwei Zahlen ein und betätigen Sie "berechne": Sie erhalten die Summe.

    Beenden Sie Jetty mit "Strg + C".

  11. Testen verschiedener Maven-Kommandos:

    Normaler Build (MvnHtmlIntTest.war wird im target-Verzeichnis erzeugt):

    mvn clean package

    Nur einen einzelnen bestimmten Komponententest ausführen:

    mvn test -Dtest=de.meinefirma.meinprojekt.MeineBeanTest

    Build ohne Tests:

    mvn package -Dmaven.test.skip=true

  12. Manueller Aufruf einzelner bestimmter Integrationstests:

    Versuchen Sie zuerst testweise einen Integrationstest ohne laufenden Jetty aufzurufen und sehen Sie sich die Fehlermeldung in target\surefire-reports\integrationstest.HtmlUnitTest.txt an:

    mvn test -Dtest=integrationstest.HtmlUnitTest

    Starten Sie jetzt in einem Kommandozeilenfenster Jetty mit der Webanwendung:

    mvn jetty:run

    Starten Sie in einem zweiten Kommandozeilenfenster die Integrationstests (diesmal ohne Fehlermeldung):

    cd \MeinWorkspace\MvnHtmlIntTest

    mvn test -Dtest=integrationstest.HtmlUnitTest

    mvn test -Dtest=integrationstest.HttpUnitTest

    mvn test -Dtest=integrationstest.*Test

    Beenden Sie Jetty mit "Strg + C".

  13. Automatisierter Integrationstest:

    Der wichtigste Trick in diesem Beispiel ist die vollständig automatisierte Ausführung der Integrationstests inklusive automatischem Start des Jetty-Servers in einem zusätzlichen Hintergrund-Thread (über <daemon>true</daemon>), Ausführung der Integrationstests und anschließend der automatischen Beendigung von Jetty:

    mvn clean integration-test

    Sie erhalten zwei Testphasen, zuerst default-test und dann integration-tests:

    ...
    [INFO] [surefire:test {execution: default-test}]
    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running de.meinefirma.meinprojekt.MeineBeanTest
    Results :
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    ...
    ...
    [INFO] Starting jetty 6.1.20
    ...
    [INFO] Started Jetty Server
    [INFO] [surefire:test {execution: integration-tests}]
    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running integrationstest.HttpUnitTest
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    Running integrationstest.HtmlUnitTest
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    Results :
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
     INFO:  Shutdown hook executing
     INFO:  Shutdown hook complete
    
  14. Dependencies:

    Lassen Sie sich folgendermaßen einen Dependency-Baum anzeigen und sehen Sie sich an, wie Maven automatisch Versionskonflikte bei den Abhängigkeiten gelöst hat:

    mvn dependency:tree -Dverbose

  15. Eclipse:

    Um Maven-Projekte in Eclipse bearbeiten zu können, müssen Sie verfahren wie oben unter Maven mit Eclipse beschrieben ist (also "mvn eclipse:eclipse", Import... bzw. F5 etc.).
    Ein besonderer Vorteil des Jetty-Plugins ist, dass Sie über das scanIntervalSeconds-Attribut steuern können, ob Änderungen direkt in den Webcontainer übernommen werden, ohne dass ein Redeployment notwendig ist. Testen Sie dies, indem Sie zuerst Jetty über "mvn jetty:run" starten und anschließend in Eclipse Änderungen zum Beispiel an BerechnungsFormular.jsp, MeineBean.java oder anderen Dateien durchführen.

  16. Infos zu HttpUnit und HtmlUnit:

    Ein weiteres Programmierbeispiel zu HttpUnit finden Sie unter java-httpunit.htm.
    Weiteres zu HttpUnit finden Sie unter http://httpunit.sourceforge.net und http://httpunit.sourceforge.net/doc/api/.
    Weiteres zu HtmlUnit finden Sie unter http://htmlunit.sourceforge.net und http://htmlunit.sourceforge.net/apidocs/.

  17. Erweiterungen:

    Wie Sie Ihre Integrationstests mit Fit und Cargo erweitern, erfahren Sie weiter unten.
    Um die Datenbank mit bestimmten Inhalten vorzubereiten, können Sie das DbUnit Maven Plugin verwenden.
    Wenn Sie erweiterte Möglichkeiten für Integrationstests benötigen, sollten Sie sich das Failsafe Maven Plugin ansehen. Siehe hierzu auch den Sonatype-Blog-Eintrag Part 1 und zur Testabdeckung in Integrationstests mit emma4it den Part 2.



Automatisierter Integrationstest mit Jetty und JWebUnit

JWebUnit leistet Ähnliches wie HtmlUnit und HttpUnit, aber ist oft wesentlich einfacher und übersichtlicher.

  1. Voraussetzung für das folgende Beispiel ist die Durchführung des vorherigen Beispiels Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit (im Folgenden MvnHtmlIntTest genannt).

  2. Erzeugen Sie ein neues Projektverzeichnis MvnJWebUnitIntTest und kopieren Sie dorthin das MvnHtmlIntTest-Projekt:

    cd \MeinWorkspace

    md MvnJWebUnitIntTest

    cd MvnJWebUnitIntTest

    xcopy ..\MvnHtmlIntTest\*.* /S

  3. Entfernen Sie in der pom.xml im MvnJWebUnitIntTest-Projektverzeichnis die beiden <dependency>...-Blöcke zu htmlunit und httpunit und fügen Sie stattdessen folgenden neuen <dependency>...-Block ein:

        <dependency>
          <groupId>net.sourceforge.jwebunit</groupId>
          <artifactId>jwebunit-htmlunit-plugin</artifactId>
          <version>2.2</version>
          <scope>test</scope>
        </dependency>
    
  4. Löschen Sie im MvnJWebUnitIntTest\src\test\java\integrationstest-Verzeichnis die beiden Dateien HtmlUnitTest.java und HttpUnitTest.java und erzeugen Sie stattdessen im selben Verzeichnis die neue Testklasse JWebUnitTest.java:

    package integrationstest;
    
    import net.sourceforge.jwebunit.junit.WebTestCase;
    
    public class JWebUnitTest extends WebTestCase
    {
       public void test()
       {
          setBaseUrl(        "http://localhost:8080/MvnHtmlIntTest/" );
          beginAt(           "/index.jsp" );
          assertTitleEquals( "Startseite" );
          clickLinkWithText( "Berechnungsformular" );
          assertTitleEquals( "Webseite mit Berechnungsformular" );
          setTextField(      "wert1", "1" );
          setTextField(      "wert2", "2" );
          submit();
          assertTextFieldEquals( "ergebnis", "3.0" );
       }
    }
    

    Falls Sie die JWebUnit-XPath-Methoden probieren wollen: Den Ergebniswert können Sie beispielsweise abfragen mit:
    getElementAttributeByXPath( "//input[lower-case(@type)='text' and @name='ergebnis']", "value" );

  5. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [D:\MeinWorkspace\MvnJWebUnitIntTest]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               '- MeineBean.java
     |   |   '- [webapp]
     |   |       |- [WEB-INF]
     |   |       |   '- web.xml
     |   |       |- BerechnungsFormular.jsp
     |   |       '- index.jsp
     |   '- [test]
     |       '- [java]
     |           |- [de]
     |           |   '- [meinefirma]
     |           |       '- [meinprojekt]
     |           |           '- MeineBeanTest.java
     |           '- [integrationstest]
     |               '- JWebUnitTest.java
     '- pom.xml
    
  6. Führen Sie den Integrationstest aus (wieder mit Jetty im Hintergrund-Thread):

    cd \MeinWorkspace\MvnJWebUnitIntTest

    mvn clean integration-test

  7. Weiteres zu JWebUnit finden Sie unter http://jwebunit.sourceforge.net, http://jwebunit.sourceforge.net/apidocs/ und http://jwebunit.sourceforge.net/apidocs/net/sourceforge/jwebunit/junit/WebTestCase.html.



Automatisierter Integrationstest mit Fit

  1. Ausführliche Infos zu Fit finden Sie in java-fit.htm.

  2. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Projekt:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnFitTest

    cd \MeinWorkspace\MvnFitTest

    tree /F

    [D:\MeinWorkspace\MvnFitTest]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [de]
     |   |           '- [meinefirma]
     |   |               '- [meinprojekt]
     |   |                   '- App.java
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- AppTest.java
     '- pom.xml
    
  3. Ersetzen Sie den Inhalt der pom.xml durch:

    <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">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnFitTest</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>MvnFitTest</name>
      <build>
        <plugins>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>fit-maven-plugin</artifactId>
              <executions>
                <execution>
                  <id>fixture</id>
                  <phase>integration-test</phase>
                  <configuration>
                    <sourceDirectory>src/test/fit</sourceDirectory>
                    <caseSensitive>true</caseSensitive>
                    <sourceIncludes>**/*FitTest.html</sourceIncludes>
                    <parseTags>
                      <parseTag>table</parseTag>
                      <parseTag>tr</parseTag>
                      <parseTag>td</parseTag>
                    </parseTags>
                    <outputDirectory>target/fit</outputDirectory>
                    <ignoreFailures>false</ignoreFailures>
                  </configuration>                
                  <goals>
                    <goal>run</goal>
                  </goals>
                </execution>
              </executions>
          </plugin>
        </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>com.c2.fit</groupId>
          <artifactId>fit</artifactId>
          <version>1.1</version>
        </dependency>
      </dependencies>
    </project>
    

    Erläuterungen zu den fit-maven-plugin-<configuration>-Optionen finden Sie unter http://mojo.codehaus.org/fit-maven-plugin/run-mojo.html.

    Falls Sie die Fit-Tests nicht mit HTML-Seiten, sondern mit Wiki-Seiten steuern wollen, finden Sie Konfigurationshinweise unter http://mojo.codehaus.org/fit-maven-plugin/usage.html.

  4. Löschen Sie im MvnFitTest\src\main\java\de\meinefirma\meinprojekt-Verzeichnis App.java und erzeugen Sie stattdessen (die schon bekannte) MeineBean.java:

    package de.meinefirma.meinprojekt;
    
    public class MeineBean
    {
       public String berechne( String d1, String d2 )
       {
          try {
             return "" + (Double.parseDouble( d1 ) + Double.parseDouble( d2 ));
          } catch( Exception ex ) {
             return "";
          }
       }
    }
    
  5. Löschen Sie im MvnFitTest\src\test\java-Verzeichnis das Unterverzeichnis de (inkl. aller weiteren Unterverzeichnisse).

  6. Erzeugen Sie im MvnFitTest\src\test\java-Verzeichnis das Unterverzeichnis fixtures und darin MeinFixture.java:

    package fixtures;
    
    public class MeinFixture extends fit.ColumnFixture
    {
       public String wert1;
       public String wert2;
    
       public String berechne()
       {
          return (new de.meinefirma.meinprojekt.MeineBean()).berechne( wert1, wert2 );
       }
    }
    
  7. Die Fit-Testbeschreibung erfolgt als HTML-Tabelle (erste Zeile: Fixture-Angabe, zweite Zeile: Eingabeparameter und erwartete zu berechnende Ausgabewerte, weitere Zeilen: Testfälle).

    Erzeugen Sie im MvnFitTest\src\test-Verzeichnis das Unterverzeichnis fit und darin MeinFitTest.html:

    <!doctype html public "-//w3c//dtd html 4.0 transitional//en">
    <html>
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
       <title>Mein Fit-Test</title>
    </head>
    <body>
    
    <h2>Mein Fit-Test</h2>
    
    <table border="1" cellspacing="0" cellpadding="3">
       <tr><td colspan="3"> fixtures.MeinFixture </td></tr>
       <tr><td>  wert1 </td><td>  wert2 </td><td> berechne() </td></tr>
       <tr><td>      0 </td><td> &nbsp; </td><td>     &nbsp; </td></tr>
       <tr><td>      1 </td><td>      2 </td><td>        3.0 </td></tr>
       <tr><td>     +1 </td><td>     -1 </td><td>        0.0 </td></tr>
       <tr><td>    -42 </td><td>     13 </td><td>      -29.0 </td></tr>
       <tr><td>  1.234 </td><td>  5.678 </td><td>      6.912 </td></tr>
    </table>
    
    <br>
    
    <table border="1" cellspacing="0" cellpadding="3">
       <tr><td colspan="2">fit.Summary</td></tr>
    </table>
    
    </body>
    </html>
    

    Sehen Sie sich die Testfall-Beschreibungen an:

    start src\test\fit\MeinFitTest.html

    fixtures.MeinFixture
    wert1wert2berechne()
    0  
    123.0
    +1-10.0
    -4213-29.0
    1.2345.6786.912
  8. Überprüfen Sie Ihre Projektstruktur mit tree /F:

    [D:\MeinWorkspace\MvnFitTest]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [de]
     |   |           '- [meinefirma]
     |   |               '- [meinprojekt]
     |   |                   '- MeineBean.java
     |   '- [test]
     |       |- [fit]
     |       |   '- MeinFitTest.html
     |       '- [java]
     |           '- [fixtures]
     |               '- MeinFixture.java
     '- pom.xml
    
  9. Führen Sie den Akzeptanztest als Maven-Integrationstest aus:

    cd \MeinWorkspace\MvnFitTest

    mvn clean integration-test

    Sie erhalten:

    [INFO] [fit:run {execution: fixture}]
    [INFO] Running Fixture with input file src\test\fit\MeinFitTest.html and output file target\fit\MeinFitTest.html
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
    
  10. Sehen Sie sich das Ergebnis als HTML-Seite an (grün bedeutet "ok"):

    start target\fit\MeinFitTest.html

    fixtures.MeinFixture
    wert1wert2berechne()
    0null 
    123.0
    +1-10.0
    -4213-29.0
    1.2345.6786.912
    fit.Summary
    counts 4 right, 0 wrong, 0 ignored, 0 exceptions
  11. Ändern Sie in src\test\fit\MeinFitTest.html einen der Vorgabewerte in der berechne()-Spalte, führen Sie den Akzeptanztest erneut aus und sehen Sie sich die Fehlermeldungen im Kommandozeilenfenster und in der Ergebnis-HTML-Seite an, zum Beispiel so:

    [INFO] [fit:run {execution: fixture}]
    [INFO] Running Fixture with input file src\test\fit\MeinFitTest.html and output file target\fit\MeinFitTest.html
    [INFO] ------------------------------------------------------------------------
    [ERROR] BUILD ERROR
    [INFO] ------------------------------------------------------------------------
    [INFO] Failed to execute FitRunner
    Embedded error: Fixture failed with counts: 3 right, 1 wrong, 0 ignored, 0 exceptions
    
    fixtures.MeinFixture
    wert1wert2berechne()
    0null 
    124.0 expected
    3.0 actual
    +1-10.0
    -4213-29.0
    1.2345.6786.912
    fit.Summary
    counts 3 right, 1 wrong, 0 ignored, 0 exceptions


Automatisierter HTML-Akzeptanztest mit Jetty und Fit

  1. Im letzten Beispiel hat das Fit-Fixture direkt die Berechnungs-Bean angesprochen. Das folgende Beispiel setzt stattdessen auf einer höheren Ebene an und ermittelt das Ergebnis über die HTML-Webseite.

    Voraussetzung für das folgende Beispiel ist die Durchführung der beiden obigen Beispiele Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit und Automatisierter Integrationstest mit Fit.

  2. Erzeugen Sie ein neues Projektverzeichnis MvnHtmlFitTest und kopieren Sie dorthin Teile der beiden Projekte:

    cd \MeinWorkspace

    md MvnHtmlFitTest

    cd MvnHtmlFitTest

    xcopy ..\MvnHtmlIntTest\*.* /S

    xcopy ..\MvnFitTest\src\test\*.* src\test\ /S

    tree /F

  3. Ihre Projektstruktur sieht jetzt so aus:

    [D:\MeinWorkspace\MvnHtmlFitTest]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               '- MeineBean.java
     |   |   '- [webapp]
     |   |       |- [WEB-INF]
     |   |       |   '- web.xml
     |   |       |- BerechnungsFormular.jsp
     |   |       '- index.jsp
     |   '- [test]
     |       |- [fit]
     |       |   '- MeinFitTest.html
     |       '- [java]
     |           |- [de]
     |           |   '- [meinefirma]
     |           |       '- [meinprojekt]
     |           |           '- MeineBeanTest.java
     |           |- [fixtures]
     |           |   '- MeinFixture.java
     |           '- [integrationstest]
     |               |- HtmlUnitTest.java
     |               '- HttpUnitTest.java
     '- pom.xml
    
  4. Fügen Sie aus der pom.xml des MvnFitTest-Projekts sowohl den <plugin>... als auch den <dependency>...-Block ein in die pom.xml des MvnHtmlFitTest-Projekts.

  5. Ändern Sie im MvnHtmlFitTest\src\test\java\fixtures-Verzeichnis die MeinFixture.java zu:

    package fixtures;
    
    public class MeinFixture extends fit.ColumnFixture
    {
       public String wert1;
       public String wert2;
    
       public String berechne() throws Exception
       {
          return (new integrationstest.HtmlUnitTest()).test( wert1, wert2, null );
       }
    }
    

    Statt HtmlUnitTest könnten Sie genauso gut auch HttpUnitTest nehmen.

    Bitte beachten Sie, dass das Fit-Fixture jetzt nicht mehr direkt die Berechnungs-Bean anspricht, sondern über die HTML-Webseite das Ergebnis ermittelt.

  6. Führen Sie die Tests aus:

    cd \MeinWorkspace\MvnHtmlFitTest

    mvn clean integration-test

    start target\fit\MeinFitTest.html

    Sie erhalten die bereits oben gezeigten Erfolgsmeldungen.



Webapp mit Wicket

Das Maven-Archetype-Plugin bietet Unterstützung für Apache Wicket.

Mit Apache Wicket können komponentenbasierte Webanwendungen erstellt werden, inklusive AJAX-Unterstützung. Der Entwickler hat dabei nur mit Java und HTML zu tun und benötigt kein JSP, JSF und JavaScript. Weiteres zu Wicket finden Sie unter http://wicket.apache.org und http://wicket.sourceforge.net/apidocs.

Das folgende Beispiel implementiert wieder das bereits bekannte Webformular, welches wieder über eine MeineBean.berechne()-Methode zwei Zahlen addiert (stellvertretend für Businesslogik).

  1. Generieren Sie ein Wicket-Projekt:

    cd \MeinWorkspace

    mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnWicket

    cd \MeinWorkspace\MvnWicket

    tree /F

    [D:\MeinWorkspace\MvnWicket]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [de]
     |   |   |       '- [meinefirma]
     |   |   |           '- [meinprojekt]
     |   |   |               |- HomePage.html
     |   |   |               |- HomePage.java
     |   |   |               '- WicketApplication.java
     |   |   |- [resources]
     |   |   |   '- log4j.properties
     |   |   '- [webapp]
     |   |       '- [WEB-INF]
     |   |           '- web.xml
     |   '- [test]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       |- Start.java
     |                       '- TestHomePage.java
     '- pom.xml
    
  2. Testen Sie die vorläufige Quickstart-Webanwendung:

    mvn jetty:run

    start http://localhost:8080/MvnWicket

    -->

    Wicket Quickstart Archetype Homepage
    If you see this message wicket is properly configured and running
    

    Beenden Sie Jetty mit "Strg + C".

  3. Ändern Sie Folgendes in der pom.xml:

    Hinter <artifactId>maven-jetty-plugin</artifactId> einfügen:

            <configuration>
              <scanIntervalSeconds>10</scanIntervalSeconds>
            </configuration>
    

    Vor dem </plugins>-Ende-Tag einfügen:

          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>1.6</source>
              <target>1.6</target>
            </configuration>
          </plugin>
    

    Ersetzen Sie <wicket.version>1.3.2</wicket.version> durch:

        <wicket.version>1.4.1</wicket.version>
    
  4. Fügen Sie im MvnWicket\src\main\java\de\meinefirma\meinprojekt-Verzeichnis in der HomePage.html kurz vor dem </body>-Ende-Tag folgende Zeile hinzu:

    <p><wicket:link><a href="BerechnungsFormular.html">Berechnungsformular</a></wicket:link></p>
    
  5. Erzeugen Sie im MvnWicket\src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende BerechnungsFormular.html:

    <html>
    <head><title>Webseite mit Berechnungsformular</title></head>
    <body>
    <h2>Webseite mit Berechnungsformular</h2>
    <form wicket:id="meinFormular" method="post"><pre>
    Wert1 <input wicket:id="wert1"    type="text"   size=10 maxlength=10><br>
    Wert2 <input wicket:id="wert2"    type="text"   size=10 maxlength=10><br>
          <input wicket:id="berechne" type="submit" value="berechne"> <input wicket:id="ergebnis" type="text" size=10 readonly><br>
    </pre></form>
    <span wicket:id="FehlerFeedback"/>
    </body>
    </html>
    
  6. Erzeugen Sie im MvnWicket\src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende BerechnungsFormular.java:

    package de.meinefirma.meinprojekt;
    
    import java.io.Serializable;
    import org.apache.wicket.markup.html.WebPage;
    import org.apache.wicket.markup.html.form.*;
    import org.apache.wicket.markup.html.panel.FeedbackPanel;
    import org.apache.wicket.model.CompoundPropertyModel;
    
    public class BerechnungsFormular extends WebPage
    {
       public BerechnungsFormular()
       {
          Form<MeinDatenModel> form = new Form<MeinDatenModel>( "meinFormular" ) {
             private static final long serialVersionUID = 1L;
             @Override protected void onSubmit() {
                MeinDatenModel datenModel = getModelObject();
                datenModel.ergebnis = (new MeineBean()).berechne( datenModel.wert1, datenModel.wert2 );
             }
          };
          form.setModel( new CompoundPropertyModel<MeinDatenModel>( new MeinDatenModel() ) );
          form.add( new RequiredTextField<Double>( "wert1" ) );
          form.add( new RequiredTextField<Double>( "wert2" ) );
          form.add( new Button( "berechne" ) );
          form.add( new TextField<Double>( "ergebnis" ) );
          add( form );
          add( new FeedbackPanel( "FehlerFeedback" ) );
       }
    
       class MeinDatenModel implements Serializable
       {
          private static final long serialVersionUID = 1L;
          public Double wert1, wert2, ergebnis;
       }
    }
    
  7. Erzeugen Sie im MvnWicket\src\main\java\de\meinefirma\meinprojekt-Verzeichnis MeineBean.java:

    package de.meinefirma.meinprojekt;
    
    public class MeineBean
    {
       public Double berechne( Number d1, Number d2 )
       {
          return ( d1 == null || d2 == null ) ? null
                 : new Double( d1.doubleValue() + d2.doubleValue() );
       }
    }
    
  8. Testen Sie die Wicket-Webanwendung:

    mvn clean jetty:run

    start http://localhost:8080/MvnWicket

    Klicken Sie auf Berechnungsformular, tragen Sie zwei Zahlen ein und lassen Sie die Summe berechnen. Sie können auch Dezimalzahlen eingeben: Dabei müssen Sie als Dezimalpunkt bei deutscher Webbrowser-Einstellung ein Komma (und keinen Punkt) verwenden.

    Lassen Sie ein Feld leer, tragen Sie in das andere Feld Buchstaben ein und sehen Sie sich die Fehlermeldungen an.

    Beenden Sie Jetty mit "Strg + C".



Automatisierter Integrationstest mit Cargo für JBoss, WebLogic und Tomcat

Installation von JBoss (falls Sie JBoss verwenden wollen)

  1. Downloaden Sie den JBoss Application Server von http://jboss.org/jbossas (z.B. jboss-5.1.0.GA-jdk6.zip).
    Entzippen Sie das JBoss-Archive zum Beispiel nach C:\JBoss.

  2. Testen Sie die JBoss-Installation (passen Sie den Pfad zu Ihrer JBoss-Installation an):

    cd C:\JBoss\bin

    run.bat

    Warten Sie bis "... JBoss ... Started ..." erscheint.

    start http://localhost:8080

    Eine JBoss-Übersichtsseite erscheint.

    Beenden Sie JBoss mit "Strg + C".

  3. Weitere Installationshinweise (z.B. Datasource) finden Sie unter jee-ejb.htm#InstallationJBoss.

Installation von Oracle WebLogic 10.3.0 (falls Sie WebLogic verwenden wollen)

  1. Zum Oracle WebLogic 10.3.0 finden Sie eine Installationsbeschreibung unter jee-oracleweblogic-10.3.0.htm#Installation.
    (Mit WebLogic 10.3.2 und dem Cargo-Plugin in der Version 1.0.1-alpha-2 funktioniert die hier gezeigte Konfiguration leider nicht.)

Installation von Tomcat (falls Sie Tomcat verwenden wollen)

  1. Zum Tomcat finden Sie eine Installationsbeschreibung unter jsp-install.htm#InstallationUnterWindows.

Automatisierter Integrationstest mit dem Cargo-Plugin sowie mit HttpUnit und HtmlUnit

  1. Voraussetzung für das folgende Beispiel ist die Installation von JBoss, WebLogic, Tomcat oder eines anderen Servers, den Cargo unterstützt.

  2. Eine weitere Voraussetzung ist die Durchführung des obigen Beispiels Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit.

  3. Erzeugen Sie ein neues Projekt mit dem Projektverzeichnis MvnAppSrvIntTest:

    cd \MeinWorkspace

    md MvnAppSrvIntTest

    cd MvnAppSrvIntTest

    xcopy ..\MvnHtmlIntTest\*.* /S

  4. Ändern Sie Folgendes in der pom.xml:

    Entfernen Sie den gesamten folgenden Jetty-<plugin>-Block (aber nicht den maven-surefire-plugin-Block):

          <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
            ...
          </plugin>
    

    Fügen Sie stattdessen folgenden Cargo-<plugin>-Block ein:

          <plugin>
            <groupId>org.codehaus.cargo</groupId>
            <artifactId>cargo-maven2-plugin</artifactId>
            <configuration>
              <container>
                <containerId>${appsrv.containerId}</containerId>
                <home>${appsrv.srvhome}</home>
              </container>
              <configuration>
                <type>existing</type>
                <home>${appsrv.dmnhome}</home>
                <!--
                <properties>
                  <cargo.remote.username>${appsrv.usr}</cargo.remote.username>
                  <cargo.remote.password>${appsrv.pwd}</cargo.remote.password>
                </properties>
                -->
              </configuration>
              <wait>false</wait>
            </configuration>
            <executions>
              <execution>
                <id>start-container</id>
                <phase>pre-integration-test</phase>
                <goals>
                  <goal>start</goal>
                </goals>
              </execution>
              <execution>
                <id>stop-container</id>
                <phase>post-integration-test</phase>
                <goals>
                  <goal>stop</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
    

    Fügen Sie vor dem </project>-Ende-Tag ein (passen Sie die Pfade an Ihre Application-Server-Installation an):

      <profiles>
        <profile>
          <id>JBoss</id>
          <activation>
            <property>
              <name>env.APPSRV</name>
              <value>JBoss</value>
            </property>
          </activation>
          <properties>
            <appsrv.containerId>jboss5x</appsrv.containerId>
            <appsrv.srvhome>C:\JBoss</appsrv.srvhome>
            <appsrv.dmnhome>${appsrv.srvhome}/server/default</appsrv.dmnhome>
          </properties>
        </profile>
        <profile>
          <id>WebLogic</id>
          <activation>
            <property>
              <name>env.APPSRV</name>
              <value>WebLogic</value>
            </property>
          </activation>
          <properties>
            <appsrv.containerId>weblogic103x</appsrv.containerId>
            <BEA_HOME>C:\WebLogic</BEA_HOME>
            <appsrv.srvhome>${BEA_HOME}\wlserver_10.3</appsrv.srvhome>
            <appsrv.dmnhome>${BEA_HOME}\user_projects\domains\MeineDomain</appsrv.dmnhome>
          </properties>
        </profile>
        <profile>
          <id>Tomcat</id>
          <activation>
            <property>
              <name>env.APPSRV</name>
              <value>Tomcat</value>
            </property>
          </activation>
          <properties>
            <appsrv.containerId>tomcat6x</appsrv.containerId>
            <appsrv.srvhome>D:\Java\Tomcat</appsrv.srvhome>
            <appsrv.dmnhome>${appsrv.srvhome}</appsrv.dmnhome>
          </properties>
        </profile>
      </profiles>
    

    Natürlich können Sie analog auch andere Application Server einbinden.

  5. Lassen Sie sich anzeigen, welche Profile zur Verfügung stehen:

    cd \MeinWorkspace\MvnAppSrvIntTest

    mvn help:all-profiles

  6. Führen Sie für Ihren App-Server (automatisiert) die Schritte des Integrationstests aus (Vorbereitung, App-Server-Start, Deployment, Integrationstest, App-Server-Stopp):

    mvn integration-test -P JBoss

    mvn integration-test -P WebLogic

    mvn integration-test -P Tomcat

    Bitte beachten Sie, dass der App-Server automatisch hoch- und heruntergefahren wird.

    Im Falle des JBoss Application Servers erhalten Sie:

    [INFO] [talledLocalContainer] JBoss 5.1.0 started on port [8080]
    [INFO] [surefire:test {execution: integration-tests}]
    [INFO] Surefire report directory: D:\MeinWorkspace\MvnAppSrvIntTest\target\surefire-reports
    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running integrationstest.HttpUnitTest
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    Running integrationstest.HtmlUnitTest
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    Results :
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
    
  7. Das .war-Archiv wurde in Autodeploy-Verzeichnisse unterhalb von ${appsrv.dmnhome} kopiert:
    Im Falle von JBoss nach: ${appsrv.dmnhome}\deploy,
    im Falle von WebLogic nach: ${appsrv.dmnhome}\autodeploy und
    im Falle von Tomcat nach: ${appsrv.dmnhome}\webapps.

  8. Doku zum Cargo-Maven-Plugin finden Sie unter http://cargo.codehaus.org/Maven2+plugin und http://cargo.codehaus.org/Maven2+Plugin+Reference+Guide.

  9. Für viele Application Server gibt es auch speziell angepasste Maven-Plugins, zum Beispiel für JBoss das jboss-maven-plugin.



Java-EE-Anwendungen (Servlet, JSP, JSF, JPA, EJB3)

Projekte mit Maven 2 für die Java Enterprise Edition finden Sie unter:



OSGi-Bundle mit dem Maven-Bundle-Plugin

OSGi ist ein leistungsfähiges dynamisches Komponentensystem für Java.

Im Folgenden wird anhand eines kurzen Beispiels gezeigt, wie mit dem Maven-Bundle-Plugin des Apache-Felix-Projekts ein OSGi-Bundle erstellt werden kann.

  1. Falls Sie noch kein OSGi installiert haben:
    Sie können wahlweise eine komplette OSGi-Umgebung installieren, zum Beispiel wie für Eclipse Equinox beschrieben.
    Für dieses einfache Beispiel genügt es, wenn Sie alternativ lediglich eine einzelne OSGi-jar-Bibliothek downloaden und in Ihr Projektverzeichnis kopieren, zum Beispiel für das "Equinox OSGi R4 Framework" die org.eclipse.osgi_3.5.1.R35x_v20090827.jar von http://download.eclipse.org/equinox.

  2. Legen Sie ein Projektverzeichnis MvnOsgi an und erzeugen Sie darin folgende Verzeichnisstruktur:

    [D:\MeinWorkspace\MvnOsgi]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     '- org.eclipse.osgi_3.5.1.R35x_v20090827.jar  [falls keine andere OSGi-Installation]
    
  3. Erzeugen Sie im MvnOsgi\src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende Bundle-Java-Klasse Activator.java:

    package de.meinefirma.meinprojekt;
    
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    
    public class Activator implements BundleActivator
    {
       public void start( BundleContext context ) throws Exception
       {
          System.out.println( context.getBundle().getSymbolicName() +  ": Hallo OSGi-Welt." );
       }
    
       public void stop( BundleContext context ) throws Exception
       {
          System.out.println( context.getBundle().getSymbolicName() +  ": Tschau OSGi-Welt." );
       }
    }
    
  4. Erzeugen Sie im MvnOsgi-Projektverzeichnis folgende pom.xml:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>MvnOsgi</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>bundle</packaging>
      <name>MvnOsgi</name>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.felix</groupId>
            <artifactId>maven-bundle-plugin</artifactId>
            <extensions>true</extensions>
            <configuration>
              <instructions>
                <!-- Falls Sie etwas zu exportieren haben:
                <Export-Package>de.meinefirma.meinprojekt.intf...</Export-Package> -->
                <Private-Package>de.meinefirma.meinprojekt.*</Private-Package>
                <Bundle-Activator>de.meinefirma.meinprojekt.Activator</Bundle-Activator>
              </instructions>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>org.apache.felix</groupId>
          <artifactId>org.osgi.core</artifactId>
          <version>1.2.0</version>
        </dependency>
      </dependencies>
    </project>
    
  5. Ihre Projektverzeichnisstruktur sieht jetzt so aus:

    tree /F

    [D:\MeinWorkspace\MvnOsgi]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [de]
     |               '- [meinefirma]
     |                   '- [meinprojekt]
     |                       '- Activator.java
     |- org.eclipse.osgi_3.5.1.R35x_v20090827.jar  [falls keine andere OSGi-Installation]
     '- pom.xml
    
  6. Geben Sie im Kommandozeilenfenster (und in der OSGi-Konsole) Folgendes ein (passen Sie den OSGi-Dateinamen an):

    cd D:\MeinWorkspace\MvnOsgi

    mvn package

    java -jar org.eclipse.osgi_3.5.1.R35x_v20090827.jar -console -clean

    install file:target\MvnOsgi-1.0-SNAPSHOT.jar start

    ss

    uninstall 1

    close

    Sie erhalten:

    osgi> install file:target\MvnOsgi-1.0-SNAPSHOT.jar start
    Bundle id is 1
    de.meinefirma.meinprojekt.MvnOsgi: Hallo OSGi-Welt.
    osgi> ss
       id   State    Bundle
        0   ACTIVE   org.eclipse.osgi_3.5.1.R35x_v20090827
        1   ACTIVE   de.meinefirma.meinprojekt.MvnOsgi_1.0.0.SNAPSHOT
    osgi> uninstall 1
    de.meinefirma.meinprojekt.MvnOsgi: Tschau OSGi-Welt.
    osgi> close
    
  7. Falls Sie nicht die org.eclipse.osgi_3.5.1.R35x_v20090827.jar downgeloaded haben, sondern eine andere OSGi-Installation verwenden wollen, müssen Sie das java...-Kommando entsprechend ändern, zum Beispiel für eine Installation entsprechend der Beschreibung zu Eclipse Equinox folgendermaßen (passen Sie den Pfad und den OSGi-Dateinamen an):

    java -jar D:\Java\eclipse-equinox-SDK-3.4.2\eclipse\plugins\org.eclipse.osgi_3.4.3.R34x_v20081215-1030.jar -console -clean

  8. Erläuterungen finden Sie beim nahezu identischen OSGi-HelloWorld-Programmierbeispiel. Dort sind auch weitere ausführbare Kommandos und weitere OSGi-Programmierbeispiele aufgeführt.



Maven-Repository

Zugang zu Internet-Repositories über Firmen-Proxy

In Firmennetzwerken ist häufig der direkte Internetzugang für Tools wie Maven gesperrt. Um dennoch benötigte Libs aus den Maven-Repositories im Internet verwenden zu können, kann der Cntlm Authentication Proxy verwendet werden (wenn die IT-Abteilung das erlaubt). Er steht für verschiedene Betriebssysteme zur Verfügung. Hier folgt eine Kurzanleitung für Windows-Workstations, die übers Firmennetzwerk nur über eine NTLM-Authentifizierung ins Internet gelangen.

Installieren Sie Cntlm wie beschrieben unter Eclipse-Updates über Firmen-Firewalls mit dem Cntlm Authentication Proxy
(die Eclipse-spezifischen Einstellungen können Sie weglassen).

Erweitern Sie Ihre lokale %M2_HOME%\conf\settings.xml zum Beispiel so (die Portnummer muss dieselbe wie bei Cntlm sein):

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <localRepository>C:\Java\m2\repository</localRepository>

  <proxies>
   <proxy>
      <active>true</active>
      <protocol>http</protocol>
      <host>127.0.0.1</host>
      <port>3128</port>
    </proxy>
  </proxies>

</settings>

Team-Repository über freigegebenes Verzeichnis

Wenn nur ein festes Repertoire an Libs mehreren Entwicklern zur Verfügung gestellt werden soll, können Sie ein Maven-Repository als einfaches Verzeichnis einstellen. Allerdings werden so keine fehlenden Libs übers Internet nachgeladen.

Erweitern Sie Ihre lokale settings.xml zum Beispiel so:

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <localRepository>C:\Java\m2\repository</localRepository>

  <mirrors>
    <mirror>
      <id>MeineMirrorId</id>
      <mirrorOf>central</mirrorOf>
      <url>file://\\<MeinPC>\<MeinFreigabeName>\m2-Central-Repository</url>
    </mirror>
  </mirrors>

</settings>

Repository-Manager

Falls ein Repository-Manager (im Beispiel: Archiva) installiert ist, können Sie ihn über folgenden Eintrag in der settings.xml einstellen:

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <localRepository>C:\Java\m2\repository</localRepository>

  <mirrors>
    <mirror>
      <id>archiva</id>
      <mirrorOf>*</mirrorOf>
      <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/internal</url>
    </mirror>
  </mirrors>

</settings>

Installation und Konfiguration des Repository-Managers Archiva

Archiva ist einer der bekanntesten Repository-Manager. Installieren Sie ihn wie beschrieben unter System Administrators Guide to Apache Archiva.
Einige eventuell benötigte Schritte sind auch im Folgenden aufgeführt.

  1. Downloaden Sie apache-archiva-1.3-bin.zip von http://archiva.apache.org/download.html und entzippen Sie die Datei, zum Beispiel nach C:\Java.
  2. Suchen Sie in C:\Java\apache-archiva-1.3\conf\jetty.xml nach "jetty.port" und setzen Sie bei default="8080" eine andere Portnummer ein, beispielsweise "4422".
    (Freie Portnummern finden Sie unter http://www.iana.org/assignments/port-numbers.)
  3. Starten Sie Archiva über:

    C:\Java\apache-archiva-1.3\bin\archiva.bat console

    Wenn "Started SelectChannelConnector@0.0.0.0:4422" erscheint, starten Sie die Archiva-Weboberfläche:

    start http://localhost:4422/archiva

  4. Legen Sie einen Admin-Account an.
  5. Falls Sie in einem Firmennetzwerk hinter einem Proxy sitzen und den Cntlm Authentication Proxy installiert haben, legen Sie in Archiva einen Network Proxy an. Klicken Sie in der linken Spalte unter "Administration" auf "Network Proxies" und dann auf "Add Network Proxy" und tragen Sie ein:
    Identifier: MeineNetworkProxyID
    Protocol:http
    Hostname: 127.0.0.1
    Port:3128
    Username: 
    Password:  
    Save Network Proxy.
    In der C:\Java\apache-archiva-1.3\conf\archiva.xml entsteht der Eintrag:
      <networkProxies>
        <networkProxy>
          <id>MeineNetworkProxyID</id>
          <host>127.0.0.1</host>
          <port>3128</port>
          <username/>
          <password/>
        </networkProxy>
      </networkProxies>
    
  6. Tragen Sie den Network Proxy in alle Proxy Connectors ein. Klicken Sie in der linken Spalte auf "Proxy Connectors" und tragen Sie dann bei allen Proxy Connectoren über "Edit Proxy Connector" ein:
    Network Proxy: MeineNetworkProxyID
    Save Proxy Connector.
    In der C:\Java\apache-archiva-1.3\conf\archiva.xml entstehen die Einträge:
        <proxyConnector>
          ...
          <proxyId>MeineNetworkProxyID</proxyId>
          ...
        </proxyConnector>
    
  7. Fügen Sie folgendes Repository hinzu. Klicken Sie in der linken Spalte auf "Repositories" und dann rechts unter "Remote Repositories" auf "Add" und tragen Sie ein:
    Identifier: maven1-repository.dev.java.net
    Name: Java.net Repository for Maven 1
    URL: http://download.java.net/maven/1/
    Type: Maven 1.x Repository
    Add Repository.
  8. Fügen Sie einen Proxy Connector für das "Java.net Repository for Maven 1" hinzu: Klicken Sie in der linken Spalte auf "Proxy Connectors" und dann auf "Add" und tragen Sie ein:
    Network Proxy: MeineNetworkProxyID
    Managed Repository: internal
    Remote Repository: maven1-repository.dev.java.net
    Cache failures: yes
    Releases: once
    Checksum: fix
    Snapshots: never
    Add Proxy Connector.
  9. Erweitern Sie auf dem Entwickler-PC die lokale %M2_HOME%\conf\settings.xml wie oben beschrieben und testen Sie, ob Sie mit dem Archiva Repository ihre Maven-Projekte bauen können.
  10. Stoppen Sie Archiva mit "Strg+C".
  11. Installieren Sie Archiva als Hintergrund-Dienst:

    C:\Java\apache-archiva-1.3\bin\archiva.bat install

    Wenn Sie jetzt Windows neu starten, läuft Archiva automatisch als Dienst. Steuern können Sie dies unter:
    'Start' | 'Systemsteuerung' | 'System und Wartung' | 'Verwaltung' | 'Dienste'
    (oder alternativ: rechter Mausklick auf 'Computer' bzw. 'Arbeitsplatz' | 'Verwalten' | 'Dienste und Anwendungen' | 'Dienste').

    Für kleine Teams in Firmen kann praktisch sein, dass der so installierte Archiva-Dienst bereits funktionsfähig und von anderen PCs aus erreichbar läuft, ohne dass sich jemand auf dem Archiva-Server im Firmennetzwerk anmelden muss.





Weitere Themen: andere TechDocs | maven.apache.org | Glossar
© 2010 Torsten Horn, Aachen