Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  2/2010  Pfeil  Java/JEE
suche: 
Dieser Artikel richtet sich an JEE-Entwickler, die Build Management und IDE in Ihren Projekten einsetzen möchten.

Glossar

IDE
Integrated Development Environment, auch Integrated Design Environment. Eine integrierte Entwicklungsumgebung ist ein Anwendungsprogramm zur Entwicklung von Software.
POM
Project Object Model. Hier werden sämtliche Informationen über das Software-Projekt in einer XML-Datei mit dem Dateinamen pom.xml (für Project Object Model) gespeichert. Diese Datei enthält alle Informationen zum Softwareprojekt und folgt einem standardisierten Format.


Java Best Practice (Teil VII): Maven und Eclipse in komplexen Java-Enterprise-Projekten

Build Management und IDE effizient einsetzen


Die Beliebtheit von Maven wächst immer mehr. Kaum ein Java-Projekt möchte auf die Vorteile des mächtigen Build-Werkzeuges verzichten. Die wesentlichen Erfolgsfaktoren von Maven sind ein vorgegebener Build Lifecycle, Dependency Management, IDE-Unterstützung und Build Profile. Der Artikel zeigt, wie diese Faktoren den Entwicklungsprozess von größeren Java-Enterprise-Projekten, die häufig aus vielen parallel zu entwickelnden Komponenten bestehen, maßgeblich vereinfachen und beschleunigen können.

Abb. 1: Multi-Modul und Vererbungsbeziehungen.
Abb. 1: Multi-Modul und Vererbungsbeziehungen. Vergrößern
Abb. 2: Multi-Modul-Projektstruktur.
Abb. 2: Multi-Modul-Projektstruktur. Vergrößern
<groupIdgt;de.ordix.maven</groupID>
<artifactIdgt;big-system</artifactID>
<packaginggt;pom</packaging>
<versiongt;1.0</version>
<modulesgt;
    <modulegt;ejb-module-1</module>
    <modulegt;ejb-module-2</module>
    <modulegt;web-module</module>
</modulesgt;
...
    <Plug-In>
      <groupId>org.apache.maven.Plug-Ins</groupId>
      <artifactId>maven-compiler-Plug-In</artifactId>
      <version>2.0.2</version>
      <configuration>
         <source>1.5</source>
Abb. 3: Konfiguration des Supermoduls big-system/pom.xml.
<parent>
    <groupId>de.ordix.maven</groupId>
    <artifactId>big-system</artifactId>
    <version>1.0</version>
</parent>
<groupId>de.ordix.maven</groupId>
<artifactId>ejb-module-1</artifactId>
<version>1.0</version>
<packaging>ejb</packaging>
...
<build>
    <Plug-Ins>
      <Plug-In>
         <groupId>org.apache.maven.Plug-Ins</groupId>
         <artifactId>maven-ejb-Plug-In</artifactId>
.....
Abb. 4: Konfiguration des Submoduls big-system/ejb-module-1/pom.xml.
<groupId>de.ordix.maven</groupId>
<artifactId>web-module</artifactId>
<version>1.0</version>
<packaging>war</packaging>
...
      <Plug-In>
        <groupId>org.apache.maven.Plug-Ins</groupId>
        <artifactId>maven-war-Plug-In</artifactId>
...
<dependencies>
    <dependency>
        <groupId>de.ordix.maven</groupId>
        <artifactId>ejb-module-1</artifactId>
        <version>1.0</version>
    </dependency>
...
Abb. 5: Konfiguration des Submoduls big-system/web-module/pom.xml.
Abb. 6: Maven Multi-Module in Eclipse.
Abb. 6: Maven Multi-Module in Eclipse.Vergrößern
Java Best Practice
Teil I: Java-Objekte in der Identitätskrise
Teil II: Konfiguration der IDE
Teil III: Typisch Ant
Teil IV: Ausnahmen sind die Regel
Teil V: Das kleine 1x1
der Software-Entwicklung

Teil VI: Data Access Object
Teil VII: Build Management
und IDE

Einleitung

