Project Object Model (POM) im Detail

Jedem Software Entwicklungsprojekt wohnt ein eigener Charakter inne. Das bedeute im Umkehrschluss, das kein Projekt einem anderen Gleicht. Um diesem Umstand gerecht zu werden können wir in der Maven POM (Project Object Model) die Charakteristik eines jeden Projektes festlegen.

Die POM ist für jedes Projekt das, was auch als Build Logik bezeichnet wird. Es handelt sich bei der POM um eine deklarative (beschreibend) Konfiguration, die in XML notiert wird. Die POM befindet sich immer im Wurzelverzeichnis eines Maven Projektes. Eine ausfühliche Beschreibung der Maven Verzeichnisstruktur befindet sich im gleichnamigen Kapitel.

Für Maven 2 und Maven 3 Projekte wird die POM in der Modellversion 4.0 verwendet. Für Maven 4 wird es eine neue POM Modellversion (5) geben. Während Maven 1 zu späteren Maven Versionen nicht kompatibel ist wurde bei der Entwicklung von Maven 4 sehr viel Wert darauf gelegt die Abwärtskompatibilität zu erhalten.


GAV Koordinaten

Um ein Maven Artefakt eindeutig zu identifizieren zu können werden die sogenannten GAV Koordinaten herangezogen. GAV steht für: groupId, artifactId und version, die ebenfalls Bestandteil einer minimal POM sind. Die Kombination dieser drei Koordinaten müssen eineindeutig sein. Das heißt es kann keine zwei Artefakte mit gleichen GAV Parametern geben. Schauen wir uns einmal genau an was die einzelne Einträge der GAV Koordinaten ausmacht.

  • GroupID kann wie ein Namensraum verstanden werden. Im kommerziellen Umfeld wird meist der Domänen Name der Organisation verwendet. Für Maven Plugins lautet die groupId: org.apache.maven.plugins
  • ArtefactId spiegelt den Projektnamen wieder.
  • Version gibt die Versionsnummer des Artefaktes an.

Diese drei Koordinaten erzeugen einen entsprechenden Verzeichnispfad. Um das Beispiel erneut aufzugreifen schauen wir uns das Maven Compiler Plugin im Konkreten an.

<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>

Dieses Beispiel erzeugt im lokalen Maven Repository folgenden Verzeichnispfad: org/apache/maven/plugins/ maven-compiler-plugin/3.11.0. Es wird also der gleiche Mechanismus herangezogen wie in Java die Packages.

Bei der Versionsnummer gibt es allerdings eine Besonderheit. Wird der Versionsnummer das Label SNAPSHOT angefügt, kennzeichnet dies Artefakte die nicht released sind und sich noch in der Entwicklung befinden. Technisch gesehen setzt dies für Repository Manager die Konfiguration des Repositories auf redeploy. Konkret überschreibt Maven bei einem install oder deploy das vorhandene Artefakt nicht, sondern fügt diesem einen Zeitstempel an und fügt den neuen Build dem Repository hinzu hinzu. Ausführliche Informationen über die Vergabe von Versionsnummern und dem Release Prozess erfahren Sie im Kapitel Release Management.

Minimal POM

Wie bereits im Kapitel Grundlagen erwähnt folgt Maven dem Paradigma Convention over Configuration. Was besagt das möglichst alle Konfigurationsparameter mit einem default Wert vorbelegt sind und nur Abweichungen vom Standard notiert werden müssen. Dieses Konzept erlaubt es ein Projekt mit einer minimalen POM zu definieren. Hierzu werden lediglich die GAV Paramater benötigt. Zusätzliche muss noch der Package Type angegeben werden. Maven unterstützt die drei klassischen Java Pakete: jar, war und ejb. Zusätzlich gibt es noch den Maven spezifischen Pakettypen pom, der für dem Vererbungsmechanismus in POMs benötigt wird. Dazu später etwas mehr.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.elmardott.samples.maven</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
</project>
XML

