Maven ist (ähnlich wie Ant und Gradle) 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 Multimodulprojekte geeignet.
Im Folgenden wird nur auf die veraltete Version Maven 2.2.1 eingegangen. Für aktuellere Versionen siehe Maven 3. Informationen zur Vorgängerversion finden Sie unter Maven 1.
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:
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:
Kommando | Bedeutung |
---|---|
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 |
Maven-Basisinstallation
Setzen Sie folgende Umgebungsvariablen (Environment-Variablen)
(passen Sie die Pfadangaben an Ihre Java- und Maven-Verzeichnisse an)
(unter Windows 7 und Vista: 'WindowsTaste + PauseTaste' | 'Erweiterte Systemeinstellungen' | Reiter 'Erweitert' | 'Umgebungsvariablen...';
unter Windows XP: 'WindowsTaste + PauseTaste' | 'Erweitert' | 'Umgebungsvariablen';
unter Linux: siehe linux.htm#Umgebungsvariablen):
Benutzervariablen setzen: | ||
JAVA_HOME | C:\Program Files\Java\jdk1.6 | |
M2_HOME | D:\Tools\Maven2 | [bzw. D:\Tools\Maven3] |
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).
Jetzt müssen folgende Kommandos ein erfolgreiches Ergebnis liefern. Öffnen Sie (in Windows 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
Konfiguration
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.
Wenn Sie die Voreinstellung nicht ändern, wird als lokales Maven-Repository-Verzeichnis je nach Betriebssystem zum Beispiel C:\Users\%USERNAME%\.m2\repository, C:\Dokumente und Einstellungen\%USERNAME%\.m2\repository, <user-home>/.m2/repository oder ~/.m2/repository verwendet.
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.
Maven in Firmen
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
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_REPO | C:\Users\%USERNAME%\.m2\repository |
Anschließend müssen Sie Eclipse neu starten.
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
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.
Weiteres zu Eclipse mit Maven finden Sie hier.
Falls Sie zusätzliche Unterstützung in Eclipse zu Maven wünschen, können Sie das M2Eclipse-Plugin testen, siehe hierzu: java-eclipse.htm#M2Eclipse.
Ö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 MvnHelloApp
tree /F
Sie erhalten:
[\MeinWorkspace\MvnHelloApp] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- App.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml
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!
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
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.
Im letzten Beispiel wurde direkt die kompilierte Klasse App.class ausgeführt. Im folgenden Beispiel wird das letzte Beispiel so erweitert, dass die kompilierten Klassen mit dem Maven JAR Plugin zu einem ausführbaren Jar-Archiv zusammengefasst werden, inklusive einer automatisch generierten MANIFEST.MF mit geignetem Main-Class-Eintrag.
Ersetzen Sie im MvnHelloApp-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>MvnHelloApp</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MvnHelloApp</name> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.1</version> <configuration> <archive> <manifest> <mainClass>de.meinefirma.meinprojekt.App</mainClass> <addClasspath>true</addClasspath> </manifest> </archive> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> </dependencies> </project>
Führen Sie die Jar-Datei im Kommandozeilenfenster aus:
cd \MeinWorkspace\MvnHelloApp
mvn package
java -jar target/MvnHelloApp-1.0-SNAPSHOT.jar
Sie erhalten wieder:
Hello World!
Sie können auch weiterhin die App.class direkt ausführen (wie im obigen Beispiel):
java -cp target/MvnHelloApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App
Sehen Sie sich die generierte MANIFEST.MF mit dem Main-Class-Eintrag an:
cd \MeinWorkspace\MvnHelloApp
jar xvf target/MvnHelloApp-1.0-SNAPSHOT.jar META-INF/MANIFEST.MF
type META-INF\MANIFEST.MF
Sie enthält die Zeile:
Main-Class: de.meinefirma.meinprojekt.App
Übrigens kann das maven-jar-plugin noch mehr, beispielsweise:
Falls Sie die Attribute in der MANIFEST.MF zur Laufzeit auslesen wollen, ersetzen Sie den Inhalt der App.java durch:
package de.meinefirma.meinprojekt; import java.io.File; import java.util.jar.*; public class App { public static void main( String[] args ) throws Exception { System.out.println( "\nManifest-Attribute:\n" + getJarManifestAttributes( App.class ).entrySet() ); } public static Attributes getJarManifestAttributes( Class<?> clazz ) throws Exception { JarFile jarFile = new JarFile( new File( clazz.getProtectionDomain().getCodeSource().getLocation().toURI() ) ); Manifest manifest = jarFile.getManifest(); jarFile.close(); return manifest.getMainAttributes(); } }
Und führen Sie aus:
mvn package
java -jar target/MvnHelloApp-1.0-SNAPSHOT.jar
Im folgenden Beispiel besteht die Anwendung aus einer Klasse, die eine zusätzliche .jar-Bibliothek benötigt. Die kompilierten Klassen, die benötigten .jar-Libs und eine automatisch generierte MANIFEST.MF werden mit dem Assembly Plugin in ein ausführbares Jar gepackt.
Starten Sie ein neues Projekt:
cd \MeinWorkspace
mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJarMitLibs
cd MvnJarMitLibs
Ersetzen Sie im 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.
Ersetzen Sie im 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 ); } }
Erzeugen Sie im 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
Die Abhängigkeit zur log4j-1.2.16.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> <version>2.2.1</version> <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.16</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</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.
Sehen Sie sich mit tree /F die Verzeichnisstruktur an:
[\MeinWorkspace\MvnJarMitLibs] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- App.java | | '- [resources] | | '- log4j.properties | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml
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.16.jar im Classpath und der Log4j-Logger funktioniert.
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.16.jar-Lib fehlt, erhalten Sie eine Exception:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/...
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! ----
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).
Sehen Sie sich die Hilfstexte zu den Goals des Assembly-Plugins an:
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-assembly-plugin
Ö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 MvnJettyWebApp
tree /F
Sie erhalten:
[\MeinWorkspace\MvnJettyWebApp] |- [src] | '- [main] | |- [resources] | '- [webapp] | |- [WEB-INF] | | '- web.xml | '- index.jsp '- pom.xml
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>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> </plugin> </plugins> </build> </project>
Ausführung des Projekts:
cd \MeinWorkspace\MvnJettyWebApp
mvn jetty:run
Warten Sie, bis "Started Jetty Server" erscheint, und rufen Sie dann im Webbrowser auf:
http://localhost:8080/MvnJettyWebApp
Sie erhalten eine Webseite mit:
Hello World!
Beenden Sie Jetty mit "Strg + C".
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.
Dieses Beispiel demonstriert Folgendes:
Führen Sie folgende Schritte durch:
Ö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=MvnWebManifest
cd MvnWebManifest
tree /F
Sie erhalten:
[\MeinWorkspace\MvnWebManifest] |- [src] | '- [main] | |- [resources] | '- [webapp] | |- [WEB-INF] | | '- web.xml | '- index.jsp '- pom.xml
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>MvnWebManifest</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>MvnWebManifest</name> <url>http://www.meinefirma.de</url> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <!-- <packagingExcludes>, um folgende Warnung mit der 2.1.1-Version zu vermeiden: [WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ignored (webxml attribute is missing from war task, or ignoreWebxml attribute is specified as 'true') --> <packagingExcludes>WEB-INF/web.xml</packagingExcludes> <archive> <manifest> <!-- Default-Properties: --> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> </manifest> <manifestEntries> <!-- Maven-Properties: --> <Projektname>${project.name}</Projektname> <Projektversion>${project.version}</Projektversion> <!-- Andere Properties, z.B. von Hudson oder per Kommandozeile gesetzt: --> <Projektbuild>${BUILD_TAG}, ${BUILD_ID}</Projektbuild> <MeinSpezProp>${MeinSpezProp}</MeinSpezProp> </manifestEntries> </archive> </configuration> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> </plugin> </plugins> </build> </project>
Ersetzen Sie im src\main\webapp-Verzeichnis den Inhalt der index.jsp durch:
<%@ page import="java.io.*" %> <%@ page import="java.util.jar.*" %> <html> <body> <h2>Hello World!</h2> <% String pth = getServletConfig().getServletContext().getRealPath( "/" ); if( !pth.endsWith( "/" ) && !pth.endsWith( "\\" ) ) pth += "/"; File mf = new File( pth + "META-INF/MANIFEST.MF" ); if( mf.exists() ) { Manifest manifest = new Manifest( new FileInputStream( mf ) ); Attributes attr = manifest.getMainAttributes(); out.println( "<h4>Attribute in der META-INF/MANIFEST.MF:</h4>" + attr.entrySet() ); } %> </body> </html>
Ausführung des Projekts:
cd \MeinWorkspace\MvnWebManifest
mvn clean war:manifest war:war -DMeinSpezProp=AbcXyz
type src\main\webapp\META-INF\MANIFEST.MF
mvn jetty:run
Warten Sie, bis "Started Jetty Server" erscheint, und rufen Sie dann im Webbrowser auf:
http://localhost:8080/MvnWebManifest
Sie erhalten eine Webseite mit der Auflistung der Attribute in der MANIFEST.MF inklusive des per Kommandozeile angegebenen Attributs MeinSpezProp=AbcXyz (die BUILD_...-Properties könnten z.B. durch Hudson gesetzt werden).
Beenden Sie Jetty mit "Strg + C".
Sie können die resultierende WAR-Datei target\MvnWebManifest.war auch in beliebige andere Web Application Server (z.B. Tomcat) deployen.
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).
Ö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
cd MvnPropApp
Ersetzen Sie in 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" ); } }
Ersetzen Sie in 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.
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.8.1</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).
Erzeugen Sie unterhalb von src\main ein Unterverzeichnis mit dem Namen filters.
Erzeugen Sie in diesem Unterverzeichnis die Datei filter.properties mit folgendem Inhalt:
MeinProp=PropAusFilter MeinFilterProp=PropAusFilter
Erzeugen Sie unterhalb von 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}
Erzeugen Sie unterhalb von src\test ein Unterverzeichnis mit dem Namen resources und erzeugen Sie darin die Datei MeineTestProps.properties mit folgendem Inhalt:
MeinPomProp=PropAusTestProps
Lassen Sie sich die Verzeichnisstruktur anzeigen:
cd \MeinWorkspace\MvnPropApp
tree /F
-->
[\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
Um den Build durchzuführen und das entstandene Programm inklusive eines Kommandozeilenparameters ("-D...") auszuführen, geben Sie im Kommandozeilenfenster ein:
cd \MeinWorkspace\MvnPropApp
mvn clean package "-DMeinCmdProp=PropVonCmd"
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_24 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 wird etwa ${settings.localRepository} unter Maven 2.2.1 nicht aufgelöst, wenn dieser Wert in keiner settings.xml definiert ist. Trotzdem ist dieser Wert natürlich verfügbar, wie Sie über "mvn help:evaluate" leicht ermitteln können. Maven 3.0 löst diesen Wert auch ohne Eintrag in einer settings.xml auf.
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 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"/>).
Sehen Sie nach, was in der resultierenden Jar-Datei enthalten ist:
cd \MeinWorkspace\MvnPropApp
mvn package "-DMeinCmdProp=PropVonCmd"
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.
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 definiert werden. Unter Maven 2.2.1 war zusätzlich auch die Definition von Profilen in einer profiles.xml-Datei möglich, was mit Maven 3.0 nicht mehr möglich ist (siehe Maven 3.x Compatibility Notes).
In der settings.xml 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).
Sie können dieses und die meisten der folgenden Programmierbeispiele wahlweise downloaden oder, wie im Folgenden beschrieben wird, schrittweise ausführen.
Voraussetzung ist, dass Sie das obige Beispiel Maven-Properties-Projekt (im Folgenden "MvnPropApp" genannt) erfolgreich durchgeführt haben.
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>
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>
Ergänzen Sie in der Properties-Datei 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}
Ö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 | sort
--> 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 | sort
--> Maven 2.2: Die appsrv...- und db...-Properties sind für "Entwicklung" gesetzt.
--> Maven 3.0: Die appsrv...- und db...-Properties sind sind weiterhin nicht gesetzt,
weil Maven 3.0 profiles.xml nicht berücksichtigt.
mvn clean package "-Denv.UMGEBUNG=Integrationstest"
java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort
--> Die appsrv...- und db...-Properties sind für "Integrationstest" gesetzt
(die set-Umgebungsvariable wird durch den Kommandozeilenparameter überschrieben).
Statt des Kommandozeilenparameters können Sie auch die Umgebungsvariable setzen:
set UMGEBUNG=Integrationstest
mvn clean package
java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort
Sie erhalten eine Ausgabe wie oben gezeigt, aber je nach gesetztem env.UMGEBUNG-Property-Wert ergänzt um beispielsweise:
appsrv.pwd=geheim appsrv.url=Url.zum.IntTest.Appserver appsrv.usr=IntTest-AppServer-User db.pwd=supergeheim db.url=Url.zur.IntTest.Datenbank db.usr=IntTest-DB-User env.UMGEBUNG=Integrationstest
Alternativ können Sie die verschiedenen Profile auch über die -P-Option aktivieren:
set UMGEBUNG=
mvn package
java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort
--> Die appsrv...- und db...-Properties sind nicht gesetzt.
mvn -P IntTest package
java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort
--> Die appsrv...- und db...-Properties sind für "Integrationstest" gesetzt.
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
Active Profiles: There are no active profiles.
Sowohl
mvn help:active-profiles "-Denv.UMGEBUNG=Integrationstest"
als auch
mvn help:active-profiles -P IntTest
führen zu:
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=Integrationstest"
Das folgende Beispiel demonstriert Verschiedenes:
Aus einer XML-Schema-Definitions-Datei (XSD-Datei) wird Java-Code generiert.
Falls Sie sich nicht mit XML-Schema-Definitionen auskennen:
Sehen Sie sich die Einführungen unter
java-xsd.htm und
http://de.wikipedia.org/wiki/XML_Schema an
sowie die
W3C XML Schema Specification
entweder im englischen Original oder ins deutsche übersetzt unter
Einführung,
Strukturen und
Datentypen.
Als Codegenerator wird JAXB 2 verwendet. Siehe hierzu: Generierung von Java-Code aus einem XSD-Schema, JSR 222 und JAXB 2.x.
Die Einbindung in Maven erfolgt über das maven-jaxb2-plugin.
Da die JAXB2-Implementierung und das JAXB2-Maven-Plugin aus lizenzrechtlichen Gründen nicht im üblichen Maven Remote Repository vorliegen, sondern im Maven Repository von Sun, wird gezeigt, wie zusätzliche Repositories in der pom.xml eingebunden werden, und zwar einerseits sowohl für Libraries als auch Plug-ins und andererseits sowohl für Maven-1- als auch Maven-2-Artefakte (deshalb ist die pom.xml diesmal recht lang).
Da JAXB 2 mindestens Java 5 voraussetzt, wird außerdem gezeigt, wie dies dem maven-compiler-plugin mitgeteilt wird (über <configuration>...1.5...).
Ö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 MvnJaxbApp
tree /F
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.3</version> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <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.2.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</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>
Ersetzen Sie den Inhalt von 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() ); } }
Erzeugen Sie unterhalb von 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>
Sehen Sie sich mit tree /F die Verzeichnisstruktur an:
[\MeinWorkspace\MvnJaxbApp] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- App.java | | '- [resources] | | '- buecherliste.xsd | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml
Bauen Sie das Projekt und sehen Sie sich das Ergebnis im target-Verzeichnis an:
cd \MeinWorkspace\MvnJaxbApp
mvn package
tree /F
Sie erhalten:
[\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.
Beispiele für RESTful-Webservices mit JAX-RS finden Sie unter jee-rest.htm#JaxRsMitMaven.
Das folgende Beispiel zeigt die Implementierung eines SOAP-Webservices mit JAX-WS inklusive Server und Client, allerdings nur in einer Quick-and-Dirty-Minimalversion. Weiterführende Hinweise finden Sie unter jee-jax-ws.htm. 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.
Ö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 MvnJaxWs
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> <version>2.3.2</version> <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.
Löschen Sie im src-Verzeichnis das gesamte test-Unterverzeichnis.
Löschen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt die App.java.
Legen Sie im Verzeichnis 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; } }
TestWsServer.java:
package de.meinefirma.meinprojekt; import javax.xml.ws.Endpoint; public class TestWsServer { public static void main( final String[] args ) { String url = ( args.length > 0 ) ? args[0] : "http://localhost:8080/test"; Endpoint.publish( url, new HalloWeltImpl() ); } }
TestWsClient.java:
package de.meinefirma.meinprojekt; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.ws.Service; public class TestWsClient { 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] : "" ) ); } }
Die simplifizierte Projektstruktur sieht so aus:
cd \MeinWorkspace\MvnJaxWs
tree /F
[\MeinWorkspace\MvnJaxWs] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | |- HalloWelt.java | |- HalloWeltImpl.java | |- TestWsClient.java | '- TestWsServer.java '- pom.xml
Ö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.TestWsServer http://localhost:8080/test
Ö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.TestWsClient http://localhost:8080/test ich
Sie erhalten:
Hallo ich
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.
Sie können auch andere URLs beim TestWsServer- und TestWsClient-Aufruf übergeben, beispielsweise http://localhost:4711/xyz.
Beenden Sie den Webservice-Server mit "Strg + C".
Sehen Sie sich die Webservice-Kommunikation mit soapUI an (siehe soapUI-Screenshot).
Das folgende Beispiel demonstriert:
Folgendermaßen führen Sie das Beispiel aus:
Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine neue Projektstruktur:
cd \MeinWorkspace
md MvnFtp\src\test\java\de\meinefirma\meinprojekt
cd MvnFtp
Erstellen Sie im MvnFtp-Projektverzeichnis die Projektkonfiguration: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnFtp</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MvnFtp</name> <dependencies> <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>1.4.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.ftpserver</groupId> <artifactId>ftpserver-core</artifactId> <version>1.0.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.mina</groupId> <artifactId>mina-core</artifactId> <version>2.0.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> </dependencies> </project>
Erstellen Sie im MvnFtp-Projektverzeichnis eine Testtextdatei: TestSrc.txt
Mein Text.
Erstellen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis die FTP-Utilility-Klasse: FtpUtils.java
package de.meinefirma.meinprojekt; import java.io.*; import java.net.SocketException; import java.util.*; import org.apache.commons.net.ftp.FTPClient; import org.apache.ftpserver.*; import org.apache.ftpserver.ftplet.*; import org.apache.ftpserver.listener.ListenerFactory; import org.apache.ftpserver.usermanager.*; import org.apache.ftpserver.usermanager.impl.*; /** * FTP-Utilities, basierend auf:<br> * Apache FTPClient: * {@link "http://commons.apache.org/net/api/org/apache/commons/net/ftp/FTPClient.html"} * Apache FtpServer: * {@link "http://www.jarvana.com/jarvana/view/org/apache/ftpserver/ftpserver-core/1.0.4/ftpserver-core-1.0.4-javadoc.jar!/org/apache/ftpserver/FtpServer.html"} */ public class FtpUtils { /** * Erzeuge FTP-Server * @param ftpPort FTP-Port, z.B. 2121 * @param ftpHomeDir FTP-Verzeichnis, z.B. "target/FtpHome" * @param readUserName leseberechtigter Benutzer: Name * @param readUserPwd leseberechtigter Benutzer: Passwort * @param writeUserName schreibberechtigter Benutzer: Name * @param writeUserPwd schreibberechtigter Benutzer: Passwort * @param ftpUsersPropsFile kann null sein, oder z.B. "target/FtpUsers.properties" */ public static FtpServer createFtpServer( int ftpPort, String ftpHomeDir, String readUserName, String readUserPwd, String writeUserName, String writeUserPwd, String ftpUsersPropsFile ) throws FtpException, IOException { File fhd = new File( ftpHomeDir ); if( !fhd.exists() ) fhd.mkdirs(); ListenerFactory listenerFactory = new ListenerFactory(); listenerFactory.setPort( ftpPort ); PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory(); userManagerFactory.setPasswordEncryptor( new SaltedPasswordEncryptor() ); if( ftpUsersPropsFile != null && ftpUsersPropsFile.trim().length() > 0 ) { File upf = new File( ftpUsersPropsFile ); if( !upf.exists() ) upf.createNewFile(); userManagerFactory.setFile( upf ); } // Einen Nur-Lese-User und einen User mit Schreibberechtigung anlegen: UserManager userManager = userManagerFactory.createUserManager(); BaseUser userRd = new BaseUser(); BaseUser userWr = new BaseUser(); userRd.setName( readUserName ); userRd.setPassword( readUserPwd ); userRd.setHomeDirectory( ftpHomeDir ); userWr.setName( writeUserName ); userWr.setPassword( writeUserPwd ); userWr.setHomeDirectory( ftpHomeDir ); List<Authority> authorities = new ArrayList<Authority>(); authorities.add( new WritePermission() ); userWr.setAuthorities( authorities ); userManager.save( userRd ); userManager.save( userWr ); FtpServerFactory serverFactory = new FtpServerFactory(); serverFactory.addListener( "default", listenerFactory.createListener() ); serverFactory.setUserManager( userManager ); return serverFactory.createServer(); } /** * FTP-Client-Upload * @return true falls ok */ public static boolean upload( String localSourceFile, String remoteResultFile, String host, int port, String usr, String pwd, boolean showMessages ) throws SocketException, IOException { FTPClient ftpClient = new FTPClient(); FileInputStream fis = null; boolean resultOk = true; try { ftpClient.connect( host, port ); if( showMessages ) System.out.println( ftpClient.getReplyString() ); resultOk &= ftpClient.login( usr, pwd ); if( showMessages ) System.out.println( ftpClient.getReplyString() ); fis = new FileInputStream( localSourceFile ); resultOk &= ftpClient.storeFile( remoteResultFile, fis ); if( showMessages ) System.out.println( ftpClient.getReplyString() ); resultOk &= ftpClient.logout(); if( showMessages ) System.out.println( ftpClient.getReplyString() ); } finally { try { if( fis != null ) fis.close(); } catch( IOException e ) {/*ok*/} ftpClient.disconnect(); } return resultOk; } /** * FTP-Client-Download * @return true falls ok */ public static boolean download( String localResultFile, String remoteSourceFile, String host, int port, String usr, String pwd, boolean showMessages ) throws SocketException, IOException { FTPClient ftpClient = new FTPClient(); FileOutputStream fos = null; boolean resultOk = true; try { ftpClient.connect( host, port ); if( showMessages ) System.out.println( ftpClient.getReplyString() ); resultOk &= ftpClient.login( usr, pwd ); if( showMessages ) System.out.println( ftpClient.getReplyString() ); fos = new FileOutputStream( localResultFile ); resultOk &= ftpClient.retrieveFile( remoteSourceFile, fos ); if( showMessages ) System.out.println( ftpClient.getReplyString() ); resultOk &= ftpClient.logout(); if( showMessages ) System.out.println( ftpClient.getReplyString() ); } finally { try { if( fos != null ) fos.close(); } catch( IOException e ) {/*ok*/} ftpClient.disconnect(); } return resultOk; } /** * FTP-Dateienliste * @return String-Array der Dateinamen auf dem FTP-Server */ public static String[] list( String host, int port, String usr, String pwd ) throws SocketException, IOException { FTPClient ftpClient = new FTPClient(); String[] filenameList; try { ftpClient.connect( host, port ); ftpClient.login( usr, pwd ); filenameList = ftpClient.listNames(); ftpClient.logout(); } finally { ftpClient.disconnect(); } return filenameList; } }
Erstellen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis den FTP-JUnit-Test: FtpTest.java
package de.meinefirma.meinprojekt; import java.io.*; import java.util.Arrays; import org.apache.ftpserver.FtpServer; import org.apache.ftpserver.ftplet.FtpException; import org.junit.*; public class FtpTest { private static final int FTP_PORT = 2121; private static final String FTP_HOST = "localhost"; private static final String FTPHOME_DIR = "target/FtpHome"; private static final String FTPUSERSPROPS_FILE = "target/FtpUsers.properties"; private static final String READ_USER_NAME = "ReadUserName"; private static final String READ_USER_PWD = "ReadUserPwd"; private static final String WRITE_USER_NAME = "WriteUserName"; private static final String WRITE_USER_PWD = "WriteUserPwd"; private static FtpServer ftpServer; @BeforeClass public static void startFtpServer() throws FtpException, IOException { ftpServer = FtpUtils.createFtpServer( FTP_PORT, FTPHOME_DIR, READ_USER_NAME, READ_USER_PWD, WRITE_USER_NAME, WRITE_USER_PWD, FTPUSERSPROPS_FILE ); ftpServer.start(); } @AfterClass public static void stoppFtpServer() { // Um den FTP-Server von ausserhalb des Tests eine Zeit lang ueber // ftp://WriteUserName:WriteUserPwd@localhost:2121 // anzusprechen, kann folgende Zeile aktiviert werden: // try { Thread.sleep( 55000 ); } catch( InterruptedException e ) {/*ok*/} ftpServer.stop(); ftpServer = null; } @Test public void testFtp() throws IOException { final String LOCAL_SRC_FILE = "TestSrc.txt"; final String LOCAL_DST_FILE = "target/TestDst.txt"; final String REMOTE_FILE = "Test.txt"; File testFile = new File( LOCAL_SRC_FILE ); if( !testFile.exists() ) testFile.createNewFile(); // Upload-Versuch ohne Schreibberechtigung muss fehlschlagen: Assert.assertFalse( "READ_USER", FtpUtils.upload( LOCAL_SRC_FILE, REMOTE_FILE, FTP_HOST, FTP_PORT, READ_USER_NAME, READ_USER_PWD, false ) ); // Teste Upload mit Schreibberechtigung: Assert.assertTrue( "WRITE_USER", FtpUtils.upload( LOCAL_SRC_FILE, REMOTE_FILE, FTP_HOST, FTP_PORT, WRITE_USER_NAME, WRITE_USER_PWD, false ) ); Assert.assertTrue( "REMOTE_FILE.exists", new File( FTPHOME_DIR, REMOTE_FILE ).exists() ); // Teste Download: Assert.assertTrue( "Download", FtpUtils.download( LOCAL_DST_FILE, REMOTE_FILE, FTP_HOST, FTP_PORT, READ_USER_NAME, READ_USER_PWD, false ) ); Assert.assertTrue( "LOCAL_DST_FILE.exists", new File( LOCAL_DST_FILE ).exists() ); // Teste Auflistung: String[] remoteFilenameList = FtpUtils.list( FTP_HOST, FTP_PORT, READ_USER_NAME, READ_USER_PWD ); Assert.assertTrue( "remoteFilenameList", Arrays.asList( remoteFilenameList ).contains( REMOTE_FILE ) ); } }
Führen Sie den Test aus:
cd \MeinWorkspace\MvnFtp
mvn test
Nach der Ausführung des Tests erhalten Sie folgende Dateien:
tree /F
[\MeinWorkspace\MvnJaxWs] |- [src] | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | |- FtpTest.java | '- FtpUtils.java |- [target] | |- [FtpHome] . . . . . . . . . . . . . [Home-Verzeichnis vom embedded FTP-Server] | | '- Test.txt . . . . . . . . . . . [per FTP hochgeladene Datei] | |- [surefire-reports] | | '- ... | |- [test-classes] | | '- ... | |- FtpUsers.properties . . . . . . . . [User-Eigenschaften, falls 'ftpUsersPropsFile' an 'createFtpServer()' uebergeben wurde] | '- TestDst.txt . . . . . . . . . . . . [per FTP downgeloadete Datei] '- pom.xml '- TestSrc.txt
Lassen Sie sich anzeigen, was in die FtpUsers.properties eingetragen wurde:
type target\FtpUsers.properties
In dieser Datei sehen Sie nicht nur die während des Tests hinzugefügten Benutzer und deren Eigenschaften, sondern Sie hätten darin auch vorher weitere Benutzer definieren können, die während des Tests hätten verwendet werden können. Wenn Sie diese Datei nicht benötigen, können Sie der FtpUtils.createFtpServer()-Methode als ftpUsersPropsFile-Parameter "null" übergeben.
Entfernen Sie vor "Thread.sleep( 55000 )" die Auskommentierung, starten Sie den Test neu, und rufen Sie (innerhalb der 55 Sekunden) die hochgeladene Testtextdatei vom FTP-Server per FTP ab (nach dem "start cmd /C mvn test"-Kommando jeweils kurz warten):
a) Mit cURL:
start cmd /C mvn test
curl -l ftp://ReadUserName:ReadUserPwd@localhost:2121
curl ftp://ReadUserName:ReadUserPwd@localhost:2121/Test.txt
curl -T TestSrc.txt ftp://WriteUserName:WriteUserPwd@localhost:2121/Upload-per-curl-Test.txt
curl ftp://ReadUserName:ReadUserPwd@localhost:2121/Upload-per-curl-Test.txt
b) Mit dem Webbrowser:
start cmd /C mvn test
start ftp://WriteUserName:WriteUserPwd@localhost:2121
Klicken Sie auf der FTP-Webseite auf den Test.txt-Link.
c) Mit dem Windows-Explorer:
start cmd /C mvn test
explorer.exe ftp://WriteUserName:WriteUserPwd@localhost:2121
Sie können im Windows-Explorer Dateien und ganze Verzeichnisbäume mit "Drag and Drop" per FTP kopieren.
Folgendermaßen können Sie in Windows 7 über den Windows-Explorer ein Verzeichnis auf einem FTP-Server
ähnlich wie ein Festplattenlaufwerk einbinden:
Extras | Netzlaufwerk verbinden... | Verbindung mit einer Website herstellen, auf der Sie Dokumente und Bilder speichern können |
Weiter | Weiter | URL eintragen (z.B. ftp://WriteUserName:WriteUserPwd@localhost:2121) |
Weiter | Namen eintragen (z.B. Test-FTP) | Weiter.
Falls dies nicht funktioniert: Überprüfen Sie die Aktivierung folgender Einstellung:
Start | Systemsteuerung | Netzwerk und Internet | Internetoptionen | Reiter Erweitert |
Rubrik Browsen | FTP-Ordneransicht aktivieren (außerhalb von Internet Explorer).
Dies funktioniert auch in älteren Windows-Versionen, aber dann lauten einige Menüeinträge etwas anders.
Sehen Sie sich die FTP-Kommandos und -Meldungen an (z.B. während des Testlaufs).
Am einfachsten geht dies, wenn Sie in der pom.xml die Zeile
"<artifactId>slf4j-log4j12</artifactId>" durch
"<artifactId>slf4j-simple</artifactId>" ersetzen.
Sehen Sie sich auch die Bedeutung der FTP-Kommandos an.
Sie können den Apache FtpServer natürlich nicht nur "embedded", sondern auch "stand-alone" verwenden, siehe hierzu die Doku zum Apache-FtpServer-Projekt.
Sie können den Apache FtpServer auch als Windows-Dienst starten, siehe Installing FtpServer as a Windows service.
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.
Voraussetzung ist, dass Sie die beiden obigen Beispiele
Maven-Webapp-Projekt (MvnJettyWebApp) und
Maven-Properties-Projekt (MvnPropApp, egal ob mit oder ohne
Erweiterung um Maven-Profile)
erfolgreich durchgeführt haben.
(Alternativ können Sie auch die genannten Projekte oder das Multiprojekt
downloaden.)
Kopieren Sie die beiden Projekte MvnPropApp und MvnJettyWebApp in neue Verzeichnisse und erstellen Sie für das neue Multiprojekt das neue Verzeichnis MvnMultiProj1:
cd \MeinWorkspace
xcopy MvnPropApp MvnPropApp-Mp1\ /S
xcopy MvnJettyWebApp MvnJettyWebApp-Mp1\ /S
md MvnMultiProj1
Wechseln Sie in das MvnPropApp-Mp1-Verzeichnis und überprüfen Sie, dass das Projekt weiterhin funktioniert:
cd \MeinWorkspace\MvnPropApp-Mp1
mvn clean package "-DMeinCmdProp=PropVonCmd"
java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App
Wechseln Sie in das MvnJettyWebApp-Mp1-Verzeichnis und überprüfen Sie, dass auch dieses Projekt weiterhin funktioniert:
cd \MeinWorkspace\MvnJettyWebApp-Mp1
mvn package jetty:run
start http://localhost:8080/MvnJettyWebApp
Beenden Sie Jetty mit "Strg + C".
Bislang sind die beiden Projekte MvnPropApp-Mp1 und MvnJettyWebApp-Mp1 unabhängig voneinander.
Multiprojekte machen insbesondere bei voneinander abhängigen Projekten Sinn.
Deshalb ändern wir MvnJettyWebApp-Mp1 so, dass es MvnPropApp-Mp1 benötigt.
Ersetzen Sie im Verzeichnis MvnJettyWebApp-Mp1\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>
Die Abhängigkeit zu MvnPropApp-Mp1 müssen Sie in der pom.xml von MvnJettyWebApp-Mp1 eintragen. Ergänzen Sie im MvnJettyWebApp-Mp1-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>
Wenn Sie jetzt MvnJettyWebApp-Mp1 ausführen, erhalten Sie eine Fehlermeldung:
cd \MeinWorkspace\MvnJettyWebApp-Mp1
mvn package jetty:run
-->
Failed to resolve artifact. Missing: de.meinefirma.meinprojekt:MvnPropApp:jar:1.0-SNAPSHOT
bzw. unter Maven 3.0:
[ERROR] Failed to execute goal on project MvnJettyWebApp: Could not resolve dependencies for project de.meinefirma.meinprojekt:MvnJettyWebApp:war:1.0-SNAPSHOT: Could not find artifact 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.)
Wir könnten jetzt im MvnPropApp-Mp1-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-Mp1-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 MvnMultiProj1-Verzeichnis eine neue pom.xml,
in der alle Teilprojekte eingetragen werden (im Beispiel MvnPropApp-Mp1 und MvnJettyWebApp-Mp1):
<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-Mp1</module> <module>../MvnPropApp-Mp1</module> </modules> </project>
Die drei Projekte mit ihren jeweiligen pom.xml sind jetzt so angeordnet:
[\MeinWorkspace] |- [MvnJettyWebApp-Mp1] | |- [src] | | '- ... | '- pom.xml |- [MvnMultiProj1] | '- pom.xml '- [MvnPropApp-Mp1] |- [src] | '- ... '- pom.xml
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\MvnJettyWebApp-Mp1
mvn jetty:run
start http://localhost:8080/MvnJettyWebApp
Die Webseite des MvnJettyWebApp-Mp1-Projekts zeigt das Ergebnis des MvnPropApp-Mp1-Projekts an.
Falls Unterprojekte (wie MvnPropApp-Mp1 und MvnJettyWebApp-Mp1) 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> <relativePath>../MvnMultiProj1/pom.xml</relativePath> </parent>
Dies wird im folgenden Beispiel demonstriert.
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.
Voraussetzung für das folgende Beispiel ist, dass Sie die Beispiele
JAX-WS (MvnJaxWs) und
Einfaches Multiprojekt (MvnMultiProj1) erfolgreich durchgeführt haben.
(Alternativ können Sie die Projekte auch
downloaden.)
Kopieren Sie die drei Unterprojekte in neue Verzeichnisse und erstellen Sie für das neue Multiprojekt das neue Verzeichnis MvnMultiProj2:
cd \MeinWorkspace
xcopy MvnJaxWs MvnJaxWs-Mp2\ /S
xcopy MvnJettyWebApp-Mp1 MvnJettyWebApp-Mp2\ /S
xcopy MvnPropApp-Mp1 MvnPropApp-Mp2\ /S
md MvnMultiProj2
cd MvnMultiProj2
Erzeugen Sie im MvnMultiProj2-Verzeichnis folgende pom.xml:
<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-Mp2</module> <module>../MvnJettyWebApp-Mp2</module> <module>../MvnPropApp-Mp2</module> </modules> </project>
Das neue Projekt MvnMultiProj2 und die drei Unterprojekte sind jetzt so angeordnet:
[\MeinWorkspace] |- [MvnJaxWs-Mp2] | |- [src] | | '- ... | '- pom.xml |- [MvnJettyWebApp-Mp2] | |- [src] | | '- ... | '- pom.xml |- [MvnMultiProj2] | '- pom.xml '- [MvnPropApp-Mp2] |- [src] | '- ... '- pom.xml
Bauen Sie alle vier Projekte:
cd \MeinWorkspace\MvnMultiProj2
mvn clean install
In der Reactor Summary wird zu allen vier Projekten SUCCESS gemeldet.
Entfernen Sie im MvnJaxWs-Mp2-Unterprojekt in der pom.xml den "<build>...</build>"-Block
und rufen Sie im MvnMultiProj2-Verzeichnis erneut "mvn clean install" auf:
Unter Maven 3.0 erhalten Sie keine Fehlermeldung, aber wenn Sie Maven 2.2.1 verwenden, erhalten Sie erwartungsgemäß:
[ERROR] BUILD FAILURE
Compilation failure
...\MvnJaxWs-Mp2\...\HalloWeltImpl.java: annotations are not supported in -source 1.3
Entfernen Sie im MvnPropApp-Mp2-Unterprojekt in der pom.xml die beiden Zeilen
"<version>4.8.1</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-Mp2\pom.xml
'dependencies.dependency.version' is missing for junit:junit:jar
bzw. unter Maven 3.0:
[ERROR] The project de.meinefirma.meinprojekt:MvnPropApp:1.0-SNAPSHOT has 1 error
[ERROR] 'dependencies.dependency.version' for junit:junit:jar is missing.
Ersetzen Sie im 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-Mp2</module> <module>../MvnJettyWebApp-Mp2</module> <module>../MvnPropApp-Mp2</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <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.8.1</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> </project>
Fügen Sie im MvnJaxWs-Mp2-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> <relativePath>../MvnMultiProj2/pom.xml</relativePath> </parent> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnJaxWs</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MvnJaxWs</name> </project>
Fügen Sie im MvnPropApp-Mp2-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> <relativePath>../MvnMultiProj2/pom.xml</relativePath> </parent>
Bauen Sie wieder alle vier Projekte:
cd \MeinWorkspace\MvnMultiProj2
mvn clean install
In der Reactor Summary wird wieder zu allen vier Projekten SUCCESS gemeldet.
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", "Projekt-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 bei Maven 2.2.1 in der maven-2.2.1-uber.jar und bei Maven 3.0 in der maven-model-builder-3.0.jar finden).
Voraussetzung ist, dass Sie das Beispiel
Multiprojekt mit Plugin-Management und Dependency-Management
(MvnMultiProj2) erfolgreich durchgeführt haben.
(Alternativ können Sie auch das Multiprojekt
downloaden.)
Kopieren Sie die drei Unterprojekte in neue Verzeichnisse, erstellen Sie ein neues Multiprojekt in dem neuen Verzeichnis MvnMultiProj3 und legen Sie das neue Corporate-POM-Projekt MvnCorpPom an:
cd \MeinWorkspace
xcopy MvnJaxWs-Mp2 MvnJaxWs-Mp3\ /S
xcopy MvnJettyWebApp-Mp2 MvnJettyWebApp-Mp3\ /S
xcopy MvnPropApp-Mp2 MvnPropApp-Mp3\ /S
md MvnMultiProj3
md MvnCorpPom
Ersetzen Sie sowohl in der MvnJaxWs-Mp3\pom.xml als auch in der MvnPropApp-Mp3\pom.xml jeweils an zwei Stellen:
MvnMultiProj2 durch MvnMultiProj3.
Erzeugen Sie im MvnMultiProj3-Verzeichnis folgende pom.xml:
<project> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnCorpPom</artifactId> <version>1.0</version> <relativePath>../MvnCorpPom/pom.xml</relativePath> </parent> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnMultiProj3</artifactId> <version>1.0</version> <packaging>pom</packaging> <name>MvnMultiProj3</name> <modules> <module>../MvnJaxWs-Mp3</module> <module>../MvnJettyWebApp-Mp3</module> <module>../MvnPropApp-Mp3</module> </modules> </project>
Erzeugen Sie im MvnCorpPom-Verzeichnis folgende pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnCorpPom</artifactId> <version>1.0</version> <packaging>pom</packaging> <name>MvnCorpPom</name> <properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> </properties> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <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.8.1</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> </project>
Bauen Sie die Corporate POM und installieren Sie sie im lokalen Maven-Repository:
cd \MeinWorkspace\MvnCorpPom
mvn install
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.
Testen Sie die Webseite:
cd \MeinWorkspace\MvnJettyWebApp-Mp3
mvn jetty:run
In diesem Beispiel erben die Untermodule vom übergeordneten Multiprojekt MvnMultiProj3,
welches wiederum von der Corporate POM aus MvnCorpPom erbt.
Alternativ ist es oft sinnvoll, wenn die Untermodule stattdessen direkt die Corporate POM aus MvnCorpPom einbinden.
Dann ist es einfacher, mehrere verschiedene Multiprojekte für verschiedene Zwecke einzurichten,
wie etwa für Integrationstests auf dem Entwicklungsrechner, Systemtests in einer Testumgebung, Continuous Integration, Release-Auslieferungen etc.
(falls es zu aufwändig ist, dies über Maven-Profile oder TestNG-Gruppen zu steuern).
Die Corporate POM können Sie erweitern und in beliebige weitere andere Projekte einbinden. In Firmen mit mehreren Projekten ist üblich, eine zentrale Corporate POM für firmenweite Voreinstellungen vorzusehen und zusätzlich pro Projekt eine Projekt-Master-POM für Projektspezifisches zu erstellen. Die Projektmodule binden als Parent die Projekt-Master-POM ein, und diese erbt von der Corporate POM.
Das oben gezeigte Beispiel verwendet das so genannte "Flat Project Layout": Die Maven-Module sind in der Verzeichnisstruktur parallel angeordnet. Alternativ könnten Sie die Maven-Projekthierarchie auch in einer hierarchischen Verzeichnisstruktur abbilden, also die Untermodule in Unterverzeichnisse zum Parent-Multiprojekt speichern. Aber dies hätte zwei Nachteile: Sie hätten nur genau ein Multiprojekt und es würde wesentlich aufwändiger, die Module in Eclipse zu bearbeiten.
Falls die groupId und/oder die version der Untermodule identisch zum übergeordneten Parent-Modul (z.B. Projekt-Master-POM) sein soll, sollten Sie die Angaben nicht redundant wiederholen, sondern über <groupId>${project.groupId}</groupId> bzw. <version>${project.version}</version> einbinden. Im project-Kopf der abhängigen Module lassen Sie die Angaben einfach weg, dann werden sie automatisch geerbt (andernfalls würde Maven 3 melden: "[WARNING] 'groupId' contains an expression but should be a constant").
Falls die version von Untermodulen nicht identisch zum übergeordneten Parent-Modul sein soll und es Abhängigkeiten zwischen den Untermodulen gibt, dann sollten Sie vermeiden, die Versionsnummer des von anderen Untermodulen verwendeten Untermoduls redundant direkt in den Dependencies einzutragen. Verwalten Sie auch die Versionsnummern der Untermodule in der Parent-POM (z.B. Projekt-Master-POM). Allerdings müssen Sie sicherstellen, dass alle Module stets die aktuellste Parent-POM verwenden und nicht irgendeine zufällig in einem (z.B. lokalen) Repository vorhandene. Besonders in der Startphase eines Projekts, in der es viele Änderungen gibt, kann es praktischer sein, die Projekt-Master-POM nicht im Maven-Repository zur Verfügung zu stellen (also für die Projekt-Master-POM weder mvn install noch mvn deploy auszuführen). Dann muss zusätzlich zum in Bearbeitung befindlichem Modul stets auch das Projekt-Master-POM-Modul aus dem Versionskontrollsystem (CVS, Subversion, Mercurial, Git, ...) ausgecheckt sein und beide Module können regelmäßig abgeglichen und aktualisiert werden. Damit dies funktioniert, muss beim Flat Project Layout der relativePath korrekt gesetzt sein (wie obiges Beispiel zeigt).
Bitte beachten Sie, dass es leider zwei kaum zu vermeidende Probleme gibt:
Planen Sie genau, wie ein Release für eine Auslieferung erstellt werden soll. Oft werden folgende Schritte benötigt:
Es gibt viele lohnenswerte Maven-Plugins, die automatisch Informationen zu Ihrem Projekt aufbereiten.
Im Folgenden wird die Einbindung einiger solcher Maven-Plugins beispielhaft gezeigt.
Bei den bisherigen Mini-Demos machen Project Reports kaum Sinn, aber vielleicht haben Sie ein größeres Projekt mit mehr Sourcedateien.
Es werden diverse Metriken zur Sourcecodequalität ermittelt, zum Beispiel zu:
Testabdeckung, potenzielle Fehler, Abhängigkeitsprobleme und strukturelle Probleme.
Bitte beachten Sie:
Sie können beispielsweise folgende Ergebnisse erhalten:
Project Information: | ||
Dependencies | Auflistung der Dependencies und "Dependency Tree" | maven-project-info-reports-plugin |
Project Reports: | ||
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 | Potenzielle Fehler im Sourcecode per Bytecode-Analyse finden | findbugs-maven-plugin |
JavaDocs | Javadoc-Dokumentation | maven-javadoc-plugin |
PMD Report | Potenzielle 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 |
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.
Tragen Sie 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 den folgenden <properties>-Block in die pom.xml ein:
<properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> </properties>
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-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> <artifactId>maven-surefire-report-plugin</artifactId> <configuration> <!-- Bei grossen Projekten auf false setzen: --> <showSuccess>true</showSuccess> </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> ...
Rufen Sie auf:
mvn site
Sehen Sie sich an:
start target\site\index.html
und:
start target\site\project-reports.html
In Maven 3.0 gibt es zu Site Reports erhebliche Änderungen, siehe hierzu: Maven 3.x Compatibility Notes und Maven 3.x and site plugin.
Binden Sie die folgenden <properties>- und <build.plugins.plugin>-Blöcke in einem beliebigen Projekt in die pom.xml ein:
... <properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> </properties> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.0-beta-2</version> <configuration> <reportPlugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>2.2</version> </plugin> <!-- Falls Versionskontrollsystem konfiguriert ist: <plugin> <artifactId>maven-changelog-plugin</artifactId> </plugin> --> <plugin> <artifactId>maven-checkstyle-plugin</artifactId> <version>2.6</version> <configuration> <configLocation>config/maven_checks.xml</configLocation> <includeTestSourceDirectory>true</includeTestSourceDirectory> <excludes>**/generated/*.java</excludes> </configuration> </plugin> <plugin> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> <configuration> <repositoryUrl>--</repositoryUrl> <artifactItems /> </configuration> </plugin> <plugin> <artifactId>maven-javadoc-plugin</artifactId> <version>2.7</version> </plugin> <plugin> <artifactId>maven-jxr-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-pmd-plugin</artifactId> <version>2.5</version> <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> <artifactId>maven-surefire-report-plugin</artifactId> <version>2.6</version> <configuration> <!-- Bei grossen Projekten auf false setzen: --> <showSuccess>true</showSuccess> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.4</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <version>2.3.1</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>taglist-maven-plugin</artifactId> <version>2.4</version> <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> </reportPlugins> </configuration> </plugin> ... </plugins> ... </build> ...
Rufen Sie auf:
mvn site
Sehen Sie sich an:
start target\site\index.html
und:
start target\site\project-reports.html
Sonar erleichtert Sourcecodeanalyse. Sonar liefert ähnliche Ergebnisse, wie die oben unter "Site Report um Project Reports zur Sourcecodeanalyse erweitern" gezeigten Plug-ins. Die wichtigsten Unterschiede sind:
Idealerweise wird Sonar in ein "Continuous Integration"-System eingebunden (z.B. Hudson oder TeamCity), siehe hierzu auch: Continuous Integration mit Hudson und Maven 3.
Es werden diverse Metriken zur Sourcecodequalität ermittelt, zum Beispiel zu: Testabdeckung, potenzielle Fehler, Abhängigkeitsprobleme und strukturelle Probleme.
Auch hier gilt wieder:
Folgendermaßen erstellen Sie Sourcecodeanalysen mit Sonar (hier nur für Maven 3 beschrieben):
Binden Sie die folgenden beiden Sonar-Blöcke in dem zu analysierenden Projekt in die pom.xml ein:
... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.0-beta-2</version> <configuration> <reportPlugins> ... <plugin> <groupId>org.codehaus.sonar-plugins</groupId> <artifactId>maven-report</artifactId> <version>0.1</version> </plugin> ... </reportPlugins> </configuration> </plugin> ... <plugin> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar-maven3-plugin</artifactId> <version>2.6</version> </plugin> ... </plugins> </build> ...
Downloaden Sie Sonar in derselben Version wie in der pom.xml eingetragen (z.B. sonar-2.6.zip) von http://www.sonarsource.org/downloads. Entzippen Sie Sonar (z.B. nach D:\Tools).
Prüfen Sie, ob die für die Sonar-Datenbank benötigte Portnummer noch frei ist. Falls die defaultmäßige Derby-DB mit dem Port 1527 verwendet werden soll, zum Beispiel so:
netstat -a
netstat -an | find ":1527"
Prüfen Sie, ob die Portnummer 9000 noch frei ist:
netstat -an | find ":9000"
Falls der Port nicht frei ist: Tragen Sie in D:\Tools\Sonar\conf\sonar.properties bei "sonar.web.port" eine andere Portnummer ein.
Rufen Sie auf (passen Sie die Pfade und die Portnummer an):
start "Sonar-Server" D:\Tools\Sonar\bin\windows-x86-32\StartSonar.bat
Warten Sie, bis der Sonar-Server fertig gestartet ist (Test z.B. mit: netstat -an | find "0.0.0.0:9000").
cd \MeinWorkspace\<MeinProjekt>
mvn clean install
mvn org.codehaus.sonar:sonar-maven3-plugin:sonar
start http://localhost:9000
Sehen Sie sich die Konfiguration an und führen Sie Anpassungen durch:
Loggen Sie sich mit "admin/admin" ein, klicken Sie auf "Configuration", und wählen Sie das geeignetste Ausgangsprofil,
am besten "Sonar way with FindBugs".
Kopieren Sie das gewählte Ausgangsprofil mit "Copy" und setzen Sie die Kopie mit "Set as default" als Standard.
Klicken Sie auf den neuen Profilnamen und klicken Sie auf die "Coding rule"-Namen, um die Regeln zu konfigurieren.
Unter "Configuration" | "Backup" können Sie die Konfiguration über den "Backup"-Button als XML-Datei speichern, entweder als Backup oder um sie auf einen anderen PC zu übertragen. Das Einlesen erfolgt auf derselben Seite über "Restore".
Erläuterungen zu den verwendeten Metriken finden Sie unter:
http://docs.codehaus.org/display/SONAR/Metric+definitions
Sehen Sie sich die Doku zu Sonar an:
http://docs.codehaus.org/display/SONAR/Use+Sonar
Insbesondere in Continuous-Integration-Systemen (z.B. mit Hudson) sollten Sie den Sonar-Server beim Booten automatisch als Dienst starten. Sehen Sie sich hierzu an: http://docs.codehaus.org/display/SONAR/Install+Sonar#InstallSonar-Mode3RunasaserviceonMSWindows
Falls Sie unter 64-bit-Windows installieren: Der Sonar-Wrapper funktioniert zurzeit nur mit einem 32-bit-JDK. Dieses muss installiert (z.B. nach C:\Program Files (x86)\Java\jdk1.6-32bit) und bei Sonar eingetragen werden. Hierzu in der D:\Tools\Sonar\conf\wrapper.conf die Zeile
wrapper.java.command=java
ändern zu:
wrapper.java.command=C:\Program Files (x86)\Java\jdk1.6-32bit\bin\java
Außerdem können einige Sonar-Kommandos (z.B. "Sonar\bin\windows-x86-32\InstallNTService.bat")
nur in einem Kommandozeilenfenster mit Administratorrechten ausgeführt werden:
Start | Alle Programme | Zubehör | "Eingabeaufforderung" mit rechter Maustaste und "Als Administrator ausführen".
Beachten Sie, dass für systematischen und wiederholten Einsatz empfohlen wird, statt der defaultmäßigen Derby-Datenbank eine externe Datenbank zu verwenden. Diesen und viele weitere Hinweise finden Sie unter: http://docs.codehaus.org/display/SONAR/Full+installation+in+5+steps
Falls Sie JSP oder JSF verwenden, empfiehlt es sich, zusätzlich das Sonar Web Plugin zu installieren: Downloaden Sie die Plugin-Jar-Datei (z.B. sonar-web-plugin-1.0.2.jar) von http://docs.codehaus.org/display/SONAR/Web+Plugin und kopieren Sie sie in den Sonar-Plugin-Ordner (z.B. D:\Tools\Sonar\extensions\plugins).
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.
Ö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 MvnTimestampPlugin
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> ...
Löschen Sie im Verzeichnis 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.
Die Projektstruktur sieht jetzt so aus:
cd \MeinWorkspace\MvnTimestampPlugin
tree /F
[\MeinWorkspace\MvnTimestampPlugin] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- TimestampMojo.java '- pom.xml
Die allgemeine Syntax, um ein Goal auf der Kommandozeile auszuführen, lautet:
"mvn groupID:artifactID:[version:]goal" (der Versionsteil kann manchmal 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:1.0-SNAPSHOT:timestamp
Sie erhalten:
[INFO] --- MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (default-cli) @ MvnTimestampPlugin --- [INFO] Timestamp: 2010-10-09 20:42:37,90
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-mit-TimestampMojo [anpassen!]
mvn clean package
Sie erhalten:
... [INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ MvnJaxbApp --- ... [INFO] --- MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach clean) @ MvnJaxbApp --- [INFO] +++ 20:52:03 ... [INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ MvnJaxbApp --- ... [INFO] --- MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach compile) @ MvnJaxbApp --- [INFO] +++ 20:52:05 ...
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 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 ); } }
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-mit-TimestampMojo [anpassen!]
mvn clean package
Diesmal ist die Ausgabe erweitert um die Anzeige der "Dauer":
... [INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ MvnJaxbApp --- ... [INFO] --- MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach clean) @ MvnJaxbApp --- [INFO] +++ 10:48:00 ... [INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ MvnJaxbApp --- ... [INFO] --- MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach compile) @ MvnJaxbApp --- [INFO] +++ 10:48:01, Dauer: 1234 ms ...
Es gibt verschiedene Verfahren, um Maven-Plugins automatisiert testen zu können. Einige sind beschrieben unter Introduction / Testing Styles.
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>
Für den Test wird nur eine einfache abgespeckte POM benötigt. Erzeugen Sie im 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>
Erzeugen Sie im 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.
Ihre Verzeichnisstruktur sieht jetzt so aus:
cd \MeinWorkspace\MvnTimestampPlugin
tree /F
-->
[\MeinWorkspace\MvnTimestampPlugin] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- TimestampMojo.java | '- [test] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- TimestampMojoTest.java | '- [resources] | '- test-pom.xml '- pom.xml
Führen Sie den JUnit-Test aus:
cd \MeinWorkspace\MvnTimestampPlugin
mvn test
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.
Erweitern Sie die pom.xml um erläuternde Hilfstexte, damit folgendes Kommando Hilfe bieten kann:
mvn help:describe -Dplugin=de.meinefirma.meinprojekt:MvnTimestampPlugin:1.0-SNAPSHOT -Ddetail
Manchmal werden nur für Tests benötigte wichtige Testhilfsklassen auch in anderen Maven-Modulen benötigt. Die Weitergabe von Testhilfsklassen an andere Module wird durch das maven-jar-plugin über das test-jar-Goal unterstützt, wie das folgende Beispiel zeigt (siehe auch: Guide to using attached tests).
Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Maven-Projekt:
cd \MeinWorkspace
mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTestJar1
cd MvnTestJar1
Erzeugen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis die wichtige Testhilfsklasse: MeinWichtigesTestUtil.java
package de.meinefirma.meinprojekt; public class MeinWichtigesTestUtil { public static int hilfsmethode( int a, int b ) { return a * b; } }
Ersetzen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis den Inhalt der AppTest.java durch:
package de.meinefirma.meinprojekt; import junit.framework.Assert; import junit.framework.TestCase; public class AppTest extends TestCase { public void testApp() { Assert.assertEquals( 6, MeinWichtigesTestUtil.hilfsmethode( 2, 3 ) ); } }
Führen Sie im Kommandozeilenfenster den JUnit-Test aus und installieren Sie das MvnTestJar1-Ergebnisartefakt im lokalen Maven-Repository (passen Sie den Pfad zum lokalen Maven-Repository an):
mvn test
mvn clean install
dir C:\Users\%USERNAME%\.m2\repository\de\meinefirma\meinprojekt\MvnTestJar1\1.0-SNAPSHOT
Sie erhalten u.a.:
MvnTestJar1-1.0-SNAPSHOT.jar
Erzeugen Sie ein zweites Maven-Projekt:
cd \MeinWorkspace
mvn archetype:generate -DinteractiveMode=false -DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTestJar2
cd MvnTestJar2
copy ..\MvnTestJar1\src\test\java\de\meinefirma\meinprojekt\AppTest.java src\test\java\de\meinefirma\meinprojekt /Y
mvn test
Da die Dependency zu MvnTestJar1 fehlt, erhalten Sie erwartungsgemäß die Fehlermeldung:
[ERROR] COMPILATION ERROR : ...\AppTest.java: cannot find ... MeinWichtigesTestUtil
Erweitern Sie die pom.xml des neuen MvnTestJar2-Projekts um eine Dependency zum MvnTestJar1-Projekt, indem Sie folgenden Block einfügen:
<dependency> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnTestJar1</artifactId> <version>1.0-SNAPSHOT</version> <scope>test</scope> </dependency>
Wenn Sie erneut "mvn test" ausführen, erhalten Sie weiterhin die Fehlermeldung:
[ERROR] COMPILATION ERROR : ...\AppTest.java: cannot find ... MeinWichtigesTestUtil
Der Grund ist, dass die Testhilfsklasse MeinWichtigesTestUtil nicht im Auslieferartefakt MvnTestJar1-1.0-SNAPSHOT.jar enthalten ist, da sie nur für JUnit-Tests benötigt wird.
Jetzt kommt der Trick dieses Programmierbeispiels:
Um wichtige Testhilfsklassen auch in Tests in anderen Modulen verwenden zu können,
werden sie in gesonderten Auslieferartefakten weitergegeben, wie im Folgenden beschrieben wird.
Ändern Sie in der pom.xml des neuen MvnTestJar2-Projekts folgendermaßen die MvnTestJar1-Dependency:
<dependency> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnTestJar1</artifactId> <version>1.0-SNAPSHOT</version> <type>test-jar</type> <scope>test</scope> </dependency>
Beachten Sie die "<type>test-jar</type>"-Zeile.
Fügen Sie in der pom.xml des ersteren MvnTestJar1-Projekts folgendermaßen das maven-jar-plugin mit dem test-jar-Goal hinzu:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.1</version> <executions> <execution> <goals> <goal>test-jar</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Führen Sie im MvnTestJar1-Projekt erneut "mvn install" aus:
cd ..\MvnTestJar1
mvn install
dir C:\Users\%USERNAME%\.m2\repository\de\meinefirma\meinprojekt\MvnTestJar1\1.0-SNAPSHOT
Diesmal erhalten Sie zwei Ergebnis-Jar-Archive:
MvnTestJar1-1.0-SNAPSHOT-tests.jar
MvnTestJar1-1.0-SNAPSHOT.jar
Jetzt funktionieren auch im MvnTestJar2-Projekt die Tests (inkl. MeinWichtigesTestUtil):
cd ..\MvnTestJar2
mvn test
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.
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 MvnTestNG
tree /F
[\MeinWorkspace\MvnTestNG] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- App.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml
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.5</version> <configuration> <parallel>methods</parallel> <threadCount>10</threadCount> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>5.12.1</version> <scope>test</scope> </dependency> </dependencies> </project>
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" ); } }
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" } ); } }
Ihr Projekteverzeichnis sieht jetzt so aus:
[\MeinWorkspace\MvnTestNG] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- App.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- App1Test.java | '- App2Test.java | '- App3Test.java '- pom.xml
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 SUCCESS [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.
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.
Wenn Sie TestNG-Tests in Eclipse ausführen wollen, müssen Sie das TestNG-Eclipse-Plug-in installieren, siehe http://testng.org/doc/eclipse.html und http://beust.com/eclipse/.
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 kann es manchmal sinnvoll sein, die Integrationstests in einem getrennten Maven-Projekt zu realisieren.
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.
Ö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 MvnHtmlIntTest
md src\main\java\de\meinefirma\meinprojekt
md src\test\java\de\meinefirma\meinprojekt
md src\test\java\integrationtest
rd src\main\resources
tree /F
Sie erhalten:
[\MeinWorkspace\MvnHtmlIntTest] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | '- index.jsp | '- [test] | '- [java] | |- [de] | | '- [meinefirma] | | '- [meinprojekt] | '- [integrationtest] '- pom.xml
Ersetzen Sie im 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>MvnHtmlIntTest</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>MvnHtmlIntTest: Webapp mit Integrationstest</name> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> <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> <version>2.7.1</version> <configuration> <systemProperties> <property> <name>port</name> <value>8080</value> </property> </systemProperties> <excludes> <exclude>**/integrationtest/*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>**/integrationtest/*Test.java</include> </includes> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <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.7</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>5.12.1</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.
Ersetzen Sie im 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>
Fügen Sie im 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>
Erstellen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis eine 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 ""; } } }
Erstellen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis einen Komponenten-JUnit-Test: MeineBeanTest.java
package de.meinefirma.meinprojekt; import static org.testng.AssertJUnit.assertEquals; import org.testng.annotations.Test; public class MeineBeanTest { @Test public void testMeineBean() { MeineBean meineBean = new MeineBean(); assertEquals( "3.0", meineBean.berechne( "1", "2" ) ); } }
Erstellen Sie im src\test\java\integrationtest-Verzeichnis einen HtmlUnit-Integrationstest: HtmlUnitTest.java
package integrationtest; import static org.testng.AssertJUnit.assertEquals; import org.testng.annotations.*; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.*; public class HtmlUnitTest { @Test @Parameters( { "port" } ) public void test( String port ) throws Exception { test( port, "1", "2", "3.0" ); } public String test( String port, String wert1, String wert2, String ergebnis ) throws Exception { WebClient webClient = new WebClient(); HtmlPage page = (HtmlPage) webClient.getPage( "http://localhost:" + port + "/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; } }
Erstellen Sie im src\test\java\integrationtest-Verzeichnis einen HttpUnit-Integrationstest: HttpUnitTest.java
package integrationtest; import static org.testng.AssertJUnit.assertEquals; import org.testng.annotations.*; import com.meterware.httpunit.*; public class HttpUnitTest { @Test @Parameters( { "port" } ) public void test( String port ) throws Exception { test( port, "1", "2", "3.0" ); } public String test( String port, String wert1, String wert2, String ergebnis ) throws Exception { WebConversation conversation = new WebConversation(); WebRequest request = new GetMethodWebRequest( "http://localhost:" + port + "/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.
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\MvnHtmlIntTest] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- MeineBean.java | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | |- BerechnungsFormular.jsp | | '- index.jsp | '- [test] | '- [java] | |- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- MeineBeanTest.java | '- [integrationtest] | |- HtmlUnitTest.java | '- HttpUnitTest.java '- pom.xml
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".
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
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\integrationtest.HtmlUnitTest.txt an:
mvn test -Dtest=integrationtest.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=integrationtest.HtmlUnitTest
mvn test -Dtest=integrationtest.HttpUnitTest
mvn test -Dtest=integrationtest.*Test
Beenden Sie Jetty mit "Strg + C".
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] --- maven-surefire-plugin:2.7.1:test (default-test) @ MvnHtmlIntTest --- ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running TestSuite Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 ... [INFO] --- maven-jetty-plugin:6.1.26:run (start-jetty) @ MvnHtmlIntTest --- ... [INFO] Starting jetty 6.1.26 ... [INFO] Started Jetty Server [INFO] [INFO] --- maven-surefire-plugin:2.7.1:test (integration-tests) @ MvnHtmlIntTest --- ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running TestSuite Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ ... :INFO::Shutdown hook ... ...
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
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.
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/.
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.
JWebUnit leistet Ähnliches wie HtmlUnit und HttpUnit, aber ist oft wesentlich einfacher und übersichtlicher.
Voraussetzung für das folgende Beispiel ist die Durchführung des vorherigen Beispiels Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit (im Folgenden MvnHtmlIntTest genannt).
Erzeugen Sie ein neues Projektverzeichnis MvnJWebUnitIntTest und kopieren Sie dorthin das MvnHtmlIntTest-Projekt:
cd \MeinWorkspace
md MvnJWebUnitIntTest
cd MvnJWebUnitIntTest
xcopy ..\MvnHtmlIntTest\*.* /S
Leider funktioniert dieser Test nicht mit allen maven-surefire-plugin-Versionen. Die Version 2.6 funktioniert, aber 2.7 und 2.7.1 funktionieren nicht. Ersetzen Sie deshalb die Versionszeile hinter
<artifactId>maven-surefire-plugin</artifactId>durch:
<version>2.6</version>
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>
Löschen Sie im src\test\java\integrationtest-Verzeichnis die beiden Dateien HtmlUnitTest.java und HttpUnitTest.java und erzeugen Sie stattdessen im selben Verzeichnis die neue Testklasse JWebUnitTest.java:
package integrationtest; 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" );
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\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 | '- [integrationtest] | '- JWebUnitTest.java '- pom.xml
Führen Sie den Integrationstest aus (wieder mit Jetty im Hintergrund-Thread):
cd \MeinWorkspace\MvnJWebUnitIntTest
mvn clean integration-test
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.
Mit Selenium können Sie über automatisierte Integrationstests Webanwendungen über einen Webbrowser testen. Dies hat den Vorteil, dass JavaScript und Ajax leichter und realitätsnäher getestet werden. Allerdings gibt es auch einen Nachteil: Da die Installation eines Webbrowsers vorausgesetzt wird, ist die Ausführung auf beliebigen PCs nicht mehr so einfach zu gewährleisten und die Ausführung in Continuous-Integration-Systemen könnte unsicherer sein.
Im Folgenden wird ein einziges Beispiel zur Einbindung eines Selenium-Integrationstests gezeigt. Weitere Beispiele finden Sie in selenium.htm.
Legen Sie ein neues Projekt an:
cd \MeinWorkspace
md MvnSelenium
cd MvnSelenium
md src\test\java\integrationtest
Erzeugen Sie im Projektverzeichnis die pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnSelenium</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MvnSelenium</name> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>selenium-maven-plugin</artifactId> <version>1.1</version> <executions> <execution> <phase>pre-integration-test</phase> <goals> <goal>start-server</goal> </goals> <configuration> <background>true</background> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.8</version> <configuration> <skip>true</skip> <systemPropertyVariables> <browser>firefox</browser> </systemPropertyVariables> </configuration> <executions> <execution> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <skip>false</skip> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.seleniumhq.selenium.client-drivers</groupId> <artifactId>selenium-java-client-driver</artifactId> <version>1.0.2</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> </dependencies> </project>
Erzeugen Sie im src\test\java\integrationtest-Verzeichnis die Testklasse: GoogleSeleniumTest.java
package integrationtest; import org.junit.*; import com.thoughtworks.selenium.*; public class GoogleSeleniumTest { private static Selenium selenium; @BeforeClass public static void startSelenium() { String browser = System.getProperty( "browser" ); selenium = new DefaultSelenium( "localhost", 4444, "*" + browser, "http://www.google.de" ); selenium.start(); selenium.setSpeed( "500" ); } @AfterClass public static void stopSelenium() { selenium.stop(); } @Test public void testGoogle() { selenium.open( "" ); selenium.waitForPageToLoad( "33000" ); selenium.type( "q", "seleniumhq.org" ); selenium.click( "btnG" ); selenium.waitForCondition( "selenium.isTextPresent( 'seleniumhq.org' )", "22000" ); Assert.assertTrue( selenium.isTextPresent( "seleniumhq.org" ) ); } }
Der Test startet Selenium in startSelenium() und in testGoogle() wird die Google-Webseite geöffnet, im Eingabefeld etwas eingetragen, der Such-Button betätigt und kontrolliert, ob ein bestimmter Text in der Ergebnisseite enthalten ist. Das "selenium.setSpeed( "500" )"-Kommando dient nur zur Verlangsamung des Tests, damit der Ablauf besser beobachtet werden kann. Sehen Sie sich die vielfältigen Möglichkeiten an, welche die Selenium-Klasse bietet.
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\MvnSelenium] |- [src] | '- [test] | '- [java] | '- [integrationtest] | '- GoogleSeleniumTest.java '- pom.xml
Starten Sie den Integrationstest ohne korrekter Angabe eines Webbrowsers:
cd \MeinWorkspace\MvnSelenium
mvn integration-test -Dbrowser=?
Sie erhalten eine Fehlermeldung und unter "Supported browsers" eine Liste der unterstützten Browser.
Führen Sie den Integrationstest für Firefox aus (falls Sie Firefox installiert haben):
mvn integration-test -Dbrowser=firefox
Sie können den Ablauf im Browserfenster verfolgen und erhalten im Kommandozeilenfenster eine Erfolgsmeldung.
Falls Sie unter Windows arbeiten: Führen Sie den Integrationstest für den Microsoft Internet Explorer aus:
mvn integration-test -Dbrowser=iexploreproxy
Falls Sie folgende Fehlermeldung erhalten:
BUILD ERROR Failed to resolve artifact. Missing: org.seleniumhq.selenium.server:selenium-server:jar:standalone:1.0.2
Prüfen Sie, ob Ihr Virenscanner den Download von selenium-server-1.0.2-standalone.jar geblockt hat,
weil er die darin enthaltene Datei "hudsuckr.exe" für zu gefährlich hält.
In diesem Fall müssen Sie die Datei manuell downloaden oder im Virenscanner hierfür eine Ausnahme konfigurieren oder
den Virenscanner kurzeitig temporär deaktivieren.
Falls Sie folgende Fehlermeldung erhalten:
Failed to start new browser session: Firefox 3 could not be found in the path!
Please add the directory containing ''firefox.exe'' to your PATH environment variable
Dann erweitern Sie (vorübergehend) die PATH-Umgebungsvariable, zum Beispiel für 32-bit-Firefox unter 64-bit-Windows:
set PATH=%PATH%;C:\Program Files (x86)\Mozilla Firefox
mvn integration-test -Dbrowser=firefox
Wenn Sie sich in einem Proxy-geschützten Firmennetz befinden, kann der Test fehlschlagen. Verwenden Sie Selenium in diesem Fall für Tests von eigenen Webanwendungen, für ihren lokalen Webserver oder für Webseiten aus dem internen Netz.
Wenn Sie das Firefox-Plug-in Selenium IDE installieren, können Sie Testfälle bequem im Firefox-Browser erzeugen. Die vom Selenium-Makrorekorder aufgenommenen Skripte können Sie wahlweise als HTML-Datei, als Java-Sourcecode oder in anderen Formaten speichern. Die als HTML-Datei gespeicherten Skripte können Sie mit dem selenese-Goal über Maven ausführen.
Weitere Beispiele für Selenium mit Maven finden Sie in selenium.htm.
Ausführliche Infos zu Fit finden Sie in java-fit.htm.
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 MvnFitTest
tree /F
[\MeinWorkspace\MvnFitTest] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- App.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml
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> <version>2.0-beta-3</version> <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.
Löschen Sie im 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 ""; } } }
Löschen Sie im src\test\java-Verzeichnis das Unterverzeichnis de (inkl. aller weiteren Unterverzeichnisse).
Erzeugen Sie im 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 ); } }
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 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> </td><td> </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 | ||
wert1 | wert2 | berechne() |
0 | ||
1 | 2 | 3.0 |
+1 | -1 | 0.0 |
-42 | 13 | -29.0 |
1.234 | 5.678 | 6.912 |
Überprüfen Sie Ihre Projektstruktur mit tree /F:
[\MeinWorkspace\MvnFitTest] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- MeineBean.java | '- [test] | |- [fit] | | '- MeinFitTest.html | '- [java] | '- [fixtures] | '- MeinFixture.java '- pom.xml
Führen Sie den Akzeptanztest als Maven-Integrationstest aus:
cd \MeinWorkspace\MvnFitTest
mvn clean integration-test
Sie erhalten:
[INFO] --- fit-maven-plugin:2.0-beta-3:run (fixture) @ MvnFitTest --- [INFO] Running Fixture with input file src\test\fit\MeinFitTest.html and output file target\fit\MeinFitTest.html [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
Sehen Sie sich das Ergebnis als HTML-Seite an (grün bedeutet "ok"):
start target\fit\MeinFitTest.html
fixtures.MeinFixture | ||
wert1 | wert2 | berechne() |
0 | null | |
1 | 2 | 3.0 |
+1 | -1 | 0.0 |
-42 | 13 | -29.0 |
1.234 | 5.678 | 6.912 |
fit.Summary | |
counts | 4 right, 0 wrong, 0 ignored, 0 exceptions |
Ä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 | ||
wert1 | wert2 | berechne() |
0 | null | |
1 | 2 | 4.0 expected 3.0 actual |
+1 | -1 | 0.0 |
-42 | 13 | -29.0 |
1.234 | 5.678 | 6.912 |
fit.Summary | |
counts | 3 right, 1 wrong, 0 ignored, 0 exceptions |
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.
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
Ihre Projektstruktur sieht jetzt so aus:
[\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 | '- [integrationtest] | |- HtmlUnitTest.java | '- HttpUnitTest.java '- pom.xml
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.
Ändern Sie im 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 integrationtest.HtmlUnitTest()).test( "8080", 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.
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.
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).
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 MvnWicket
tree /F
[\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
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".
Ä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> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin>
Achten Sie darauf, dass als <wicket.version> mindestens 1.4.1 eingetragen ist.
Fügen Sie im 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>
Erzeugen Sie im 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>
Erzeugen Sie im 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; } }
Erzeugen Sie im 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() ); } }
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".
Zum Oracle WebLogic 10.3.4 finden Sie eine Installationsbeschreibung unter jee-oracleweblogic.htm#Installation.
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.
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".
Weitere Installationshinweise (z.B. Datasource) finden Sie unter jee-ejb2.htm#InstallationJBoss.
Zum Tomcat finden Sie eine Installationsbeschreibung unter jsp-install.htm#InstallationUnterWindows.
Voraussetzung für das folgende Beispiel ist die Installation von WebLogic, JBoss, Tomcat oder eines anderen Servers, den Cargo unterstützt.
Eine weitere Voraussetzung ist die Durchführung des obigen Beispiels Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit.
Erzeugen Sie ein neues Projekt mit dem Projektverzeichnis MvnAppSrvIntTest:
cd \MeinWorkspace
md MvnAppSrvIntTest
cd MvnAppSrvIntTest
xcopy ..\MvnHtmlIntTest\*.* /S
Ä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> <version>1.0.6</version> <configuration> <container> <containerId>${appsrv.containerId}</containerId> <home>${appsrv.srvhome}</home> </container> <configuration> <type>existing</type> <home>${appsrv.dmnhome}</home> <properties> <!-- Falls WebLogic: Anpassen: --> <cargo.weblogic.administrator.user>weblogic</cargo.weblogic.administrator.user> <cargo.weblogic.administrator.password>weblogic0</cargo.weblogic.administrator.password> <cargo.servlet.port>${port}</cargo.servlet.port> </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>
Ersetzen Sie die Zeile
<value>8080</value>
durch:
<value>${port}</value>
Fügen Sie vor dem </project>-Ende-Tag ein (passen Sie die Pfade an Ihre Application-Server-Installation an):
<profiles> <profile> <id>WebLogic</id> <activation> <property> <name>env.APPSRV</name> <value>WebLogic</value> </property> </activation> <properties> <!-- Falls WebLogic: Anpassen: --> <appsrv.containerId>weblogic103x</appsrv.containerId> <appsrv.srvhome>C:\WebLogic\wlserver_10.3</appsrv.srvhome> <appsrv.dmnhome>C:\WebLogic\user_projects\domains\MeineDomain</appsrv.dmnhome> <port>7001</port> </properties> </profile> <profile> <id>JBoss</id> <activation> <property> <name>env.APPSRV</name> <value>JBoss</value> </property> </activation> <properties> <!-- Falls JBoss: Anpassen: --> <appsrv.containerId>jboss5x</appsrv.containerId> <appsrv.srvhome>C:\JBoss</appsrv.srvhome> <appsrv.dmnhome>${appsrv.srvhome}/server/default</appsrv.dmnhome> <port>8080</port> </properties> </profile> <profile> <id>Tomcat</id> <activation> <property> <name>env.APPSRV</name> <value>Tomcat</value> </property> </activation> <properties> <!-- Falls Tomcat: Anpassen: --> <appsrv.containerId>tomcat6x</appsrv.containerId> <appsrv.srvhome>D:\Tools\Tomcat</appsrv.srvhome> <appsrv.dmnhome>${appsrv.srvhome}</appsrv.dmnhome> <port>8080</port> </properties> </profile> </profiles>
Natürlich können Sie analog auch andere Application Server einbinden.
Lassen Sie sich anzeigen, welche Profile zur Verfügung stehen:
cd \MeinWorkspace\MvnAppSrvIntTest
mvn help:all-profiles
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 WebLogic
mvn integration-test -P JBoss
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 integrationtest.HttpUnitTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 Running integrationtest.HtmlUnitTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
Das .war-Archiv wurde in Autodeploy-Verzeichnisse unterhalb von ${appsrv.dmnhome} kopiert:
Im Falle von WebLogic nach: ${appsrv.dmnhome}\autodeploy,
im Falle von JBoss nach: ${appsrv.dmnhome}\deploy und
im Falle von Tomcat nach: ${appsrv.dmnhome}\webapps.
Unter selenium.htm finden Sie weitere Cargo-Beispiele, zum Beispiel mit dem zipUrlInstaller, mit dem Sie die Installation von Servern automatisieren können.
Cargo bietet einige weitere interessante Optionen, beispielsweise das Mergen von WAR-Dateien und die Möglichkeit, Tomcat im Security Mode zu starten. Weiters zum Cargo-Maven-Plugin finden Sie unter https://codehaus-cargo.github.io/cargo/Maven2+plugin und https://codehaus-cargo.github.io/cargo/Maven2+Plugin+Reference+Guide.
Für viele Application Server gibt es auch speziell angepasste Maven-Plugins, zum Beispiel für JBoss das jboss-maven-plugin.
Projekte mit Maven 2 für die Java Enterprise Edition finden Sie unter:
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.
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.6.1.R36x_v20100806.jar
von http://download.eclipse.org/equinox.
Legen Sie ein Projektverzeichnis MvnOsgi an und erzeugen Sie darin folgende Verzeichnisstruktur:
cd \MeinWorkspace
md MvnOsgi\src\main\java\de\meinefirma\meinprojekt
cd MvnOsgi
tree /F
[\MeinWorkspace\MvnOsgi] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] '- org.eclipse.osgi_3.6.1.R36x_v20100806.jar [falls keine andere OSGi-Installation]
Erzeugen Sie im 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." ); } }
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>
Ihre Projektverzeichnisstruktur sieht jetzt so aus:
tree /F
[\MeinWorkspace\MvnOsgi] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- Activator.java |- org.eclipse.osgi_3.6.1.R36x_v20100806.jar [falls keine andere OSGi-Installation] '- pom.xml
Geben Sie im Kommandozeilenfenster (und in der OSGi-Konsole) Folgendes ein (passen Sie den OSGi-Dateinamen an):
cd \MeinWorkspace\MvnOsgi
mvn package
java -jar org.eclipse.osgi_3.6.1.R36x_v20100806.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.6.1.R36x_v20100806 1 ACTIVE de.meinefirma.meinprojekt.MvnOsgi_1.0.0.SNAPSHOT osgi> uninstall 1 de.meinefirma.meinprojekt.MvnOsgi: Tschau OSGi-Welt. osgi> close
Falls Sie nicht die org.eclipse.osgi_3.6.1.R36x_v20100806.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:\Tools\eclipse-equinox-SDK-3.4.2\eclipse\plugins\org.eclipse.osgi_3.4.3.R34x_v20081215-1030.jar -console -clean
Erläuterungen finden Sie beim nahezu identischen OSGi-HelloWorld-Programmierbeispiel. Dort sind auch weitere ausführbare Kommandos und weitere OSGi-Programmierbeispiele aufgeführt.
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 (passen Sie den Pfad an und setzen Sie als Portnummer dieselbe wie bei Cntlm ein):
<?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>D:\Tools\m2\repository</localRepository> <proxies> <proxy> <active>true</active> <protocol>http</protocol> <host>127.0.0.1</host> <port>3128</port> </proxy> </proxies> </settings>
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>D:\Tools\m2\repository</localRepository> <mirrors> <mirror> <id>MeineMirrorId</id> <mirrorOf>central</mirrorOf> <url>file://\\<MeinPC>\<MeinFreigabeName>\m2-Central-Repository</url> </mirror> </mirrors> </settings>
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>D:\Tools\m2\repository</localRepository> <mirrors> <mirror> <id>archiva</id> <mirrorOf>*</mirrorOf> <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/internal</url> </mirror> </mirrors> </settings>
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.
Falls Sie unter 64-bit-Windows installieren: Der Archiva-Wrapper funktioniert zurzeit nur mit einem 32-bit-JDK. Dieses muss installiert (z.B. nach C:\Program Files (x86)\Java\jdk1.6-32bit) und bei Archiva eingetragen werden. Hierzu in der D:\Tools\Archiva\conf\wrapper.conf die Zeile
wrapper.java.command=java
ändern zu:
wrapper.java.command=C:\Program Files (x86)\Java\jdk1.6-32bit\bin\java
Außerdem können einige Archiva-Kommandos (z.B. "bin\archiva.bat install") nur in einem Kommandozeilenfenster mit Administratorrechten ausgeführt werden: Start | Alle Programme | Zubehör | "Eingabeaufforderung" mit rechter Maustaste und "Als Administrator ausführen".
Starten Sie Archiva über:
D:\Tools\Archiva\bin\archiva.bat console
Wenn "Started SelectChannelConnector@0.0.0.0:4422" erscheint, starten Sie die Archiva-Weboberfläche:
Identifier: | MeineNetworkProxyID |
Protocol: | http |
Hostname: | 127.0.0.1 |
Port: | 3128 |
Username: | |
Password: |
<networkProxies> <networkProxy> <id>MeineNetworkProxyID</id> <host>127.0.0.1</host> <port>3128</port> <username/> <password/> </networkProxy> </networkProxies>
Network Proxy: | MeineNetworkProxyID |
<proxyConnector> ... <proxyId>MeineNetworkProxyID</proxyId> ... </proxyConnector>
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 |
Network Proxy: | MeineNetworkProxyID |
Managed Repository: | internal |
Remote Repository: | maven1-repository.dev.java.net |
Cache failures: | yes |
Releases: | once |
Checksum: | fix |
Snapshots: | never |
D:\Tools\Archiva\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 Sicherheit"] | "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.
Mit mvn install kopieren Sie Ihre Ergebnisartefakte in Ihr lokales Maven-Repository. Falls Sie im Team arbeiten und Ihre Kollegen Ihre Ergebnisartefakte verwenden können sollen, müssen Sie diese mit mvn deploy in ein gemeinsam genutzes Remote Repository kopieren. Dies kann ein projektspezifisches, teamspezifisches, firmenspezifisches oder ein übers Internet erreichbares sein. Im Folgenden werden die dafür benötigten Einstellungen am Beispiel des Archiva-Repository-Managers aufgeführt.
Archiva einrichten wie oben beschrieben.
Der Archiva-Administrator legt in Archiva die Benutzer für das Distribution-Deployment an (im Folgenden "<DeployerName>" genannt):
Der Entwickler/Deployer ändert das Kennwort und kontrolliert den Archiva-Account:
Auf dem Deployer-PC die %M2_HOME%\conf\settings.xml erweitern (dabei korrekte Archiva-URL und korrekten Deployer-Benutzer eintragen):
<?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>D:\Tools\m2\repository</localRepository> <mirrors> <mirror> <id>archiva</id> <mirrorOf>*</mirrorOf> <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/internal</url> </mirror> </mirrors> <servers> <server> <id>archiva</id> <username>..._DeployerName_...</username> <password>....................</password> </server> </servers> </settings>
Auf dem Deployer-PC ein Testprojekt anlegen:
cd \MeinWorkspace
mvn archetype:generate -DinteractiveMode=false -DgroupId=test1 -DartifactId=Test1
cd Test1
Auf dem Deployer-PC für den Test die pom.xml des Test1-Projekts erweitern (dabei korrekte Archiva-URL eintragen):
<distributionManagement> <repository> <id>archiva</id> <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/internal</url> </repository> <snapshotRepository> <id>archiva</id> <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/snapshots</url> </snapshotRepository> </distributionManagement>
Diese POM-Einstellung erfolgt am besten in einer "Corporate POM (oder Projekt-Master-POM)".
Snapshot-Deployment:
cd \MeinWorkspace\Test1
mvn deploy
Release-Deployment:
In der pom.xml die Zeile
<version>1.0-SNAPSHOT</version>
ändern zu:
<version>1.0</version>
und aufrufen:
mvn deploy
Kontrolle:
http://<ArchivaServer>:<ArchivaPort>/archiva/browse aufrufen und beide deployte Artefakte überprüfen.
Doku zum Maven-Deploy-Plugin:
http://maven.apache.org/plugins/maven-deploy-plugin und
http://maven.apache.org/plugins/maven-deploy-plugin/usage.html
Sehen Sie sich auch die Hinweise zum Release-Auslieferungsprozess an.
Continuous Integration mit Hudson ermöglicht die kontinuierliche Überwachung, ob Systeme funktionsfähig sind, sowie ob die ins Versionskontrollsystem ("SCM", z.B. CVS, Subversion, Mercurial, Git) eingecheckten Module kompilierfähig sind, zusammenpassen und ob alle Tests (auch Integrationstests) fehlerfrei durchlaufen werden.
Im Folgenden werden einige wichtige Schritte kurz beschrieben, um Hudson für Maven-3-Projekte einzurichten.
Die folgende Beschreibung geht von einem installierten Maven 3 und von bereits vorhanden und ins SCM eingecheckten Modulen aus.
Hudson legt seine Arbeitsdateien defaultmäßig in einem .hudson-Unterverzeichnis im <user-home>-Verzeichnis (z.B. C:\Users\%USERNAME%) ab. Zumindest in Firmen sollten Sie dies vermeiden, weil 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.
Um dies zu vermeiden, erzeugen Sie ein neues Verzeichnis und übergeben dies als Umgebungsvariable, zum Beispiel so:
md C:\ContIntegr\Hudson
set HUDSON_HOME=C:\ContIntegr\Hudson
Downloaden Sie die Hudson-Installations-WAR-Datei (z.B. hudson-1.398.war) von http://hudson-ci.org, zum Beispiel in das bei %HUDSON_HOME% definierte Verzeichnis (z.B. C:\ContIntegr\Hudson).
Prüfen Sie, ob die gewünschte Portnummer (z.B. 4424) noch frei ist:
netstat -a
netstat -an | find ":4424"
Wechseln Sie in das Verzeichnis mit der hudson-1.398.war-Datei und rufen Sie auf (eventuell statt mit 4424 mit anderer Portnummer):
cd C:\ContIntegr\Hudson
start "Hudson" java -jar hudson-1.398.war --httpPort=4424
Warten Sie, bis "Completed initialization" erscheint.
start http://localhost:4424
Wählen Sie im Hudson-Hauptmenü "Hudson verwalten" | "System konfigurieren":
Wählen Sie im Hudson-Hauptmenü "Neuen Job anlegen":
Hudson setzt einige Hudson Environment Variables, die Sie während der Hudson-Jobs verwenden können. Einige der Werte können innerhalb der Hudson-Job-Beschreibungen hilfreich sein, zum Beispiel um Kopierkommandos zu formulieren (z.B. WORKSPACE und JOB_NAME, s.o.). Andere Werte können auch zur Laufzeit interessant sein (z.B. BUILD_ID, BUILD_NUMBER und CVS_BRANCH). Sie können sie während des Buildprozesses automatisch in Properties-Dateien oder in die MANIFEST.MF eintragen.
Hudson sorgt normalerweise dafür, dass alle vom Hudson-Job gestarteten Prozesse automatisch am Ende des Hudson-Jobs beendet werden. Dies ist normalerweise das gewünschte Verhalten. Falls Sie in einem Hudson-Job Prozesse starten wollen, die nicht von Hudson beendet werden sollen, können Sie dies erreichen, indem Sie in diesem Job die Umgebungsvariable BUILD_ID setzen, zum Beispiel so: "set BUILD_ID=dontKillMe". Siehe hierzu: http://wiki.hudson-ci.org/display/HUDSON/ProcessTreeKiller. Falls Sie den von Hudson gesetzten Inhalt der BUILD_ID während des Jobs verwenden wollen, müssen Sie die BUILD_ID vorher in eine andere Umgebungsvariable retten.
Falls Sie CVS verwenden:
Ein CVS-Kommandozeilenclient muss installiert sein, zum Beispiel per cvsnt_setup.exe aus WinCvs2_0_2-4.zip (der cvsnt-Server-Teil kann während der Installation deaktiviert werden und WinCvs braucht nicht installiert werden).
Falls Sie folgende Fehlermeldung erhalten: "cvs checkout: Empty password used - try 'cvs login' with a real password": Dann führen Sie (unter dem richtigen Benutzer) einmalig einen Login ins CVS per Kommandozeile durch, z.B. so:
cvs.exe -d:pserver:<Benutzername>@<CvsServerUrl>:<Pfad> login
Dabei entsteht entweder im C:\Users\%USERNAME%-Verzeichnis die Passwortdatei .cvspass oder in der Windows-Registry ein cvspass-Eintrag entweder unter HKEY_CURRENT_USER\Software\cvsnt oder unter HKEY_USERS\.DEFAULT\Software\cvsnt.
Falls Sie von Hudson aus Sonar-Reports erstellen wollen und Maven 3 verwenden, beachten Sie Folgendes:
Prüfen Sie unter
http://wiki.hudson-ci.org/display/HUDSON/Sonar+plugin,
ob eine neuere Version des Sonar-Plugins verfügbar ist, die Maven 3 unterstützt.
Mit Maven 3 können Sie nicht das Sonar-Plugin Version 1.5 verwenden und deshalb auch nicht vorgehen, wie unter
http://wiki.hudson-ci.org/display/HUDSON/Sonar+plugin#Sonarplugin-Configuration
beschrieben ist.
In diesem Fall binden Sie Sonar folgendermaßen ein:
Installieren und starten Sie den Sonar-Server, wie oben unter Sourcecodeanalyse mit Sonar beschrieben ist (am besten als automatisch startender Dienst).
Klicken Sie im Hudson-Hauptmenü auf den gewünschten "Job" und wählen Sie "Konfigurieren":
"Buildverfahren", direkt hinter der Ausführung des Maven-Goals "clean install":
"Build-Schritt hinzufügen", "Maven Goals aufrufen", bei "Maven Version" den oben eingetragen Namen (z.B. "Maven 3"),
"Goals" "org.codehaus.sonar:sonar-maven3-plugin:sonar".
Normalerweise empfiehlt es sich, Hudson als automatisch beim Booten startenden Dienst einzurichten. Dann läuft Hudson bereits, ohne dass sich ein Benutzer anmelden muss. Unter Windows erfolgt dies folgendermaßen:
Bei einigen Hudson-Versionen können Sie folgendermaßen verfahren:
Starten Sie Hudson (wie oben beschrieben) und klicken Sie unter "Hudson verwalten" auf den Menüpunkt "Als Windows-Dienst installieren".
Falls dieser Menüpunkt "Als Windows-Dienst installieren" bei Ihrer Hudson-Version fehlt, verfahren Sie folgendermaßen:
Entzippen Sie in einem temporären Verzeichnis Ihre hudson-...war-Datei (z.B. hudson-1.398.war).
Dann entzippen Sie die darin enthaltene WEB-INF\lib\hudson-core-1.398.jar.
Anschließend kopieren Sie aus dem darin enthaltenen windows-service-Verzeichnis die hudson.exe und hudson.xml ins HUDSON_HOME-Verzeichnis (z.B. C:\ContIntegr\Hudson).
Öffnen Sie die hudson.xml im Hudson-Home-Verzeichnis (z.B. C:\ContIntegr\Hudson).
Suchen Sie nach dem Eintrag zur hudson-...war-Datei (z.B. hudson.war) und
korrigieren Sie diesen Eintrag entsprechend dem Namen Ihrer hudson-...war-Datei (also z.B. zu hudson-1.398.war).
Suchen Sie weiter nach "--httpPort" und tragen Sie dort die gewünschte Portnummer ein (z.B. 4424).
Suchen Sie in der Windows-Diensteverwaltung nach dem hudson-Dienst über:
"Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste".
Falls es noch keinen hudson-Dienst gibt, öffnen Sie ein Kommandozeilenfenster mit Administratorrechten über:
Start | Alle Programme | Zubehör | "Eingabeaufforderung" mit rechter Maustaste und "Als Administrator ausführen".
Darin führen Sie aus:
cd C:\ContIntegr\Hudson
hudson.exe install
Starten Sie Hudson entweder erstmalig oder neu über:
"Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" | Doppelklick auf "hudson" | "Beenden"/"Starten").
Falls Hudson-Jobs bestimmte Benutzerrechte benötigen (z.B. für den SCM-Account), wählen Sie:
"Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" | Doppelklick auf "hudson" | Reiter "Anmelden" | "Dieses Konto".
Falls Hudson beim nächsten Windows-Start nicht automatisch startet, obwohl der Dienste-Starttyp auf "Automatisch" steht,
wählen Sie:
"Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" | Doppelklick auf "hudson" |
Reiter "Wiederherstellung" | "Bei Fehlern: Dienst neu starten" und stellen eine geeignete Wartezeit ein.
Falls Sie den Hudson-Dienst deinstallieren wollen: Dies können Sie wahlweise über
hudson.exe uninstall
oder
sc delete hudson