Java-Enterprise-Projekte bestehen typischerweise aus vielen einzelnen Komponenten, die in eigenständigen Software-Projekten parallel entwickelt werden. Innerhalb der Komponenten werden Verantwortlichkeiten in getrennten Schichten implementiert, die oftmals ebenso in eigene Subprojekte ausgelagert sind. Eine typische Geschäftsanwendung könnte beispielsweise aus einem Swing Client, einem Web-Modul, zwei EJB-Modulen und einer Datenbank bestehen. Jedes Submodul hätte hier einen individuellen Build-Prozess. Bei größeren Entwicklungsteams und speziellen technischen Anforderungen bewirkt eine Trennung der Build-Prozesse einen wesentlich effizienteren Gesamtentwicklungsprozess.

Enterprise-Projekte mit vielen unterschiedlichen Modulen werden jedoch schnell komplex und lassen die Anforderungen an das Build Management steigen. Neben klassischen Build-Aufgaben muss sichergestellt werden, dass Konfigurationen möglichst nicht redundant definiert werden und das sich aus allen Submodulen das Gesamtsystem erzeugen lässt. Weiterhin müssen sämtliche Module einfach und effizient in einer IDE zusammenspielen. Außerdem sollten die komplexen Laufzeitabhängigkeiten zwischen den einzelnen Modulen automatisch aufgelöst werden können. Bezogen auf obiges Beispiel, in dem die Präsentationsschicht des Web-Moduls auf die Businesslogik der EJB-Module zugreift, muss der Build-Prozess die abhängigen EJB-JAR-Dateien im Klassenpfad des Web-Moduls verfügbar machen.

Multi-Module

Multi-Module in Maven sind die Antwort auf diese Problemstellung. Hierunter versteht man eine Komposition aus verschiedenen Submodulen, die zusammen das Gesamtprojekt ergeben. Super- und Submodule haben jeweils einen eigenen Build-Prozess. Wird ein Build auf Supermodulebene gestartet, führt Maven diesen ebenfalls für alle Submodule aus. Abhängigkeiten zwischen den einzelnen Modulen des Gesamtprojekts können wie jede andere Abhängigkeit (Dependency) definiert werden. Bezogen auf das Beispiel würden wir eine Abhängigkeit von dem Web-Modul zum EJB-Modul definieren (siehe Abbildung 1). Der Build-Prozess des Multi-Modul-Projekts sorgt nun dafür, dass das abhängige JAR-Archiv automatisch dem Build-Prozess unseres Web-Moduls zur Verfügung steht.

Vererbung

Neben den Multi-Modul-Funktionalitäten tragen Vererbungsbeziehungen zu weiteren Vereinfachungen bei. Hierdurch lassen sich Elemente eines Elternprojekts an verschiedene Kindprojekte weitervererben. Abhängigkeiten und Konfigurationen, die für zwei Projekte gleichermaßen gelten (beispielsweise zwei Web-Module, die das gleiche Web-Framework nutzen), werden einmalig im Elternprojekt festgelegt und sind somit nicht redundant vorhanden. Natürlich lassen sich auch Eltern/Kind- und Supermodul/Submodul-Beziehungen kombinieren. Wie in Abbildung 1 dargestellt, legen wir für die beiden EJB-Submodule das Modul Big-System als Eltern-Projekt fest. Würden wir hier eine Java 1.5 Compiler-Konfiguration definieren, so wäre diese für beide Submodule gültig.

Die Maven-Projektstruktur aufsetzen

Eine der Grundvoraussetzungen von Maven ist, dass der Projektverzeichnisbaum eine fest vorgegebene Struktur einhält. Maven verlangt beispielsweise die Existenz des Ordners src/main/java für Dateien mit Java Source Code. Typischerweise hält man gewisse Template-Projekte bereit, um nicht jedesmal eine neue Projektstruktur aufsetzen zu müssen. Fängt man auf der grünen Wiese an, hilft das Maven Archetype Plug-In, um sich je nach Projekttyp eine gewünschte Projektstruktur und POMs erzeugen zu lassen.

Abbildung 2 zeigt die Code Repository Verzeichnisstruktur unseres exemplarischen Multi-Modul-Projekts. Zu beachten ist, dass die Submodule lediglich Unterverzeichnisse des Wurzelknotens sind und dem Modulnamen im Supermodul POM entsprechen müssen (siehe Abbildung 3). IDE-spezifische Steuerdateien (z. B. .classpath, .project, .settings für Eclipse) sollten möglichst nicht im Repository enthalten sein, da diese im Normalfall durch ein entsprechendes Maven PlugIn (z. B. das Maven-Eclipse-Plug-In) erzeugt werden sollten.

