Beispiel: Stateless Session Bean und Maven
Inhalt:
Anlegen des Projekts
Konfiguration des Projekts
Code
Plugins für den Build
Ausführen
Debuggen
Import
Maven-KnowHow
Für WildFly 33 und JakartaEE10: hier wird ein EAR-Projekt, bestehend aus EJB-Projekt und Web-Projekt, mittels Maven erstellt und deployed.
Hier gibt es das gepackte Eclipse-Projekt zum Download (die Importanleitung findet man am Ende dieses Dokuments): StatelessMaven.zip
Das Beispiel basiert auf diesem Blog: https://onehumanunit.github.io/blog/2015/01/16/creating-an-application-with-wildfly-java-ee-rest-and-backbone-dot-js-part-1/
Anlegen des Projekts
Wir erstellen ein neues Projekt "Maven" - "Maven Project":
Den Catalog "Maven Central" auswählen, dann das Artifact "wildfly-jakartaee-ear-archetype" wählen:
Im nächsten Schritt geben wir eine "GroupId" (kann man als Package betrachten) und die "ArtifactId" (Name der Anwendung) an. Im Beispiel sind das "de.hsrm.jakartaee.knauf" und "StatelessMaven". Eclipse schlägt dadurch
als Root-Package der Anwendung "de.hsrm.jakartaee.knauf.StatelessMaven" vor - das sollten wir der Java-Konvention zuliebe in LowerCase ändern: "de.hsrm.jakartaee.knauf.statelessmaven".
Da ich im letzten Schritt die Checkbox "run archetype generation interactively" gesetzt hatte (Defaultwert), muss man jetzt ein Konsolenfenster suchen, in dem man einmal "Y" eingeben muss:
Das Ergebnis sieht so aus (vier Projekte, wobei "StatelessMaven" das Wurzelprojekt ist, das wiederum drei Unterprojekte enthält). In jedem Projekt gibt es eine "pom.xml"
Aus Festplatte ist die Verzeichnisstruktur so, dass das Projekt "StatelessMaven" seine drei Unterprojekte in Unterverzeichnissen enthält.
Cleanup:
Der Archetype erzeugt einige Codefragmente, die nicht relevant sind (siehe Datei "README.txt" im Root-Projekt):
Konfiguration des Projekts
Die Hauptkonfiguration (Java-Version, WildFly-Version) steckt in der Datei "pom.xml" des Wurzelprojekts "StatelessMaven". Um daran etwas zu ändern, führt man einen Doppelklick auf die Datei durch =>
Es öffnet sich der "Maven POM Editor". In diesem wechseln wir auf den Karteireiter "pom.xml", um direkt die XML-Datei zu bearbeiten:
Die Java-Version ist hier definiert:
<properties>
...
<!-- maven-compiler-plugin -->
<maven.compiler.release>11</maven.compiler.release>
</properties>
Die WildFly-Abhängigkeiten werden an dieser Stelle definiert:
<dependencyManagement>
<dependencies>
....
<!-- JBoss distributes a complete set of Jakarta EE 8 APIs including
a Bill of Materials (BOM). A BOM specifies the versions of a "stack" (or
a collection) of artifacts. We use this here so that we always get the correct
versions of artifacts. Here we use the wildfly-jakartaee-8.0-with-tools stack
(you can read this as the WildFly stack of the Jakarta EE 8 APIs -->
<dependency>
<groupId>org.wildfly.bom</groupId>
<artifactId>wildfly-ee-with-tools</artifactId>
<version>${version.jboss.bom}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Diese BOM (="Bill of Materials") befindet sich im Maven-Repository unter https://repo.maven.apache.org/maven2/org/wildfly/bom/wildfly-ee-with-tools/.
Schaut man in dessen pom.xml, findet man die weiteren Abhängigkeiten - das wird über mehrere Schritte gehen.
Die konkrete WildFly-Version steht in den Properties:
<properties>
...
<!-- Define the version of the JBoss BOMs we want to import to specify tested stacks. -->
<version.jboss.bom>34.0.1.Final</version.jboss.bom>
</properties>
Diese WildFly-BOM stellt uns eine Sammlung aller JARs zur Verfügung, die zur Public API von WildFly gehören, darunter auch die Libraries des JavaEE-Standard (EJB, Web, ...). Wird in einer der "pom.xml"-Dateien der Untermodule
dieses Projekts eine solche JAR-Datei referenziert, muss man keine Version deklarieren, da die Dependency im WildFly-BOM gefunden und automatisch die von WildFly angebotene Version verwendet wird. Man bindet also
nur die Dependency unter ihrer GroupId/ArtifactId ein, und Maven kann sie automatisch auflösen.
Beim Wechsel auf eine neue WildFly-Version muss man theoretisch nur diese Versionsnummer ändern. Es kann aber sein, dass dadurch Dependencies in den Untermodulen ungültig werden,
weil sie z.B. umbenannt wurden. Hier hat man also vermutlich weiteren Anpassungsaufwand.
Von Änderungen an "pom.xml" kriegt Eclipse nichts mit. Deshalb muss im Contextmenü von "StatelessMaven" der Punkt "Maven" => "Update Project" aufgerufen werden:
Im folgenden Fenster belassen wir alles auf den Defaults (alle Projekte gewählt!) und klicken auf "OK":
Code
Im EJB-Projekt werden im Package de.hsrm.jakartaee.knauf.statelessmaven.ejb
zwei Klassen angelegt:
Die eigentliche EJB:
package de.hsrm.jakartaee.knauf.statelessmaven.ejb;
import java.util.logging.Logger;
import jakarta.ejb.Stateless;
@Stateless()
public class GeometricModelBean implements GeometricModelLocal
{
protected static final Logger logger = Logger.getLogger(GeometricModelBean.class.getName());
public GeometricModelBean()
{
}
public double computeCuboidVolume(double a, double b, double c) throws IllegalArgumentException
{
logger.info("computeCuboidVolume with a = " + a + ", b = " + b + ", c = " + c);
if (a <= 0 || b <= 0 || c <= 0)
throw new IllegalArgumentException("Side length <= 0");
return a * b * c;
}
public double computeCuboidSurface(double a, double b, double c) throws IllegalArgumentException
{
logger.info("computeCuboidSurface with a = " + a + ", b = " + b + ", c = " + c);
if (a <= 0 || b <= 0 || c <= 0)
throw new IllegalArgumentException("Side length <= 0");
return 2 * (a * b + b * c + c * a);
}
}
Local Interface
package de.hsrm.jakartaee.knauf.statelessmaven.ejb;
import jakarta.ejb.Local;
@Local
public interface GeometricModelLocal
{
public double computeCuboidVolume(double a, double b, double c) throws IllegalArgumentException;
public double computeCuboidSurface(double a, double b, double c) throws IllegalArgumentException;
}
Auf ein Remote Interface wird hier verzichtet. Auch die aus den früheren Beispielen bekannte InvalidParameterException
wird hier durch die Standard-Exception IllegalArgumentException
ersetzt.
Im Web-Projekt wird im Package de.hsrm.jakartaee.knauf.statelessmaven.web
das Servlet zugefügt. Der Deployment-Deskriptor "web.xml" ist nicht nötig, da das Servlet die EJB per Injection lädt.
Servlet:
package de.hsrm.jakartaee.knauf.statelessmaven.web;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.ejb.EJB;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import de.hsrm.jakartaee.knauf.statelessmaven.ejb.GeometricModelLocal;
@WebServlet(name="GeometricModelServlet", displayName="GeometricModelServlet",
urlPatterns={"/servlet/GeometricModelServlet"})
public class GeometricModelServlet extends HttpServlet
{
@EJB
private GeometricModelLocal geometricModelLocal;
private static final long serialVersionUID = 1L;
public GeometricModelServlet()
{
super();
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// This printwriter is used for outputting the response HTML:
PrintWriter printWriter = response.getWriter();
try
{
// Page header:
printWriter.write("<HTML>\r\n" + "<HEAD>\r\n" + "<TITLE>GeometricModelTest</TITLE>\r\n" +
" <meta http-equiv=\"content-type\" content=\"text/html; charset=Windows-1252\">\r\n" +
"</HEAD>\r\n"+
"<BODY>\r\n" +
"<H1>Test for the GeometricModel stateless session bean</H1>\r\n");
// Should only the form be shown or is this a submitted form and we have
// to calculate ?
if (request.getParameter("submit") == null)
{
// First call to this page: just create the form.
}
else
{
// Page was submitted: do some calculations.
double dblA = Double.parseDouble(request.getParameter("a"));
double dblB = Double.parseDouble(request.getParameter("b"));
double dblC = Double.parseDouble(request.getParameter("c"));
// /////////////////////////////////////////////////////////////
// Calculate by using the local interface:
double dblVolume = this.geometricModelLocal.computeCuboidVolume(dblA, dblB, dblC);
double dblSurface = this.geometricModelLocal.computeCuboidSurface(dblA, dblB, dblC);
printWriter.write("Volume = " + dblVolume + ", Surface = " + dblSurface + "\r\n" + "<br><br><br>\r\n");
}
// Write the form:
printWriter.write("<form action=\"./GeometricModelServlet\" method=\"post\"> \r\n" +
" a = <input type=\"text\" name=\"a\" /> <br> \r\n" +
" b = <input type=\"text\" name=\"b\" /> <br> \r\n" +
" c = <input type=\"text\" name=\"c\" /> <br> \r\n" +
" <input type=\"submit\" name=\"submit\" value=\"submit\" /> \r\n" +
"</form> \r\n" +
"</BODY> \r\n" +
"</HTML> \r\n");
}
catch (Exception ex)
{
// Output in Servlet-Log:
this.log("An error has occured: " + ex.getMessage(), ex);
printWriter.write("An error has occured: " + ex.getMessage());
}
// Aufräumen:
printWriter.flush();
printWriter.close();
}
}
Plugins für den Build
Im Folgenden werden die Plugins beschrieben, die über "pom.xml" eingebunden werden.
Der WildFly Maven Plugin:
Für das Ausführen wird ein Wildfly-spezifischer Plugin verwendet, der die EAR-Datei erzeugt und sie in "standalone.xml" einträgt.
Es handelt sich um den "WildFly Maven Plugin":
https://repository.jboss.org/nexus/content/groups/public/org/wildfly/plugins/wildfly-maven-plugin/.
Die Doku findet sich hier: https://docs.jboss.org/wildfly/plugins/maven/latest/.
Der Quellcode ist unter https://github.com/wildfly/wildfly-maven-plugin zu finden.
In "pom.xml" des Wurzelprojekts ist er deklariert:
<properties>
...
<version.wildfly.maven.plugin>5.0.1.Final</version.wildfly.maven.plugin>
...
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>${version.wildfly.maven.plugin}</version>
<inherited>true</inherited>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Es werden außerdem weitere Maven-Plugins verwendet.
In "pom.xml" des Wurzelprojekts sind ihre Versionen deklariert:
<version.compiler.plugin>3.13.0</version.compiler.plugin>
<version.ear.plugin>3.3.0</version.ear.plugin>
<version.ejb.plugin>3.2.1</version.ejb.plugin>
<version.surefire.plugin>3.0.0</version.surefire.plugin>
<version.failsafe.plugin>3.5.2</version.failsafe.plugin>
<version.war.plugin>3.4.0</version.war.plugin>
Der Maven EAR Plugin ist in "pom.xml" des EAR-Projekts definiert und generiert die EAR-Datei:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!--EAR plugin: format of output file -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<version>${version.ear.plugin}</version>
<configuration>
<!-- Tell Maven we are using Jakarta EE -->
<version>8</version>
<!-- Use Jakarta EE ear libraries as needed. Jakarta EE ear libraries
are in easy way to package any libraries needed in the ear, and automatically
have any modules (EJB-JARs and WARs) use them -->
<defaultLibBundleDir>lib</defaultLibBundleDir>
<modules>
<!-- Default context root of the web app is /StatelessMaven-web.
If a custom context root is needed, uncomment the following snippet to
register our War as a web module and set the contextRoot property -->
<!--
<webModule>
<groupId>${project.groupId}</groupId>
<artifactId>StatelessMaven-web</artifactId>
<contextRoot>/StatelessMaven</contextRoot>
</webModule>
-->
</modules>
<outputFileNameMapping>@{artifactId}@@{dashClassifier?}@.@{extension}@</outputFileNameMapping>
</configuration>
</plugin>
...
</plugins>
</build>
Webseite des Plugins: https://maven.apache.org/plugins/maven-ear-plugin/.
Im Maven-Repository ist er zu finden
unter https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-ear-plugin/
Die Definition des "outputFileName" bewirkt, dass das WAR-Modul "StatelessMaven-web.war" und das EJB-Modul "StatelessMaven-ejb.jar" heißen. Normalerweise würden sie so heißen:
"de.hsrm.cs.javaee8-StatelessMaven-ejb-0.0.1-SNAPSHOT.jar" (also "groupId-artifactId-version.jar"), und das ist unhandlich, wenn man z.B. im Web-Projekt EJB-References in "web.xml" definiert.
<outputFileNameMapping>@{artifactId}@.@{extension}@</outputFileNameMapping>
Siehe dazu: http://maven.apache.org/plugins/maven-ear-plugin/ear-mojo.html#outputFileNameMapping.
Der Maven EJB Plugin ist in "pom.xml" des EJB-Projekts definiert und kümmert sich um das Erstellen des EJB-Jar:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-ejb-plugin</artifactId>
<version>${version.ejb.plugin}</version>
<configuration>
<!-- Tell Maven we are using EJB 4.0 -->
<ejbVersion>4.0</ejbVersion>
</configuration>
</plugin>
</plugins>
</build>
Webseite des Plugins: https://maven.apache.org/plugins/maven-ejb-plugin/.
Im Maven-Repository ist er zu finden
unter https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-ejb-plugin/
Der Maven War Plugin ist in "pom.xml" des Web-Projekts definiert und erstellt die WAR-Datei:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>${version.war.plugin}</version>
<configuration>
<!-- Jakarta EE doesn't require web.xml, Maven needs to catch up! -->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
Webseite des Plugins: https://maven.apache.org/plugins/maven-war-plugin/.
Im Maven-Repository ist er zu finden
unter https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-war-plugin/
Der Maven Compiler Plugin übernimmt die eigentliche Compilierung.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${version.compiler.plugin}</version>
<configuration>
<!-- put your configurations here -->
</configuration>
</plugin>
...
</plugins>
</build>
Webseite des Plugins: https://maven.apache.org/plugins/maven-compiler-plugin/.
Im Maven-Repository ist er zu finden unter
https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/
Der Maven Surefire Plugin und der Maven Failsafe Plugin dienen zum Ausführen von Unit-Tests und sind erst in folgenden Beispielen relevant.
Ausführen
Jetzt wird eine "Run Configuration" angelegt, die die entsprechenden Maven-Aufrufe enthält. Dazu klingt man in der Toolbar auf das DropDown des "Run"-Buttons und wählt "Run Configurations" aus:
Links in der Liste wird unter "Maven Build" ein neuer Eintrag zugefügt:
Es werden diese Einstellungen vorgenommen:
Das Ergebnis sieht so aus:
Jetzt können wir auf "Run" klicken, und das Projekt wird erzeugt und deployed. Achtung: der WildFly-Server muss außerhalb (über Eclipse oder durch Aufruf der "standalone.bat") gestartet worden sein!
Die Ergebnisse des Build (EJB-JAR, Web-WAR, EAR) liegen pro Projekt im Unterverzeichnis "target". Hier ein Screenshot aus dem EAR-Projekt:
Die Java-Version in dem Maven-"Run Configuration" sollte der Java-Version entsprechen, die in "pom.xml" des Root-Projekts in den Properties eingetragen ist:
<properties>
...
<!-- maven-compiler-plugin -->
<maven.compiler.release>11</maven.compiler.release>
</properties>
Aber es würde auch mit einer neueren Java-Version funktionieren.
Die Java-Version kann man in der Run Configuration auf dem Karteireiter "JRE" einstellen:
Sofern wir uns Apache Maven herunterladen, können wir die Maven-Aufrufe auch außerhalb von Eclipse ausführen. Dazu wechseln wir ins Verzeichnis des Projekts und rufen "mvn.cmd" auf, wobei die
auszuführenden Goals als Parameter übergeben werden.
Im Beispiel musste ich "JAVA_HOME" noch auf das korrekte JDK stellen, da per Default nur eine Java Runtime registriert ist.
C:\Temp\workspace\StatelessMaven>set JAVA_HOME=C:\Temp\jdk-11.0.25\
C:\Temp\workspace\StatelessMaven>c:\temp\apache-maven-3.9.9\bin\mvn clean install wildfly:deploy
Wir erreichen das Servlet unter folgender Adresse: http://localhost:8080/StatelessMaven-web/servlet/GeometricModelServlet
Die Anwendung wird vom Maven-WildFly-Plugin in "standalone.xml" eingetragen:
<deployments>
<deployment name="StatelessMaven-ear.ear" runtime-name="StatelessMaven-ear.ear">
<content sha1="6c0fbe72e2b7c6acc4b177e09a8c4bc7b001b086"/>
</deployment>
</deployments>
Diese Datei verweist auf das Unterverzeichnis "%WILDFLY_HOME%\standalone\data\content\6c\0fbe72e2b7c6acc4b177e09a8c4bc7b001b086". Dort liegt die Datei "content", und das ist die umbenannte EAR-Datei.
Damit die Anwendung bei zukünftigen Serverstarts nicht geladen wird, wenn man nicht daran entwickelt, legen wir uns eine zweite Run Configuration zum Aufräumen an.
Hier wird folgendes Goal eingetragen:
wildfly:undeploy
Faszinierenderweise ist es ohne Probleme möglich, die per Maven erzeugte Anwendung zu debuggen. Dazu muss man nur seine Breakpoints in Eclipse setzen und
den WildFly-Server, auf den im Rahmen des Maven-Goal "wildfly:deploy" deployed wird, über Eclipse und die "Servers"-View im Deploy-Modus starten:
Vermutlich wird beim ersten Erreichen eines Breakpoints statt des Quellcodes ein Fenster mit der Meldung "Source not found" geöffnet:
Dann klickt man auf "Edit Source Lookup Path". Es öffnet sich dieser Dialog:
Man klickt auf "Add" und wählt in dem erscheinenden Dialog "Java Project":
Jetzt wählt man das Projekt aus, in dem die Klasse liegt, in der der Breakpoint gesetzt wurde:
Import
Um das Beispielprojekt in Eclipse zu importieren, sind folgende Schritte nötig:
1) Verlinkte zip-Datei in den Workspace entpacken.
2) In Eclipse: Menü "File" -> "Import" wählen. Dort unter "Maven" die Option "Existing Maven Projects" wählen:
Im nächsten Schritt wählt man als "Root directory" den Workspace selbst aus. Jetzt sollten alle vorhandenen Projekte auftauchen, und
außerdem das Projekt "StatelessMaven" und dessen Unterprojekte. Diese werden abgehakt.
Maven-KnowHow
Allgemein
Maven lädt beim Compile/Deploy/... einen Haufen Dateien herunter. Diese landen im Benutzer-Profil im Verzeichnis "%USERPROFILE%\.m2\repository\".
Eclipse: Maven Console
Es gibt eine Konsole, die es ausgibt wenn Maven z.B. Dependencies ins lokale Repository lädt:
Eclipse: Repository Index
Dieser Abschnitt ist nur relevant, wenn man Dependencies über die GUI hinzufügen will. Eigentlich ist dies nicht nötig, da man die Einträge auch händisch in "pom.xml" eintragen kann ;-).
Es scheint so, dass sogar die m2e-Entwickler von der Verwendung der im Folgenden beschriebene Option aufgrund der Verschwendung von Festplattenplatz abraten, siehe Diskussion z.B. in
https://bugs.eclipse.org/bugs/show_bug.cgi?id=403464.
Damit das Maven-Repository beim Hinzufügen von Dependencies durchsucht werden kann, muss man in den "Preferences" unter "Maven" die Option "Download repository index updates on startup" einschalten:
Das führt dazu, dass beim nächsten Eclipse-Start der zentrale Index heruntergeladen wird:
Den findet man hier: "%USERPROFILE%\.m2\repository\.cache\m2e\1.14.0\".
Es werden dabei (Stand: Jan 2020) ca. 800 MB heruntergeladen, danach arbeitet der Rechner über 5 Minuten (trotz SSD) und es wurden 4,5 GB in ein Unterverzeichnis gelegt. Spätere Updates des Index sollten inkrementell
sein und schneller gehen.
Auch im Workspace gibt es eine Kopie (?) des Repository-Index in "\.metadata\.plugins\org.eclipse.m2e.core", siehe Erklärungen in https://bugs.eclipse.org/bugs/show_bug.cgi?id=403464#c3.
Jetzt können wir Dependencies zufügen: Dazu die Datei "pom.xml" mit dem "Maven POM Editor" öffnen. Dort auf den Karteireiter "Dependencies" gehen und auf "Add..." klicken:
Jetzt tippt man im Suchfeld "Enter groupId, artifactId or sha1 prefix or pattern (*)" den Suchtext ein. Achtung: die folgende Suche kann einzige Zeit dauern - siehe Eclipse-Progress-Anzeige ganz rechts unten.
Im Beispiel wird "commons-lang3" zugefügt - eine sehr nützliche Bibliothek mit z.B. String-Funktionen:
Stand 21.12.2024
Historie:
21.12.2024: erstellt aus JavaEE8-Beispiel