Packaging

  • jar (Java Archive): Wird ursprünglich für Bibliotheken verwendet, kommt aber auch bei Standalone Desktop Anwendungen zum Einsatz.
  • war (Web Archive): Paketiert Java Webanwendungen. Das können beispielsweise RESTful Services oder Webapplikation mit einer Ausgabe als HTML sein. WAR Archive benötigen als minimale Laufzeitumgebung einen Servlet Container, können aber auch in einem vollwertigen Application Server ausgeführt werden.
  • ear (Enterprise Archive): Dieser Packagetype kann mehrere WAR und JAR Archive enthalten. EAR Archive können ausschließlich auf vollwertige Application Server deployed werden.
  • ejb (Enterprise Java Beans): EJBs stellen häufig die Geschäftslogik einer Anwendung bereit. Eine typische EJB-Architektur besteht aus drei Komponenten: Enterprise Java Beans (EJBs), dem EJB-Container und dem Anwendungsserver.
  • pom (Project Object Model): Ermöglicht den Vererbungsmechanismus der Maven POM und die Gruppierung von Abhängigkeit in sogenannten Bill of Materials (BoM).
  • maven-plugin: Kommt bei der Entwicklung für Maven Erweiterungen zum Einsatz.

Es ist auch möglich die von Maven mitgelieferten Package Types um eigene Package Types via Plugin zu erweitern.

Bereiche der POM

Natürlich können in der POM noch viel mehr Konfigurationseinstellungen notiert werden, als sie von der minimal POM gefordert werden. Dabei werden folgende Bereich unterschieden:

  • Projekt Information
  • Build Lifecycle Konnfiguration
  • Dependency Management
  • Reporting
  • Profile
  • Infrastruktur Information und Konfiguration

