Beispiel: Stateless Session Bean
Inhalt:
Anlegen der Enterprise Application
Anlegen der GeometricModelBean
Anlegen des WebClient
Server einrichten
Anlegen des Application Clients
Application Client mit Injection
Ausführen des Application Clients
Export des Workspace
Re-Import
Ohne Annotations
Für WildFly 8.2: Beispiel für eine Stateless Session Bean, auf die per Webclient und mittels Application Client
zugegriffen wird.
Eine aktuelle Version für JakartaEE10 und WildFly 30 findet sich
hier.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung
findet man am Ende dieses Dokuments): Stateless.ear
Aufbau des Beispieles
a) Bean-Klasse mit Local und Remote-Interfaces
b) Webclient: JSP-Seite
c) WebClient: Servlet
d) App-Client
Anlegen der Application
Schritt 1: Über "File" -> "New" -> "Other..." gelangen wir in den Wizard zum Hinzufügen von neuen Komponenten.
Wir wählen im Zweig "Java EE" ein "Enterprise Application Project".
Schritt 2: In dem erscheinenden Dialog geben wir dem Projekt einen Namen und wählen den WildFly 8.2-Server
aus. Die "EAR version" wird auf "6.0" geändert, auch wenn der Server JavaEE7 unterstützt.
Auf "Next" klicken.
Schritt 3: Hier könnten wir direkt die einzelnen Modulprojekte zufügen. Deshalb klicken wir auf "New Module" (siehe übernächster Screenshot)
und wählen im erscheinenden Dialog die Module "Application client module", "EJB module" und "Web module".
Das Ergebnis sehen wir hier:
Die Checkbox "Create application.xml Deployment Descriptor" wird nicht gesetzt, das wäre erst für die Variante "Ohne Annotations "
relevant.
Man klickt auf "Finish".
Beim ersten Aufruf erscheint die Abfrage ob wir zur "Java EE perspective" wechseln wollen. Diese bejahen wir (Screenshot aus einer älteren
Eclipse-Version).
Jetzt sieht man folgende Hierarchie im Project Explorer:
Anlegen der GeometricModelBean
Schritt 1: Im Project Explorer den Knoten "StatelessEJB" wählen.
Rechtsklick, im Contextmenü den Punkt "New" -> "Session Bean" wählen.
Schritt 2: Wir vergeben einen Namespace und einen Bean-Namen. Da wir Local- und Remote-Interfaces haben wollen, setzen wir die entsprechenden Checkboxen.
Die Checkbox "No-interface view" ist per Default an und wird unchecked.
ACHTUNG: Namenskonvention: Die eigentlichen Implementierung der Bean hat den Zusatz "Bean", das Local Interface hat den Zusatz "Local", das Remote Interface
den Zusatz "Remote" (teilweise gibt es auch die Konvention, dem Remote Interface keinerlei Namenszusatz zu geben). WTP kommt mit der Konvention "Bean-Klasse hat Zusatz Bean"
nicht so ganz zurecht und packt diesen Zusatz in die Namen der Interfaces. Deshalb wieder entfernen!
Im nächsten Schritt wird alles auf den Defaults belassen:
Schritt 3: Vorbereiten der Business-Methoden:
Die beiden Business-Methoden werfen eine eigene Exception. Diese wird dem Projekt so zugefügt:
Das Package "de.fhw.swtvertiefung.knauf.stateless" im Folder "StatelessEJB\ejbModule" im Projekt "StatelessEJB" auswählen. Rechtsklick,
"New" -> "Class". Eine Klasse namens "InvalidParameterException" zufügen, die von java.lang.Exception
abgeleitet ist.
public class InvalidParameterException extends Exception
{
private static final long serialVersionUID = 1L;
public InvalidParameterException(String str_Message)
{
super(str_Message);
}
}
Schritt 4: Jetzt legen wir die Business-Methoden "computeCuboidVolume" und "computeCuboidSurface" in der GeometricModelBean an.
Die gesamte Klasse sieht so aus:
package de.fhw.komponentenarchitekturen.knauf.stateless;
import java.util.logging.Logger;
import javax.ejb.Stateless;
@Stateless
public class GeometricModelBean implements GeometricModel, GeometricModelLocal
{
protected static final Logger logger = Logger.getLogger(GeometricModelBean.class.getName());
public GeometricModelBean()
{
}
public double computeCuboidVolume(double a, double b, double c) throws InvalidParameterException
{
logger.info("computeCuboidVolume with a = " + a + ", b = " + b + ", c = " + c);
if (a <= 0 || b <= 0 || c <= 0)
throw new InvalidParameterException("Side length <= 0");
return a * b * c;
}
public double computeCuboidSurface(double a, double b, double c) throws InvalidParameterException
{
logger.info("computeCuboidSurface with a = " + a + ", b = " + b + ", c = " + c);
if (a <= 0 || b <= 0 || c <= 0)
throw new InvalidParameterException("Side length <= 0");
return 2 * (a * b + b * c + c * a);
}
}
Schritt 5: Wir fügen das Local- und Remote-Interface der Bean zu. Dazu können wir uns die Refactoring-Möglichkeiten von Eclipse zu Nutze machen.
Im Project Explorer die Bean-Klasse wählen, Rechtsklick, "Refactor", "Pull up..." wählen.
Zuerst schieben wir die beiden Methoden ins Local Interface, indem wir es auswählen und die zwei Methoden markieren.
Im nächsten Schritt gibt es nix zu ändern.
Anmerkung: Eclipse generiert uns jetzt in der Bean-Klasse die "@Override"-Annotation, um die Bean-Methode als "aus dem Interface stammend"
zu markieren.
Das gleiche wiederholen wir, um die Methoden ins Remote Interface "GeometricModelRemote" zu schieben.
Anlegen des Webclients
Modul-Abhängigkeiten
Zuerst müssen wir eine Referenz auf das EJB-Projekt zufügen, damit wir die Interfaces der Bean verwenden können:
Auf "StatelessWeb" einen Rechtsklick durchführen und in die "Properties" gehen. Im Punkt "Deployment Assembly" gehen wir auf den
den Karteireiter "Manifest Entries" und klicken auf "Add...":
Hier wählen wir das Projekt "StatelessEJB" aus:
Das Ergebnis sieht so aus:
Schauen wir uns die Datei "StatelessWeb\WebContent\META-INF\Manifest.mf", so sieht sie so aus:
Manifest-Version: 1.0
Class-Path: StatelessEJB.jar
Die zweite Zeile haben wir durch den letzten Schritt zugefügt.
JSP anlegen
Den Ordner "StatelessWeb" -> "WebContent" auswählen und per Rechtsklick eine JSP zufügen.
Im ersten Schritt des Assistenten der JSP den Namen "GeometricModelTest.jsp" geben.
In Schritt 2 verwenden wir das vorgeschlagene Template.
Jetzt noch auf "Finish" klicken und den JSP-Code einbauen. Die vollständige JSP-Seite sieht so aus:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"
import="javax.ejb.EJB,de.fhw.komponentenarchitekturen.knauf.stateless.*, javax.naming.InitialContext"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>GeometricModelTest</title>
</head>
<body>
<H1>Test for the GeometricModel stateless session bean</H1>
<%
InitialContext initialContext = new InitialContext();
GeometricModelRemote geometricModel = (GeometricModelRemote) initialContext.lookup ("java:comp/env/ejb/GeometricModel");
GeometricModelLocal geometricModelLocal = (GeometricModelLocal) initialContext.lookup ("java:comp/env/ejb/GeometricModelLocal");
%>
<%
//Ist es der erste Aufruf oder wurde auf "Berechnen" geklickt.
if (request.getParameter("submit") == null)
{
//Erster Aufruf: Nur Formular ausgeben.
}
else
{
// Zweiter bis n-ter Aufruf: Berechnen.
double dblA = Double.parseDouble(request.getParameter("a") );
double dblB = Double.parseDouble(request.getParameter("b") );
double dblC = Double.parseDouble(request.getParameter("c") );
///////////////////////////////////////////////////////////////
// Berechnung über Remote-Interface.
double dblVolume = geometricModel.computeCuboidVolume( dblA, dblB, dblC);
double dblSurface = geometricModel.computeCuboidSurface( dblA, dblB, dblC);
%>
Volume = <%=dblVolume%>, Surface= <%=dblSurface%>
<br>And now with the Local Interface:
<%
dblVolume = geometricModelLocal.computeCuboidVolume( dblA, dblB, dblC);
dblSurface = geometricModelLocal.computeCuboidSurface( dblA, dblB, dblC);
%>
Volume = <%=dblVolume%>, Surface = <%=dblSurface%>
<br><br><br>
<%
}
%>
<form action="GeometricModelTest.jsp" method="post">
a = <input type="text" name="a" /> <br>
b = <input type="text" name="b" /> <br>
c = <input type="text" name="c" /> <br>
<input type="submit" name="submit" value="submit" />
</form>
</BODY>
</html>
ACHTUNG 1: Die Injection in JSP-Seiten ist nicht Teil des JavaEE-Standards:
Zitat aus der JSP-2.1-Spezifikation, Kapitel 7.1.11:
"Finally, note that injection is not supported on JSP pages or tag files. This is
because all the information represented by injection annotations needs to be known at deployment time. If an annotation is included
in a JSP page or tag file, it won't be seen at deployment time (unless the JSP was being precompiled during deployment)."
In JBoss 5 klappte die Injection (mit Workarounds), aber JBoss 7.1 scheint dies nicht zu unterstützen.
"web.xml" anlegen
Wie man im obigen Beispiel sieht, habe ich mir die Remote-/Local-Interfaces der EJB aus dem JNDI geholt. Besonderheit dabei:
ich bin den "sauberen" Weg gegangen, der die EJB nicht aus dem globalen JNDI holt, sondern dem Deployer der Anwendung die Möglichkeit
läßt, den JNDI-Namen individuell zu konfigurieren und im "Environment Naming Context" bereitzustellen. Der ENC wird mittels des
Deployment-Deskriptors "web.xml" konfiguriert.
Wir haben allerdings die Webanwendung ohne Deployment-Deskriptoren angelegt, da diese seit JavaEE6 (Servlet 3.0-Standard) optional sind.
Den hier benötigten Deployment-Deskriptor "web.xml" können wir uns generieren lassen: im Projekt "StatelessWeb" einen Rechtsklick durchführen
und den Menüpunkt "Generate Deployment Descriptor Stub" aufrufen:
Jetzt deklariert der Deployer der Anwendung (also wir ;-)) den Eintrag im "Environment Naming Context (ENC)".
Schritt 1 davon erfolgt über "web.xml":
<ejb-ref>
<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</remote>
</ejb-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</local>
</ejb-local-ref>
Das Binden dieses JNDI-Namens an einen Eintrag im Server-JNDI erfolgt über einen JBoss-spezifischen Deployment-Deskriptor namens "jboss-web.xml",
der an der gleichen Stelle wie "web.xml", also in "WebContent\WEB-INF", liegt. Er hat diesen Inhalt:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-web_8_0.xsd"
version="8.0">
<ejb-ref>
<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
<jndi-name>java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</jndi-name>
</ejb-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
<local-jndi-name>java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</local-jndi-name>
</ejb-local-ref>
</jboss-web>
In "web.xml" wird der Eintrag für den Environment Naming Context deklariert (z.B. "ejb/GeometricModel"). In "jboss-web.xml" erfolgt
das Binden von "ejb/GeometricModel" an den globalen JNDI-Namen, also "java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote".
Hinweis:
Man könnte den JNDI-Lookup auch auf den globalen JNDI-Namen durchführen:
GeometricModelRemote geometricModel =
(GeometricModelRemote) initialContext.lookup ("java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote");
Das hat aber den großen Nachteil, dass bei einer Änderung des globalen Namens alle Stellen, die auf diesen Namen zugreifen, angepaßt werden müssten.
Und manchmal ändert sich in einer JBoss-Version die Konvention für den Aufbau des globalen Namens, in JBoss 6 und früher wäre der JNDI-Name "Stateless/GeometricModelBean/remote"
gewesen - viel Spaß beim Anpassen von Code bei einer Migration.
Regeln für den Aufbau von JNDI-Namen:
Siehe
https://docs.wildfly.org/19/Developer_Guide.html#EJB_invocations_from_a_remote_client_using_JNDI
(Abschnitt nach dem großen Codestück im Kapitel "Writing a remote client application for accessing and invoking the EJBs deployed on the server")
Für ein Remote Interface ist die Regel so:
ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>
Alternativ geht auch der Präfix "java:/global" statt "ejb:" (wie in meinem Beispiel geschehen)
Für local Interfaces
muss man "java:/global" nehmen, "ejb:" ist hier nicht unterstützt (
https://community.jboss.org/thread/215535).
Achtung:
An der genannten Schema Location ist "jboss-web_8_0.xsd" momentan nicht verfügbar. Diese Datei findet sich nur im JBoss-Verzeichnis
("docs/schema"). Der JBoss-Tools-Plugin fügt zum Glück diese Datei den Eclipse-"XML Catalog" zu, dadurch wäre eine Validierung möglich.
Allerdings scheint es ein Problem im der Schema-Datei zu geben, weswegen sie nicht validiert
(
https://issues.jboss.org/browse/JBMETA-363)
Servlet anlegen:
Wir gehen auf "StatelessWeb" und erstellen mit Rechtsklick -> "New" -> "Servlet" ein neues Servlet.
Package und Name sollten so aussehen:
Im nächsten Schritt muss man eine URL angegeben unter der das Servlet später angesprochen werden soll. In diesem Beispiel
will ich das Servlet über Annotations steuern. Leider bietet Eclipse keinen Assistenten, um ein Servlet mit Annotations zu erzeugen,
es werden Einträge in "web.xml" erzeugt. Deshalb müssen die Ergebnisse dieses Schritts nachträglich überarbeitet werden. Für
dieses Beispiel können wir es beim Default "/GeometricModelServlet" belassen.
Würde man ein Servlet über "web.xml" deklarieren, müsste man in meinem Beispiel den Default
"/GeometricModelServlet" durch "/servlet/GeometricModelServlet" ersetzen:
In Schritt 3 belassen wir alles auf den Defaults.
Nacharbeiten: falls man bereits eine "web.xml" im Projekt hat, muss man "web.xml" mit dem "XML Editor" öffnen und löscht
die Elemente "servlet" und "servlet-mapping" wieder raus, weil im Folgenden Annotations dafür verwendet werden:
<web-app ...>
...
<servlet>
<description></description>
<display-name>GeometricModelServlet</display-name>
<servlet-name>GeometricModelServlet</servlet-name>
<servlet-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GeometricModelServlet</servlet-name>
<url-pattern>/GeometricModelServlet</url-pattern>
</servlet-mapping>
</web-app>
Hat man bisher noch keinen Deployment Deskriptor "web.xml" erzeugt, dann wurde das Servlet mit dieser Annotation versehen:
@WebServlet("/servlet/GeometricModelServlet")
public class GeometricModelServlet extends HttpServlet
{
...
Diese Annotation ersetzen wir durch eine ausführlichere Deklaration.
Der gesamte Code des Servlets sieht so aus:
package de.fhw.komponentenarchitekturen.knauf.stateless;
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;
/**
* Client-Servlet for the GeometricModeBean.
* It does the same actions as the JSP page.
*
*/
@WebServlet(name="GeometricModelServlet", displayName="GeometricModelServlet",
urlPatterns={"/servlet/GeometricModelServlet"})
public class GeometricModelServlet extends HttpServlet
{
/**Remote interface for Bean @see GeometricModelBean is injected by the server.
*/
@EJB
private GeometricModelRemote geometricModel;
/**Local interface for Bean @see GeometricModelBean is injected by the server.
*/
@EJB
private GeometricModelLocal geometricModelLocal;
/**Serialisierungs-ID. Nur zugefügt da Eclipse sonst meckert.
*/
private static final long serialVersionUID = 1L;
/**Konstruktor. Hier passiert nichts.
* @see javax.servlet.http.HttpServlet#HttpServlet()
*/
public GeometricModelServlet()
{
super();
}
/**Verarbeiten einer HTTP-Anfrage per GET-Request.
* Wird direkt weitergeleitet an doPost.
* GET-Requests erfolgen nur beim ersten Aufrufen der Seite.
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
this.doPost(request, response);
}
/**Process a HTTP-Post-Request.
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* 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 remote interface:
double dblVolume = this.geometricModel.computeCuboidVolume(dblA, dblB, dblC);
double dblSurface = this.geometricModel.computeCuboidSurface(dblA, dblB, dblC);
printWriter.write("Volume = " + dblVolume + " Surface= " + dblSurface + "\r\n"
+ "<br>And now with the Local Interface: <br>\r\n");
// /////////////////////////////////////////////////////////////
// Calculate by using the local interface:
dblVolume = this.geometricModelLocal.computeCuboidVolume(dblA, dblB, dblC);
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();
}
}
Man beachte die Annotation @WebServlet
, die die Informationen enthält, die uns der Assistent ansonsten in "web.xml" packt:
- "name" ist der Name des Servlet für den Server
- "displayName" gibt dem Servlet einen Anzeigenamen. Es wird nur für Server-Administrationstools verwendet.
- "urlPatterns" ist wichtig: es definiert, wie das Servlet über den Browser angesprochen wird und enthält ein Array von URLs.
Aufrufbar ist das Servlet jetzt über diese URL: http://localhost:8080/StatelessWeb/servlet/GeometricModelServlet
Das Ergebnis sieht im Project Explorer so aus:
Hinweis:
Wenn man im Servlet ohne Injection arbeiten will, führt man den JNDI-Lookup genauso aus wie in der JSP.
Server einrichten
Das Webprojekt auswählen, Rechtsklick -> "Run As..." -> "Run on Server" wählen.
Im Assistenten wählen wir als "Type" den WildFly-8.x-Server und unter "Server runtime Environment" die von uns eingerichtete Server Runtime.
Falls wir den Server bereits angelegt haben (dies ist außerdem die Default-Option beim Definieren der Server-Runtime), dann
wählen wir hier "Choose an existing server" und wählen den Server aus, ansonsten "Manually define a new server".
In Schritt 2 belassen wir die Defaults:
In Schritt 3 sollte unsere Anwendung bereits ausgewählt sein.
Wir klicken auf "Finish". Die Enterprise Application wird auf den JBoss deployed und der Server wird gestartet.
Falls das ohne Fehlermeldungen klappt erscheint ein integriertes Browserfenster für die URL
http://localhost:8080/StatelessWeb/.
Diese zeigt allerdings den Fehler "Seite nicht gefunden" (JBoss 7.x) bzw. "Forbidden" (WildFly 8.x - diese Meldung sieht man allerdings
nicht bei Verwendung des Eclipse-internen Browsers, da dies ein Internet Explorer ist, der im Standard die eigentliche
Fehlermeldung unterdrückt) an. Dies liegt daran dass unser Projekt keine Index-Seite definiert (mehr dazu im nächsten Beispiel).
Wir hängen also unsere Seite "GeometricModelTest.jsp" and und können endlich Berechnungen ausführen: http://localhost:8080/StatelessWeb/GeometricModelTest.jsp
Den neu angelegten Server erreichen wir über die Karteikarte "Servers" im unteren Bereich der Anwendung.
Nachdem wir die Anwendung jetzt zum ersten Mal deployed haben können wir sie in Zukunft nach einer Änderung aktualisieren
indem wir den Server auswählen, ihn aufklappen, die Anwendung "Stateless" auswählen und im Contextmenü die Option "Full publish" wählen.
Kurz nach einem Publish sollte auf der Karteikarte "Console", auf der die JBoss-Ausgabe landet, eine Ausgabe über ein Neu-Laden der Anwendung auftauchen.
Achtung:
Auch auf der Ebene des Servers gibt es die Option "Publish" im Contextmenü. Diese hat bei mir aber nicht immer das gewünschte Ergebnis geliefert.
Von einem "Incremental Publish" der Anwendung rate ich ebenfalls ab - lieber ein sauberes "Full Publish".
Automatisches Publish abschalten:
Leider hat Eclipse die Default-Einstellung, die Anwendung regelmäßig automatisch auf zu publishen. Das ist unnötig und kostet nur Laufzeit, deshalb wird es abgeschaltet.
Im Fenster "Servers" bei gestopptem Server Rechtsklick auf den Server => "Open" wählen.
In der Sektion "Publishing" wird "Never publish automatically" aktiviert.
Wichtig: Anschließend die Einstellungen speichern!
Anlegen des Application Clients
Im Project Explorer "StatelessClient" auswählen. Rechtsklick, und dann "New" -> "Class" wählen.
Wie im Webclient muss hier eine Abhängigkeit zum EJB-Projekt definiert werden (Rechtsklick auf "StatelessClient",
"Properties" wählen). Man fügt unter "Deployment Assembly", Karteireiter "Manifest Entries" die JAR-Datei des EJB-Projekts hinzu.
Application Client mit Injection
Für JBoss 6 und früher: http://www.jboss.org/community/docs/DOC-12835
Für WildFly 8 und JBoss 7: Der Client bekommt die EJB per Injection geschenkt, d.h. der Code ist minimal einfach:
Im folgenden der gesamte Code des Clients:
public class GeometricModelApplicationClient
{
@EJB()
public static GeometricModelRemote geometricModel;
public static void main(String[] args)
{
try
{
double dblVolume = geometricModel.computeCuboidVolume(10, 5, 7);
double dblSurface = geometricModel.computeCuboidSurface(10, 5, 7);
System.out.println("Calculated volume: " + dblVolume + ", surface: " + dblSurface);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
Es ist keine weitere Konfiguration nötig!
Jetzt noch die Klasse mit der "main"-Methode in "StatelessClient\appClientModule\META-INF\MANIFEST.MF" eintragen:
Manifest-Version: 1.0
Class-Path: StatelessEJB.jar
Main-Class: Main-Class: de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient
Die von Eclipse generierte Dummy-Klasse "Main.java" (für die uns diverse JavaDoc-Warnungen ausgespuckt werden) können wir übrigens gefahrlos löschen.
Ausführen des Application Clients
Mehr Details siehe hier: https://docs.wildfly.org/19/Developer_Guide.html#Application_Client_Reference
Das Ausführen des Application Client geht leider nicht über Eclipse-Bordmittel, da das Ausführen eines Application Client unter JBoss
ein Umfeld benötigt, das man grob als "Mini-JBoss-Server" bezeichnen kann.
Hinweis: es gibt einen Feature Request, um JBoss Tools entsprechend zu erweitern:
https://issues.jboss.org/browse/JBIDE-11517
Hierzu benötigen wir die in den JBoss deployed Anwendung als ganzes irgendwo auf der Festplatte. Ideal wäre das JBoss-Verzeichnis "deployments",
in dem die Daten schon liegen. Allerdings führen die JBoss Tools ein exploded Deployment durch, und ich habe es noch nicht geschafft,
den folgenden Aufruf daraufhin anzupassen.
Deshalb müssen wir uns eine EAR-Datei unserer Anwendung exportieren. Dies geht wie im Kapitel Export des Workspace beschrieben,
nur dass wir keine Quelldateien benötigen.
Aus dem Verzeichnis der EAR-Datei ruft man dann folgendes auf:
cd C:\temp\wildfly-8.2.0.Final\bin
.\appclient.bat x:\PfadZurEarDatei\Stateless.ear#StatelessClient.jar
Man beachte das "#", das für Java eine Datei innerhalb von "Stateless.ear" angibt.
Hinweis:
Ein auszuführender Application Client muss wohl immer Teil einer EAR-Datei sein, die außer der JAR-Datei mit dem Application Client
auch eine EJB-Jar-Datei enthält.
Wenn man alles richtig gemacht hat, führt das zu dieser Ausgabe:
C:\Temp>C:\Temp\wildfly-8.2.0.Final\bin\appclient.bat c:\Temp\Stateless.ear#Stat
elessClient.jar
Calling "C:\Temp\wildfly-8.2.0.Final\bin\appclient.conf.bat"
JAVA_HOME is not set. Unexpected results may occur.
Set JAVA_HOME to the directory of your local JDK to avoid this message.
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; sup
port was removed in 8.0
19:55:14,224 INFO [org.jboss.modules] (main) JBoss Modules version 1.3.3.Final
19:55:14,427 INFO [org.jboss.msc] (main) JBoss MSC version 1.2.2.Final
19:55:14,567 INFO [org.jboss.as] (MSC service thread 1-3) JBAS015899: WildFly 8
.2.0.Final "Tweek" starting
19:55:17,063 INFO [org.jboss.as.connector.subsystems.datasources] (ServerServic
e Thread Pool -- 8) JBAS010403: Deploying JDBC-compliant driver class org.h2.Dri
ver (version 1.3)
19:55:17,126 INFO [org.jboss.as.naming] (ServerService Thread Pool -- 19) JBAS0
11800: Activating Naming Subsystem
19:55:17,141 WARN [org.jboss.as.txn] (ServerService Thread Pool -- 21) JBAS0101
53: Node identifier property is set to the default value. Please make sure it is
unique.
19:55:17,188 INFO [org.jboss.as.webservices] (ServerService Thread Pool -- 22)
JBAS015537: Activating WebServices Extension
19:55:17,250 INFO [org.jboss.as.jacorb] (ServerService Thread Pool -- 16) JBAS0
16300: Activating JacORB Subsystem
19:55:17,188 INFO [org.jboss.as.security] (ServerService Thread Pool -- 20) JBA
S013171: Activating Security Subsystem
19:55:17,250 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1
-1) JBAS010417: Started Driver service with driver-name = h2
19:55:17,250 INFO [org.jboss.as.connector.logging] (MSC service thread 1-4) JBA
S010408: Starting JCA Subsystem (IronJacamar 1.1.9.Final)
19:55:17,344 INFO [org.jboss.as.naming] (MSC service thread 1-1) JBAS011802: St
arting Naming Service
19:55:17,344 INFO [org.jboss.as.security] (MSC service thread 1-3) JBAS013170:
Current PicketBox version=4.0.21.Final
19:55:18,374 INFO [org.jboss.ws.common.management] (MSC service thread 1-1) JBW
S022052: Starting JBoss Web Services - Stack CXF Server 4.3.2.Final
19:55:18,717 WARN [jacorb.codeset] (MSC service thread 1-2) Warning - unknown c
odeset (Cp1252) - defaulting to ISO-8859-1
19:55:18,888 INFO [org.jboss.as.connector.subsystems.datasources] (MSC service
thread 1-1) JBAS010400: Bound data source [java:jboss/datasources/ExampleDS]
19:55:18,904 INFO [org.jboss.as.jacorb] (MSC service thread 1-2) JBAS016330: CO
RBA ORB Service started
19:55:19,356 INFO [org.jboss.as.jacorb] (MSC service thread 1-3) JBAS016328: CO
RBA Naming Service started
19:55:19,434 INFO [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8
.2.0.Final "Tweek" started in 6021ms - Started 118 of 120 services (9 services a
re lazy, passive or on-demand)
19:55:19,466 INFO [org.jboss.as.server.deployment] (MSC service thread 1-4) JBA
S015876: Starting deployment of "Stateless.ear" (runtime-name: "Stateless.ear")
19:55:19,824 INFO [org.jboss.as.server.deployment] (MSC service thread 1-4) JBA
S015973: Starting subdeployment (runtime-name: "StatelessClient.jar")
19:55:19,824 INFO [org.jboss.as.server.deployment] (MSC service thread 1-2) JBA
S015973: Starting subdeployment (runtime-name: "StatelessWeb.war")
19:55:19,824 INFO [org.jboss.as.server.deployment] (MSC service thread 1-3) JBA
S015973: Starting subdeployment (runtime-name: "StatelessEJB.jar")
19:55:20,043 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeplo
ymentUnitProcessor] (MSC service thread 1-4) JNDI bindings for session bean name
d GeometricModelBean in deployment unit subdeployment "StatelessEJB.jar" of depl
oyment "Stateless.ear" are as follows:
java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponenten
architekturen.knauf.stateless.GeometricModelRemote
java:app/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen
.knauf.stateless.GeometricModelRemote
java:module/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.sta
teless.GeometricModelRemote
java:jboss/exported/Stateless/StatelessEJB/GeometricModelBean!de.fhw.kom
ponentenarchitekturen.knauf.stateless.GeometricModelRemote
19:55:20,339 INFO [org.jboss.as.server] (Thread-30) JBAS018559: Deployed "State
less.ear" (runtime-name : "Stateless.ear")
19:55:20,370 INFO [org.jboss.ejb.client] (Thread-38) JBoss EJB Client version 2
.0.1.Final
19:55:20,386 INFO [org.xnio] (Thread-38) XNIO version 3.3.0.Final
19:55:20,417 INFO [org.xnio.nio] (Thread-38) XNIO NIO Implementation Version 3.
3.0.Final
19:55:20,589 INFO [org.jboss.remoting] (Thread-38) JBoss Remoting version 4.0.6
.Final
19:55:20,792 INFO [org.jboss.ejb.client.remoting] (Remoting "endpoint" task-5)
EJBCLIENT000017: Received server version 2 and marshalling strategies [river]
19:55:20,807 INFO [org.jboss.ejb.client.remoting] (Thread-38) EJBCLIENT000013:
Successful version handshake completed for receiver context EJBReceiverContext{c
lientContext=org.jboss.ejb.client.EJBClientContext@4f47f7e8, receiver=Remoting c
onnection EJB receiver [connection=Remoting connection <53e763e4>,channel=jboss.
ejb,nodename=turbotante]} on channel Channel ID d696c244 (outbound) of Remoting
connection 694fd236 to localhost/127.0.0.1:8080
19:55:21,172 INFO [stdout] (Thread-38) Calculated volume: 350.0, surface: 310.0
19:55:21,203 INFO [org.jboss.ejb.client.remoting] (Remoting "endpoint" task-10)
EJBCLIENT000016: Channel Channel ID d696c244 (outbound) of Remoting connection
694fd236 to localhost/127.0.0.1:8080 can no longer process messages
19:55:21,281 INFO [org.jboss.as.connector.subsystems.datasources] (MSC service
thread 1-1) JBAS010409: Unbound data source [java:jboss/datasources/ExampleDS]
19:55:21,297 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1
-1) JBAS010418: Stopped Driver service with driver-name = h2
19:55:21,344 INFO [org.hibernate.validator.internal.util.Version] (MSC service
thread 1-3) HV000001: Hibernate Validator 5.1.3.Final
19:55:21,484 INFO [org.jboss.as.server.deployment] (MSC service thread 1-1) JBA
S015974: Stopped subdeployment (runtime-name: StatelessEJB.jar) in 262ms
19:55:21,500 INFO [org.jboss.as.server.deployment] (MSC service thread 1-4) JBA
S015974: Stopped subdeployment (runtime-name: StatelessWeb.war) in 268ms
19:55:21,500 INFO [org.jboss.as.server.deployment] (MSC service thread 1-2) JBA
S015974: Stopped subdeployment (runtime-name: StatelessClient.jar) in 267ms
19:55:21,531 INFO [org.jboss.as.server.deployment] (MSC service thread 1-2) JBA
S015877: Stopped deployment Stateless.ear (runtime-name: Stateless.ear) in 296ms
19:55:21,546 INFO [org.jboss.as] (MSC service thread 1-2) JBAS015950: WildFly 8
.2.0.Final "Tweek" stopped in 312ms
Und mittendrin steht die Konsolenausgabe unserer Anwendung:
Calculated volume: 350.0, surface: 310.0
Auf der Serverkonsole findet sich die EJB-seitige Debuggingausgabe.
Hinweis:
Im Beispiel hat der Application Client auf eine EJB im lokalen Server zugegriffen. Wenn man auf einen remote Server zugreifen möchte,
geht das gemäß der Anleitung, die ich zu Beginn dieses Kapitels verlinkt hatte.
Allerdings hat der Aufruf "./appclient.sh --host=hostname myear.ear#appClient.jar" bei mir zu einer Fehlermeldung geführt.
java.lang.RuntimeException: org.jboss.remoting3.UnknownURISchemeException: No connection provider for URI scheme "localhost" is installed
at org.jboss.as.appclient.service.LazyConnectionContextSelector.createConnection(LazyConnectionContextSelector.java:81)
at org.jboss.as.appclient.service.LazyConnectionContextSelector.getCurrent(LazyConnectionContextSelector.java:93)
at org.jboss.as.appclient.service.LazyConnectionContextSelector.getCurrent(LazyConnectionContextSelector.java:51)
at org.jboss.ejb.client.EJBClientContext.getCurrent(EJBClientContext.java:253)
at org.jboss.ejb.client.EJBClientContext.requireCurrent(EJBClientContext.java:263)
at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:156)
...
Es klappt mit "%JBOSS_HOME\bin\appclient.bat --host=remote://hostname:4447 myear.ear#appClient.jar"
Ohne Angabe des Ports hing sich die Kommunikationsschicht scheinbar auf.
Bei
WilfFly 8 ist folgender Aufruf nötig:
"%JBOSS_HOME\bin\appclient.bat --host=http-remoting://localhost:8080 myear.ear#appClient.jar"
(oder alternativ: "-H=http-remoting://localhost:8080")
Grund ist, dass in WildFly die Menge an offenen Ports reduziert wurde, und deshalb das "Http upgrade"-Verfahren
(
http://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header) verwendet wird,
bei dem ein Http-Request an den Server geschickt wird, in dem ein Wechsel auf ein anderes Protokoll (über den gleichen Port) angefragt wird.
Mehr Infos im Bezug auf JBoss:
http://planet.jboss.org/post/wildfly_8_0_0_alpha3_released_with_support_for_ejb_invocations_over_http
Export des Workspace
Um den kompletten Workspace so zu exportieren, dass er auf einem anderen Rechner wieder importiert
werden kann, im Menü "File" den Punkt "Export..." wählen. Als "export destination" wählen wir
"EAR file".
Im nächsten Schritt die zu exportierende EAR-Anwendung "Stateless" auswählen und eine Zieldatei für den Export
angeben. Ich würde empfehlen diese Datei genauso zu benennen wie die EAR-Anwendung da der Dateiname beim Import
als Vorschlag für die neu zu erstellen EAR-Anwendung verwendet wird.
Wichtig: den Haken bei "Export source files" setzen!
Die Bedeutung der Option "Optimize for a specific server runtime" hat sich
mir bisher nicht erschlossen, scheinbar hat sie zumindest für JBoss keine Auswirkung.
Fertig!
Re-Import:
Falls wir das zu importierende Projekt bereits im Workspace haben dann sollten wir es vorher löschen.
Im Menü "File" ruft man "Import..." auf. Man wählt wiederum die Quelle "EAR file".
Im nächsten Schritt wählt man die Quelldatei (unsere eben exportierte EAR-Datei), den Namen der Ziel-EAR-Anwendung
("Stateless"), und den eben angelegten Target-Server.
Unsere Anwendung verfügt über keine Hilfsprojekte, wir können den nächsten Schritt also ignorieren.
Im letzten Schritt werden die zu importierenden Module angezeigt - es sollten per Default alle abgehakt sein.
Ohne Annotations
Alle Annotations des JavaEE6-Standards lassen sich komplett durch Deployment-Deskriptoren ersetzen.
Die Deployment-Deskriptoren gemäß JavaEE-Standard (also ohne JBoss-spezifisches) kann man sich durch Eclipse generieren lassen.
Schritt 1: Enterprise Application
Beim Erzeugen der Enterprise Application müssen wir den Haken "Generate application.xml deployment descriptor" setzen.
Achtung: trotz Setzen dieses Hakens haben EJB- und ApplicationClient-Modul keinen Deployment Deskriptor, hier ist die Web Tools Platform unvollständig.
Schritt 2: Deployment-Deskriptoren
Jetzt fügen wir die Standard-Deskriptoren für EJB-Projekt und ApplicationClient zu. Dazu Rechtsklick auf das EJB-Projekt, "Java EE Tools" =>
"Generate Deployment Descriptor Stub" aufrufen. Das erzeugt eine Datei "ejb-jar.xml".
Analog gehen wir für den Application Client vor.
Deployment-Deskriptoren im EJB-Projekt
Damit das Beispiel funktioniert, muss "ejb-jar.xml" im EJB-Projekt so geändert werden:
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar id="ejb-jar_ID" version="3.1"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd">
<display-name>StatelessEJB</display-name>
<enterprise-beans>
<session>
<description><![CDATA[This is the Stateless SessionBean "GeometricModel",
which might be used for simple geometric calculations.]]>
</description>
<display-name>GeometricModelBean</display-name>
<ejb-name>GeometricModelBean</ejb-name>
<business-local>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</business-local>
<business-remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</business-remote>
<ejb-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelBean</ejb-class>
<session-type>Stateless</session-type>
</session>
</enterprise-beans>
</ejb-jar>
Pflichtangaben gibt es eigentlich keine mehr, da alles durch Annotations gesteuert werden kann.
Wird ein Element "session" eingefügt dann muss allerdings zumindest ein "ejb-name" angegeben werden.
Durch Angabe des "ejb-name" können wir steuern unter welchem Namen JBoss die Bean im Default ins JNDI hängt ("Stateless/GeometricModelBean/local" und
"Stateless/GeometricModelBean/remote", fett markiert ist der "ejb-name"). Der Name der Bean-Klasse, also "GeometricModelBean", ist gleichzeitig der Default
wenn kein EJB-Name angegeben ist.
Deployment-Deskriptoren im Web-Projekt
Im Servlet können EJBs auch in der Annotation-freien Variante durch Injection gesetzt werden. Für JSPs ist dies leider nicht unterstützt (siehe Einschub im Annotation-Beispiel:
kein Bestandteil des offiziellen Standards). Deshalb muss auf der JSP ein JNDI-Lookup durchgeführt werden.
"web.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>StatelessWeb</display-name>
<servlet>
<description>
</description>
<display-name>GeometricModelServlet</display-name>
<servlet-name>GeometricModelServlet</servlet-name>
<servlet-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GeometricModelServlet</servlet-name>
<url-pattern>/servlet/GeometricModelServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<ejb-ref>
<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</remote>
<injection-target>
<injection-target-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</injection-target-class>
<injection-target-name>geometricModel</injection-target-name>
</injection-target>
</ejb-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</local>
<injection-target>
<injection-target-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</injection-target-class>
<injection-target-name>geometricModelLocal</injection-target-name>
</injection-target>
</ejb-local-ref>
</web-app>
Die Datei "jboss-web.xml" ist identisch zur obigen Variante mit Annotations und wird hier nicht nochmal gezeigt.
Deployment-Deskriptoren im Application-Client-Projekt
Der Standard-Deskriptor "application-client.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="6"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_6.xsd">
<display-name>StatelessClient</display-name>
<ejb-ref>
<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</remote>
<injection-target>
<injection-target-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient</injection-target-class>
<injection-target-name>geometricModel</injection-target-name>
</injection-target>
</ejb-ref>
</application-client>
Der JBoss-spezifische Deskriptor "jboss-client.xml" aus dem Application-Client-Projekt (scheinbar gibt es noch keine Version der XSD für WildFly 8,
deshalb wird hier die JBoss 6-Variante verwendet):
<?xml version="1.0" encoding="UTF-8"?>
<jboss-client xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-client_6_0.xsd"
version="6.0">
<jndi-name>StatelessClient</jndi-name>
<ejb-ref>
<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
<jndi-name>java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</jndi-name>
</ejb-ref>
</jboss-client>
Die modifizierte Version des Projekts gibt es hier: StatelessNoAnnotation.ear.
Beim Import sollte der "EAR name" von "StatelessNoAnnotation" auf "Stateless" geändert werden.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen Stateless-Beispiel existieren!
Stand 20.04.2020
Historie:
27.02.2013: Erstellt aus JBoss5-Version, angepaßt an JBoss 7.2 und Eclipse 4.1
05.05.2013: Deployment-Deskriptoren korrigiert (unnötige "xmlns"-Deklaration entfernt).
27.09.2013: WildFly 8: "http upgrade" als Protokoll für Angabe des Client-Host
12.02.2015: Anpassungen an WildFly 8
18.04.2020: Links auf die WildFly-Doku aktualisiert