POMs konfigurieren

Nachdem die verschiedenen Abhängigkeitsbeziehungen eines Java-Projekts sorgfältig geplant worden sind, müssen die entsprechenden Konfigurationen in den POMs verankert werden. Die Abbildungen 3, 4 und 5 zeigen, wie die für unser Projekt Big-System angestellten Abhängigkeitsüberlegungen aus Abbildung 1 in die POM-Dateien eingeflossen sind. Hiermit ist die initiale Maven-Projektstruktur fertig. Die eigentliche Implementierung des Systems und Verwendung des Maven Build-Prozesses kann nun starten.

Eclipse-Projekte erzeugen

Sinnvollerweise sollte man für jedes Maven Submodul ein entsprechendes Eclipse-Projekt erzeugen. Dazu müssen die einzelnen Submodule in Eclipse importiert werden. Statt diese direkt aus dem Code Repository in Eclipse zu importieren, checkt man zunächst das Gesamtprojekt unabhängig von der IDE in ein beliebiges lokales Verzeichnis aus und importiert es später in den Eclipse Workspace. Auf diese Weise behält man sich die Möglichkeit vor, auch auf Supermodul-Ebene einen Build zu starten, während dies bei einem direkten Import der Submodule in den Eclipse Workspace verloren gegangen wäre. Zu beachten ist also, dass die Arbeitskopie in keinem Eclipse Workspace Directory abgelegt wird.

Man kann nun unterschiedlich vorgehen: Entweder arbeitet man in Eclipse mit allen Submodulen oder man beschränkt sich nur auf bestimmte Module, für die man beispielsweise zuständig ist. Ist ersteres der Fall, lassen sich durch das Maven Eclipse-Plug-In [1] die nötigen Steuerdateien ohne weitere Optionen generieren. Maven wertet dabei spezifische Elemente des POM aus, um die Ressourcen .classpath (aus den Abhängigkeiten hergeleitet), .project und .settings (aus den Compiler-Plug-In-Angaben hergeleitet) zu erzeugen.

Ändert sich im Laufe des Projekts das POM, muss der Generierungsprozess erneut gestartet werden. Neben den normalen Abhängigkeiten werden auch Abhängigkeiten zwischen Submodulen durch das Plug-In erkannt und als Eclipse-Projektabhängigkeit dem Klassenpfad hinzugefügt. Hierdurch sind Änderungen im Source Code in abhängigen Modulen bereits zur Eclipse Build-Zeit sichtbar. Kompilierfehler fallen dem Entwickler so direkt auf. Sonst hätten diese erst bemerkt werden können, wenn der Maven Build die abhängige JAR-Datei aus dem Maven Repository geladen und mit der Kompilierung der Klassen begonnen hätte.

Entscheidet man sich hingegen für das Arbeiten mit bestimmten Modulen, aktiviert man im Maven Eclipse-Plug-In zusätzlich die Option useProjectReferences=false. Maven generiert in diesem Fall Steuerdateien, die keine Eclipse-Projektabhängigkeiten beinhalten. Einzelne Submodule lassen sich so fehlerlos in Eclipse importieren. Im Umkehrschluss zur ersten Variante sind Änderungen im Source Code von Abhängigkeiten erst zur Maven Build-Zeit sichtbar. In jedem Fall sollte vor dem Import der Projekte in Eclipse die Klassenpfadvariable M2_REPO angelegt sein und auf das lokale Maven Repository verweisen. Dies ist erforderlich, damit Eclipse die zum Kompilieren benötigten JAR-Dateien lokalisieren kann.

Maven in Eclipse