Die im nachfolgenden Beispiel demonstrierten Einträge basieren auf der für Maven 3.x gültigen POM Model Version 4.0.0. In diesem Kurs: Apache Maven Master Class besprechen wir sämtliche Punkte der POM in eigenständigen Kapiteln. Diese Kapitel erläutert vor allem die grundlegende Struktur einer POM und bietet zum Einen den technische Einstieg in Maven, als auch eine Referenz die tägliche Arbeit.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    < !-- =================================================== -- >
    < !-- ===============   INHERITANCE   =================== -- >
    < !-- =================================================== -- >    
    <parent>
        <groupId>com.elmardott.samples.maven</groupId>
        <artifactId>parent-pom</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>    
    
    <groupId>com.elmardott.samples.maven</groupId>
    <artifactId>sample-pom</artifactId>
    <version>1.o-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>Apache Maven Master Class</name>
    <description>Examples for the Apache Maven Master Class online Course.</description>
    <inceptionYear>01/2023</inceptionYear>
    <url>https://elmar-dott.com</url>

    <licenses>
        <license>
            <name>Apache 2.0</name>
            <url>https://www.apache.org/licenses/LICENSE-2.0</url>
        </license>
    </licenses>

    <properties>
        <snapshot.repro />
        <release.repo />
        <site.repro />
        <fetch.repo />
    </properties>
    
    <organization>
        <name>Elmar Dott Consultin</name>
        <url>https://elmar-dott.com</url>
    </organization>
    <developers>
        <developer>
            <id>CEO</id>
            <name>Elmar Dott</name>
            <roles>
                <role>CEO</role>
                <role>Architect</role>
                <role>Build Maanager</role>
                <role>Lead Developer</role>
            </roles>
            <email>elmar.dott@gmail.com</email>
            <url>https://elmar-dott.com</url>
        </developer>
    </developers>
    
    <contributors>
        <contributor>
            <email></email>
            <name></name>
            <organization></organization>
            <organizationUrl></organizationUrl>
            <properties>
                <project></project>
            </properties>
            <roles></roles>
            <timezone></timezone>
            <url></url>
        </contributor>
    </contributors>
    
    < !-- =================================================== -- >
    < !-- ===============   MULTI MODULE  =================== -- >
    < !-- =================================================== -- >
    <modules>
        <module>sub-project</module>
    </modules>
            
    < !-- =================================================== -- >
    < !-- ===============   BUILD      ====================== -- >
    < !-- =================================================== -- >
    <build>
        <pluginManagement>
            <plugins>
            ...
            </plugins>
        <pluginManagement>
        <plugins>
        ...
        </plugins>
    </build>
            
    < !-- =================================================== -- >
    < !-- ===============   DEPENDENCIES   ================== -- >
    < !-- =================================================== -- >
    <dependencyManagement>
        <dependencies>
        ...
        </dependencies>
    </dependencyManagement>
    <dependencies>        
    ...
    </dependencies>
    
    < !-- =================================================== -- >
    < !-- ===============   REPORTING    ==================== -- >
    < !-- =================================================== -- >
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>4.0.0-M11</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-project-info-reports-plugin</artifactId>
                <version>3.4.5</version>
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>index</report>
                            <report>summary</report>
                            <report>plugin-management</report>
                            <report>dependency-management</report>
                            <report>licenses</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
        </plugins>
    </reporting>           
   
    < !-- =================================================== -- >
    < !-- ===============   PROFILES     ==================== -- >
    < !-- =================================================== -- >
    <profiles>    
        <profile>
            <id>owasp</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.owasp</groupId>
                        <artifactId>dependency-check-maven</artifactId>
                        <version>8.4.0</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>check</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <outputDirectory>${directory.reporting.out}</outputDirectory>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
            
    < !-- =================================================== -- >
    < !-- ===============   INFRASTRUCTURE   ================ -- >
    < !-- =================================================== -- >
    <distributionManagement>
        <snapshotRepository>
            <id>local-nexus</id>
            <url>${snapshot.repro}</url>
        </snapshotRepository>
        <repository>
            <id>local-nexus</id>
            <url>${release.repo}</url>
        </repository>
        <site>
            <id>local-http-nexus</id>
            <name>local deployed site</name>
            <url>${site.repro}</url>
        </site>
    </distributionManagement>

    <repositories>
        <repository>
            <id>RepositoryServer</id>
            <url>${fetch.repo}</url>
            <layout>default</layout>
        </repository>
    </repositories>
     
    <pluginRepositories>
        <pluginRepository>
            <id></id>
            <layout></layout>
            <name></name>
            <releases></releases>
            <snapshots></snapshots>
            <url></url>
        </pluginRepository>
    </pluginRepositories>
    
    <scm>
        <connection>scm:https://git.elmar-dott.com/scm/repo/</connection>
        <developerConnection>scm:https://git.elmar-dott.com/scm/repo/</developerConnection>
        <url>scm:https://git.elmar-dott.com/scm/repo/</url>
    </scm>

    <issueManagement>
        <system>Redmine</system>
        <url>https://issues.elmar-dott.com/projects/</url>
    </issueManagement>


    <ciManagement>
        <system>jenkins</system>
        <url>build.elmar-dott.com</url>
        <notifiers>
            <notifier>
                <address></address>
                <configuration>
                    <project>                  </project>
                </configuration>
                <sendOnError></sendOnError>
                <sendOnFailure></sendOnFailure>
                <sendOnSuccess></sendOnSuccess>
                <sendOnWarning></sendOnWarning>
                <type></type>
            </notifier>
        </notifiers>
    </ciManagement>
</project>    
XML

POM Vererbung

Jede Projekt POM ist eine Ableitung der Super POM. Die Super POM ist Bestandteil der Maveninstallation und enthält default Werte vieler Konfigurationseinstallungen wie Beispielsweise Zielverzeichnisse.

Der einfachste Vererbungsmechanismus ist das Einbinden einer Parent POM. Die Parent POM hat die Aufgabe für unterschiedliche Projekte den Build möglichst zu vereinheitlichen. Ein wichtiger Punkt bei der Reproduzierbarkeit von Maven Builds ist die feste Definition aller verwendeten Plugings in ihrer explizit notierten Version. Aber auch die Definition von Profilen oder Testabhängigkeiten finden oft ihren Platz in einer Parent POM. Da diese Parent POM keinen Sourcecode und andere Projektdateien enthält ist der Package Type für eine Parent POM stets pom. Mit der Auslagerung solcher Projektübergreifenden Konfigurationen werden die einzelnen Projekt POMs übersichtlicher. Schauen wir uns dazu ein einfaches Beispiel an.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.elmardott.samples.maven</groupId>
    <artifactId>parent-pom</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>17</java.version>
    </properties>

    <build>
        <pluginManagement>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.3.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
            </plugin>
        </pluginManagement>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>5.10.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>    
