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 26: hier wird ein EAR-Projekt, bestehend aus EJB-Projekt und Web-Projekt, mittels Maven erstellt und deployed.
Eine aktuelle Version für JakartaEE 10 und WildFly 34 findet sich
hier.
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.cs.javaee8" und "StatelessMaven". Eclipse schlägt dadurch
als Root-Package der Anwendung "de.hsrm.cs.javaee8.StatelessMaven" vor - das sollten wir der Java-Konvention zuliebe in LowerCase ändern: "de.hsrm.cs.javaee8.statelessmaven".
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-jakartaee8-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-jakartaee8-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>26.0.0.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.cs.javaee8.statelessmaven.ejb
zwei Klassen angelegt:
Die eigentliche EJB:
package de.hsrm.cs.javaee8.statelessmaven.ejb;
import java.util.logging.Logger;
import javax.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.cs.javaee8.statelessmaven.ejb;
import javax.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.cs.javaee8.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.cs.javaee8.statelessmaven.web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import de.hsrm.cs.javaee8.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" einegbunden 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>2.1.0.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.8.1</version.compiler.plugin>
<version.ear.plugin>3.2.0</version.ear.plugin>
<version.ejb.plugin>3.1.0</version.ejb.plugin>
<version.surefire.plugin>2.22.2</version.surefire.plugin>
<version.failsafe.plugin>2.22.2</version.failsafe.plugin>
<version.war.plugin>3.3.2</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 3.2 -->
<ejbVersion>3.2</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>
<!-- Java EE 7 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:
Eventuell erhält man hier folgende Fehlermeldung:
[ERROR] Failed to execute goal org.wildfly.plugins:wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) on project ear: Execution default-cli of
goal org.wildfly.plugins:wildfly-maven-plugin:1.0.2.Final:deploy failed: Plugin org.wildfly.plugins:wildfly-maven-plugin:1.0.2.Final or one of its dependencies could not be
resolved: Could not find artifact sun.jdk:jconsole:jar:jdk at specified path C:\Program Files\Java\jre1.8.0_152/../lib/jconsole.jar -> [Help 1]
Dies liegt daran, dass Eclipse ein Java Runtime Environment verwendet statt eines JDK.
Also wechselt man in der Run Configuration auf den Karteireiter "JRE" und wählt dort ein JDK aus - das muss man vorher in den Eclipse-Preferences eintragen:
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:\Program Files\Java\jdk1.8.0_162\
C:\Temp\workspace\StatelessMaven>c:\temp\apache-maven-3.5.0\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 garnicht 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 14.01.2022
Historie:
21.01.2018: erstellt
24.02.2018: Kapitel "Debuggen"
03.01.2019: auf WildFly 15 aktualisiert
14.02.2019: diverse Plugins aktualisiert
11.01.2020: Projekt auf Basis des Archetype "wildfly-jakartaee-ear-archetype" erstellen - dadurch keine Nacharbeiten an "pom.xml" mehr nötig, WildFly 18
03.04.2020: Maven Console in Eclipse
14.01.2022: WildFly 26, Projekt auf JavaEE8 umgestellt, "web.xml" in Beispiel entfernt.