Abbildung 6 zeigt die einzelnen Submodule unseres Beispielprojekts als eigene Eclipse-Projekte. Die beiden Source-Verzeichnisse java und test wurden bereits durch das Maven Eclipse-Plug-In in den Klassenpfad der IDE eingefügt. Standardmäßig landen sowohl die von Eclipse als auch die von Maven kompilierten Klassen im Verzeichnis target/classes (bzw. target/test-classes). Das Verzeichnis target/classes dient als Behälter für Klassenpfad-Ressourcen des zu erzeugenden Zielarchivs. Für ein WAR-Archiv werden die Inhalte von target/classes nach webapp/WEB-INF/classes gepackt. Inhalte von src/main/resources (z. B. eine log4j.xml-Datei) gelten ebenfalls als Ressourcen, die später im Klassenpfad nötig sind und werden ebenfalls nach target/classes kopiert. Um den Klassenpfad zwischen Eclipse und dem Zielarchiv konsistent zu halten, sollte man daher das Verzeichnis src/main/resources ebenfalls dem Eclipse Klassenpfad hinzufügen. Ressourcen, die man zwar in Eclipse aber nicht im Zielarchiv benötigt (beispielsweise ein test-applicationContext.xml für JUnit) sollten in dem Klassenpfad-Verzeichnis src/test/resources untergebracht werden.

Das M2Eclipse Plug-In

Die Ausführung von Maven Builds ist neben der Kommandozeile auch von der IDE heraus möglich. Um einen Build über Eclipse zu starten, wird das M2Eclipse Plug-In [2] benötigt. Ist das Plug-In installiert, können über das Run-Menü verschiedene Maven Builds konfiguriert und gestartet werden. Statt POM-Konfigurationen direkt per XML vorzunehmen, bietet das Plug-In außerdem die Möglichkeit, einen grafischen Editor bei der Bearbeitung eines POM zu nutzen. Eines der wesentlichen Features des Plug-In ist das automatische Dependency Management. Ist ein Projekt mit dieser Option versehen, wird der Klassenpfad in Eclipse durch den Maven ClasspathContainer erweitert. Der Container sorgt dafür, dass der Klassenpfad diejenigen Bibliotheken enthält, die unmittelbar aus den Dependencies des POM hergeleitet werden können. Die statische Erzeugung der Klassenpfadeinträge durch das Maven Eclipse-Plug-In ist somit obsolet, da diese Aufgabe nun dynamisch zur Laufzeit der IDE von M2Eclipse übernommen wird.

Weitere Vorteile des M2Eclipse Plug-In stecken in dem Maven Builder. Hierdurch erreicht man, dass M2Eclipse auf die IDE Build Events clean und resource change reagiert und im Hintergrund ein gewünschtes Maven Goal ausführt. Resource change bezieht sich dabei nur auf Änderungen in den Ressourcen-Verzeichnissen und nicht etwa auf eine Java Source Code Modifikation. Ändert man beispielsweise die log4j.xml-Datei des Ressourcen-Verzeichnisses, würde der Maven Builder nach dem Speichern der Datei automatisch das Goal process-resources starten. Damit lassen sich komplexere Build-Funktionalitäten hinter einfachen Eclipse Builds verstecken. Würde die log4j-Datei einen Platzhalter ${logDirectory} definieren, der bei Ausführung des Goals process-resources durch das umgebungsspezifische Log-Verzeichnis ersetzt wird, erreichen wir durch ein Speichern der Datei, dass diese mit der gewünschten Ersetzung im Klassenpfad vorliegt.

Nutzung des Maven Build Lifecycles

Maven funktioniert grundsätzlich nach dem Plug-In-/Goal-Prinzip. Ein Plug-In bietet eine gewisse Build-Funktionalität (z. B. Compiler) und stellt verschiedene Aktionen (= Goals) zur Verfügung, die ausgeführt werden können (z. B. compile oder help). Goal-spezifische Optionen (z. B. showWarnings) werden mit der Option -D angehängt. Optionen, die dauerhaft verwendet werden, können einfach in das POM verlagert werden.

Da ein Build aus einer fest vorgegebenen Sequenz von Aktionen bestehen sollte, stellt Maven vordefinierte Lifecycles bereit [3]. Es gibt drei verschiedene Lifecycles: Clean, Default und Site.

Jeder Lifecycle besteht aus Phasen, die eine bestimmte Reihenfolge haben. Bei dem Aufruf einer Phase werden alle vorherigen sequentiell aufgerufen. Hinter jeder Phase steckt ein von Maven vorgegebener Plug-In-/Goal-Aufruf. Für die Phase compile ruft Maven zum Beispiel implizit compiler:compile auf.