XML
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.elmardott.samples.maven</groupId>
        <artifactId>parent-pom</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.elmardott.samples.maven</groupId>
    <artifactId>project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>21</java.version>
    </properties>
</project>
XML

In dem Beispiel definieren wir zwei eigenständige Maven Projekte: parent-pom und project. Das Projekt parent-pom hat als Package Type pom und das Projekt project hat als Package Type jar. In der Parent POM definieren wir eine Property ${java.version} die für das Compiler Plugin die Java Quell und Zielversion festlegt. Zudem definieren wir noch global das Testframework JUnit 5 mit den wichtigsten Abhängigkeiten. Die Verknüpfung der Parent POM im project erfolgt über den Eintrag <parent>. Nun werden alle Konfigurationen der Parent POM in project vererbt. Zusätzlich überschreiben wir in project die Property {java.version} mit dem Wert 21. Ohne das Compiler Plugin in der Projekt POM erneut notieren zu müssen verwendet dies nun den überschriebenen Wert 21. Damit konnten wir sogleich auch den Vererbungsmechanismus demonstrieren.

So praktisch die Verwendung einer Parent POM auch ist, gibt es einige Stolpersteine die zu beachten sind. In vielen Projekten kommt es vor, das die Projekt POM die Parent POM nicht findet, weil diese als Unterprojekt in einem Multi Modul Projekt definiert wurde. Es gibt im Netz unzählige Lösungsvorschläge, wie man den Pfad der Parent POM möglichst relativ bekannt macht. Meiner Ansicht nach sind diese Lösungen keine guten Ratschläge. Der richtige Weg eine Parent POM zu definieren ist dies in einem eigenständigem Projekt zu tun und dieses entsprechend regelmäßig zu releasen und das Artefakt auf einen Repositopry Manager zu deployen, damit es auch im Zweifel von anderen Infrastrukturkomponenten wie CI Server und so weiter aufgelöst werden kann.

Eine weitere Variante mit POM Vererbung umzugehen sind Multi Modul Projekte. Da aber das Thema sehr komplex ist und den Rahmen diese Kapitels sprengen würde habe ich dafür ein eigenständiges Kapitel Multi Modul Projekte erstellt, das alle Facetten im Detail beleuchtet.

Eine sehr spezielle Variante der Multi Module Projekte sind sogenannte Reactor POMs. Diese Konzept kommt vor allem in sogenannten MONO Repositories zum Einsatz. Bei einem MONO Repo handelt es sich um ein z. B. in Git organisiertes Quelltext Repository das mehrere eigenständige Projekte zusammenfasst. Ein einfaches Beispiel das diesen Zusammenhang schnell erläutert ist das Code Repository für diesen online Kurs. Alle Beispiele sind für sich eigenständige Projekte. Der Aufwand alle diese Mini Projekte in eigenständigen Repositories zu organisieren wäre extrem hoch, weswegen die Organisation als MONO Repo gewählt wurde.

Um bei der Aktualisierung der Beispiele nicht jedes einzelne Teilprojekt einzeln bauen zu müssen, ist es sehr praktisch wenn diese über eine Reactor zentral gebaut werden können. Dabei ist es aber auch wichtig das die Projekte unabhängig bleiben und keine Vererbung zum Parent enthalten.

Das bedeutet also in wenigen Worten zusammen gefasst, das eine Reactor POM mehrere Projekte zusammen fasst um diese gemeinsam bauen zu können ohne das es Referenzen zwischen den Projekten oder zum Parent gibt. Technisch gesehen handelt es sich daher um ein Multi Modul Projekt, in dem die Unterprojekte keinen Eintrag zum Parent haben. Dazu ein kleines Beispiel:

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <name>Sampels: Apache Maven</name>

    <groupId>com.elmardott.samples</groupId>
    <artifactId>maven</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>enforcements</module>
    </modules>
</project>
XML
<?xml version="1.0" encoding="UTF-8"?>

<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.elmardott.samples.maven</groupId>
    <artifactId>enforcement</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.version>3.8</maven.version>
        <java.jdk>17</java.jdk>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>3.4.1</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>display-info</goal>
                            <goal>enforce</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <requireJavaVersion>
                                    <version>${java.jdk}</version>
                                </requireJavaVersion>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${java.jdk}</source>
                    <target>${java.jdk}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
XML

Abschließend sind die verschieden Vererbungsmechanismen welche für POMs gelten noch einmal grafisch dargestellt, um eine konkretere Vorstellung der Thematik zu gewinnen.

Maven Properties

Auch in Maven gibt es einen Mechanismus um Variablen zu definieren. Dieser Mechanismus heißt Properties. Properties können auf drei verschieden Art und Weisen angelegt werden: in der POM, in der settings.xml und über Profile.

Variablen werden über die POM vererbt und können sowohl über Parameter in der Kommandozeile überschreiben werden, als auch in untergeordneten Instanzen der Vererbungshierarchie.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.elmardott.samples.maven</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>17</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
         </plugins>
    </build>   
</project>
XML

Das vorangestellte Listing definiert im Abschnitt <properties> die Variable java.version. Diese Variable nutzen wir in der Konfiguration des Compiler Plugins um die Quellen und Zielversion für Java bei der Erstellung des Artefaktes festzulegen. Der Zugriff auf die definierte Variable erfolgt über ${my.property} wobei die Bezeichnung my.propertiy mit java.version auszutauschen ist.

Variablen die über den Tag <properties> in einer Parent POM definiert wurden stehen automatisch allen abgeleiteten Child POMs zur Verfügung. Durch eine erneute Definition im Abschnit <properties> können diese Variablen in der Child POM überschrieben werden.

Das nachfolgende Listing zeigt, wie Properties über ein Profil definiert werden können.

<profiles>
    <profile>
        <id>myProfile</id>

        <properties>
            <enforcer.skip>false</enforcer.skip>
        </properties>
    </profile>
</profiles>
XML

Die Definition von globalen Properties die Projekt übergreifend ansprechbar sind, wie beispielsweise Passwörter und ähnliche Konfigurationen, müssen über ein Profil in der settings.xml definiert werden. Dieses Profil muss dann auch aktiv geschaltet werden. Ausführliche Informationen über den Umgang mit Profilen finden Sie im entsprechenden Abschnitt.

Vordefinierte Properties

Um die Konfiguration der vielen Maven Plugins zu vereinfachen, gibt es die Möglichkeit auf vordefinierte Properties zurück zugreifen. Typische Anwendungsfälle für die Verwendung vordefinierter Properties sind Anpassungen in den Verzeichnisstrukturen oder das Auslesen von POM Parametern wie der Projekt Versionsnummer. An dieser Stelle habe ich eine kleine Übersicht wichtiger vordefinierter (predefined) Properties zusammen getragen:

  • ${project.basedir} – Wurzelverzeichnis des aktuellen Projektes.
  • ${project.version} – (veraltet: version / pom.version) zeigt auf die (GAV) Versionsnummer des Projektes.
  • ${parent.version} – zeigt auf die (GAV) Versionsnummer des Projektes der parent POM.
  • ${env.M2_HOME} – Zugriff auf Environment-Variablen die über das Betriebssystem defineirt wurden.
  • ${settings.localRepository} – Ließt die Konfiguration der lokalen User settings.xml aus.
  • ${directory.reporting.out} – Ausgabeverzeichniss des maven site Lifecycle. Referenziert auf: ${project.basedir}/target/site/.
  • ${project.build.directory} – Referenziert auf das während des Build Vorgangs erzeugte target/ Verzeichnis.
  • ${project.build.finalName} – Enthält den vollständigen Dateinamen des zu erstellenden Artefaktes. Der finale Name setz sich wie folgt zusammen: <artifachtId><version>.<packaging>

Die hier aufgeführte List ist natürlich nicht vollständig und wird auch durch eigene Konfigurationen wie Betriebssystem Umgebungsvariablen bestimmt. Die hier gezeigten Beispiel vermitteln aber eine guten Überblick und helfen dabei auf andere hier nicht erwähnte Variablen zu schließen.

Profile