Ein Maven Build wird typischerweise durch die Angabe einer Phase pro Lifecycle gestartet. Statt einer Phase kann aber auch ein direkter Plug-In-/Goal-Aufruf erfolgen. Die wichtigsten Phasen stecken im Default LifeCycle. Neben der Generierung des eigentlichen Zielarchivs unterstützt der Default Lifecycle den Entwickler dabei, möglichst stabile Working Copy-Änderungen in das Code Repository committen zu können. Das erfolgreiche Durchlaufen der Phasen test und integration-test dient hierbei als wesentliches Kriterium, ob die lokalen Änderungen eingecheckt werden sollten. Voraussetzung ist hier natürlich, dass die Phasen mit entsprechenden Test-Frameworks und ausreichend Testläufen konfiguriert sind. Um Integrationstests überhaupt lauffähig zu machen, sollten weitere Plug-Ins in das POM integriert und den Integrationstest-Phasen zugeordnet sein. Nennenswert ist hier insbesondere das Maven-SQL-Plug-In [4] zur Erzeugung von Datenbanktabellen und Testdaten sowie das Maven-Cargo-Plug-In [5] für das Deployment von WAR- oder EAR-Archiven in gängige Applicationserver.

Build-Profile und Properties

Durch die Verwendung von Maven Profilen [6] können Software-Builds möglichst umgebungsunabhängig durchgeführt werden. Das Level of Portability bestimmt, wie einfach es, ein Projekt erstmals in einer bestimmten Umgebung zu bauen. Das Level „Environment Portability“ ist erreicht, wenn sich das Projekt durch das alleinige Setzen eines Schalters sowohl in die Test- als auch Produktionsumgebung einsetzen ließe. Maven unterstützt dies durch die Möglichkeit, eigene Build-Profile zu definieren. Hinter einem Profil verbergen sich in der Regel umgebungsspezifische Properties (z. B. Datenbank-URLs), die im Build-Prozess herangezogen werden, wenn das spezifische Profil aktiviert worden ist.

Typischerweise müssen umgebungsspezifische Properties nicht nur im Build-Prozess vorliegen, sondern auch der Anwendung bekannt sein. Verwendet ein Build das Property URL-DB, um eine Test-Datenbank aufzusetzen, so muss die URL auch in der Anwendung verfügbar sein, damit eine Verbindung zu dieser Datenbank aufgebaut werden kann. Maven unterstützt dies durch einen speziellen Filtermechanismus.

Filter werden während der Phase Process Resources angewendet und sorgen dafür, dass Platzhalter innerhalb von Ressourcendateien durch die im Build eingelesenen Properties ersetzt werden. Realisieren lässt sich dieses Vorhaben dadurch, dass eine Property-Datei im Ressourcenverzeichnis als Vermittler zwischen Build und Anwendung fungiert. Die Values einer solchen Property-Datei bestehen aus Platzhaltern, die im Build-Prozess durch umgebungsspezifische Properties ersetzt werden. Als Key stehen in der Datei Property-Namen, die der Anwendung bekannt sind und beim Start der Applikation zur Variableninitialisierung eingelesen werden.

Fazit

Durch den Einsatz von Maven Multi-Modulen lassen sich Build-Prozesse größerer Java-Projekte einfach und effizient verwalten. Abhängigkeiten vieler einzelner Submodule werden automatisch per Dependency Management zugeordnet, außerdem werden redundante Konfigurationen durch Eltern/Kind-Beziehungen verhindert. Die Unterstützung von Maven bei der Eclipse-Integration ist eine weitere wesentliche Erleichterung für den Entwickler. Das Maven Eclipse-Plug-In generiert hier für das gesamte Multi-Modul-Projekt die nötigen Steuerdateien. Das M2Eclipse Plug-In bietet darüber hinaus eine optimale Möglichkeit, den Maven Build Lifecycle aus Eclipse heraus zu steuern sowie abhängige Bibliotheken einfach durch den Maven ClasspathContainer zu verwalten. Natürlich sollten nach Konfiguration der Maven- Umgebung die Vorteile des Build Lifecycles effizient genutzt werden. Insbesondere die Ausführung von JUnit- und Integrationstests mit entsprechenden Plug-Ins und Build-Profilen bieten dem Entwickler eine gute Möglichkeit, einfach und schnell die Qualität seiner Code-Änderungen zu überprüfen.


Björn Konrad (info@ordix.de).