Profile sind Erweiterungen um den Build an besondere Gegebenheiten anzupassen. Es ist also möglich den in der POM definierten Prozess um weitere optionale Schritte zu erweitern. Das erlaubt uns den default Build, welcher vornehmlich lokal durch die Entwickler ausgeführt wird für zusätzliche Aufgaben wie beispielsweise das Signieren von Artefakten oder das Erzeugen der JavaDocs zu ergänzen. Diese zusätzlichen Schritte können jederzeit bei Bedarf auf unterschiedliche Art und Weise aktiviert werden.

Der große Mehrwert für die Nutzung von Profilen ist die Ausführungszeit des Standard Build kurz und möglichst unter 5 Minuten zu halten, indem seltener benötigte und zeitintensive Aktionen in ein Profil ausgelagert werden.

Maven Profile können entweder in der POM oder der settings.xml angelegt werden. Es gibt keine Begrenzung wieviel Profile maximal erstellt werden dürfen. Auch bei der Ausführung eines Builds können mehrere Profile gleichzeitig aktiviert werden.

Im ersten Schritt schauen wir uns an einem einfachen Beispiel an wie wir in der POM ein Profil anlegen können. In diesem Profil werden wir einen OWASP Dependency Report erstellen, der für die im Projekt verwendeten Bibliotheken Sicherheitsbedrohungen aufzeigt. Da das Erstellen dieses Berichtes sehr viel Zeit in Anspruch nimmt, ist es sinnvoll diese Aktion in ein eigenes Profil auszulagern.

<profiles>
    <profile>
        <id>owasp</id>
        <properties>
            <owasp.version>9.0.7</owasp.version>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.owasp</groupId>
                    <artifactId>dependency-check-maven</artifactId>
                    <version>${owasp.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>check</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <outputDirectory>${directory.reporting.out}</outputDirectory>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>
XML

Der wichtigste Punkt bei der Erstellung eines Profils ist die eindeutige Zuweisung einer Profil ID. Diese ID darf nicht mehrfach vorkommen. In einem Profil können Properties die bereits in der POM definiert wurden überschrieben werden oder durch zusätzliche Properties erweitert werden. Anschließend werden die für die Aktion benötigenden Plugins konfiguriert.

Bei der Erstellung von Profilen sollte man darauf achten feingranulare Aktionen zu definieren. Das erlaubt eine größtmögliche Flexibilität wie der Build später aufgerufen werden kann. Damit kommen wir auch schon zum nächsten Punkt, wie ein solches Profil gestartet werden kann.

Um Profile für einen Build zu aktivieren gibt es mehrere Wege. Im Tutorial über das Signieren der Artefakte ist beschrieben wie in der settings.xml Profile permanent aktiviert werden. Dieser Weg hat den Nachteil, das in jeder Maven Installation die settings.xml entsprechend angepasst werden muss, was durchaus eine Fehlerquelle darstellen kann.

Ein weiterer sehr üblicher Weg ein Profil zu aktivieren ist über die Kommandozeile mittels des -P Parameters. Diese Variante kommt vor allem bei Build Servern wie Jenkins zum Einsatz.

mvn install -P owasp

Dem Parameter -P werden mit Leerzeichen alle gewünschten Profil IDs angehängt die während des Builds ausgeführt werden sollen. Die dabei angegebene Reihenfolge bestimmt den Ablauf der Profilaufrufe.

Ein weiterer Schritt um ein Profil zu aktivieren ist die Verwendung von Regeln beziehungsweise Bedingungen. Diese Bedingungen werden in <activation /> definiert.

<profile>
    <id>owasp</id>
    <activation>
        <activeByDefault>true</activeByDefault>
        <file></file>
        <jdk></jdk>
        <os></os>
        <property></property>
    </activation>
    ...
</proifile>  
XML

Wie sich anhand der verfügbaren Einträge ableiten lässt kann ein Profil über das Vorhandensein einer Datei, eine explizite JDK Version, ein Betriebssystem oder über den Wert einer vorhandene Property aktiviert werden. Gerade letztere Option ermöglicht es Profile in einem Prozess wie beispielsweise ein Release zusammen zuschalten.

[zum Seitenanfang]

Ressourcen