Beispiel: Stateful Session Bean
Inhalt:
Anlegen der Enterprise Application
Anlegen der GeometricModelBean
Anlegen der GeometricModelStoreBean
Anlegen des WebClient
Testen des WebClient
Ohne Annotations
Beispiel für eine Stateful Session Bean, auf die per Webclient zugegriffen wird.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die
Importanleitung findet man im Stateless-Beispiel): Stateful.ear
Dieses Beispiel kann übrigens parallel zum Stateless-Beispiel auf den Server deployed werden, da die GeometricModelBean
zwar in beiden Beispielen vorkommt, aber mit unterschiedlichen Namen ins JNDI gehängt wird.
Aufbau des Beispieles
a) Stateless Bean-Klasse mit Remote-Interfaces.
b) Stateful Bean-Klasse mit Remote-Interfaces.
c) Webclient: JSP-Seite
Anlegen der Application
Ein "Enterprise Application Project" mit dem Namen "Stateful", einem EJB-Projekt und einem Webprojekt erstellen.
Im Project Explorer sollte es so aussehen:
Anlegen der StatelessBean
Die Schritte sind identisch mit denen des letzten Beispiels. Die Bean (im Package de.fhw.komponentenarchitekturen.knauf.stateful
)
hat allerdings nur ein Remote Interface "GeometricModelRemote", das LocalInterface "GeometricModelLocal" entfällt.
Die Geschäftsmethoden und die Exceptionklasse "InvalidParameterException" sind identisch.
Anlegen der StatefulBean
Die Bean hat den Namen "GeometricModelStoreBean" im Package de.fhw.komponentenarchitekturen.knauf.stateful
.
Wir wählen als "State Type" den Wert "Stateful".
Die Bean erhält automatisch die Annotation "Stateful":
@Stateful()
public class GeometricModelStoreBean
{
Wir deklarieren drei Membervariablen für die Tripel der Kantenlängen:
private Vector<Double> vectorA = null;
private Vector<Double> vectorB = null;
private Vector<Double> vectorC = null;
Außerdem gibt es zwei Business-Methoden "addCalculation" (hinzufügen eines neuen Satzes von Kantenlängen) und
"getAllCalculations" (alle bisherigen Berechnungen in String-Form zurückliefern, für die Zeilenumbrüche wird ein
HTML-Zeilenumbruch in die Rückgabe eingefügt):
public void addCalculation(double a, double b, double c) throws InvalidParameterException
{
if (a <= 0 || b <= 0 || c <= 0)
throw new InvalidParameterException("Seitenlänge <= 0");
this.vectorA.add(new Double(a));
this.vectorB.add(new Double(b));
this.vectorC.add(new Double(c));
}
public String getAllCalculations()
{
String strReturn = "";
for (int intIndex = 0; intIndex < this.vectorA.size(); intIndex++)
{
if (intIndex > 0)
strReturn += "<br>\r\n";
strReturn += "Anfrage " + intIndex + ": " + this.vectorA.get(intIndex) + "/" + this.vectorB.get(intIndex) + "/"
+ this.vectorC.get(intIndex);
}
return strReturn;
}
Eine Besonderheit werden wir hier anwenden: unsere Stateful Session Bean wird nicht unbedingt physisch neu erzeugt
wenn ein neuer Client sie anfordert, sondern es wird eventuell eine "freie" (keinem anderen Client zugeordnete) Instanz
recycled. Deshalb reicht es nicht die Vektoren im Konstruktor zu erzeugen, sondern wir müssen eine Methode implementieren
die der Container bei jedem neuen Binden an einen Client aufruft.
Dieses Binden geschieht über die Annotation "@PostConstruct":
@PostConstruct()
public void postConstruct()
{
this.vectorA = new Vector<Double>();
this.vectorB = new Vector<Double>();
this.vectorC = new Vector<Double>();
}
Das Remote Interface sollte so aussehen:
@Remote()
public interface GeometricModelStore
{
public abstract void addCalculation(double a, double b, double c) throws InvalidParameterException;
public abstract String getAllCalculations();
}
Anlegen des Webclients
Die Injection der Stateful EJB in die JSP hat leider nicht wie gewünscht geklappt, da ein zweiter Client/Browser mit völlig neuer Session trotzdem
die Daten des ersten Clients sah. Scheinbar wurde die "@PostConstruct"-Methode zur Initialisierung nicht aufgerufen. Da die JSP-Injection scheinbar sowieso
ein optionales Feature im Standard ist, wird hier ein JNDI-Lookup durchgeführt, um die Instanz der Bean zu holen.
Vorbereitung:
Damit die JSP-Seite die EJB referenzieren kann, muss eine Referenz auf die JAR-Datei zugefügt werden.
Dazu einen Rechtsklick auf den Project-Explorer-Knoten "StatefulWeb" ausführen und "Properties" wählen.
Unter dem Punkt "J2EE Module Dependencies" -> "J2EE Modules" wählt man die Option "Use EJB JARs" und setzt den
Haken beim JAR der EJB-Datei.
Im Web-Projekt wird eine JSP-Seite "GeometricModelTest.jsp" zugefügt.
Der Quelltext sieht so aus:
<%@ page import="javax.rmi.PortableRemoteObject, javax.naming.InitialContext, de.fhw.komponentenarchitekturen.knauf.stateful.*" %>
<HTML>
<HEAD>
<TITLE>
GeometricModelTest
</TITLE>
</HEAD>
<BODY>
<H1>GeometricModel mit Stateful Bean</H1>
<%
InitialContext initialContext = new InitialContext();
//Parameter parsen, falls vorhanden.
GeometricModelStoreRemote geometricModelStore = null;
if (request.getParameter("submit") == null)
{
//Erster Aufruf: Wir erzeugen den Store.
//Stateful Session Bean holen !
Object objGeometricModelStore = initialContext.lookup ("java:comp/env/ejb/GeometricModelStore");
geometricModelStore = (GeometricModelStoreRemote) PortableRemoteObject.narrow(objGeometricModelStore, GeometricModelStoreRemote.class);
//In Session-Context schreiben:
session.setAttribute("GeometricModelStore", geometricModelStore);
}
else
{
// Zweiter bis n-ter Aufruf: Berechnen.
//Den Store aus Session holen:
geometricModelStore = (GeometricModelStoreRemote) session.getAttribute("GeometricModelStore");
double dblA = Double.parseDouble(request.getParameter("a") );
double dblB = Double.parseDouble(request.getParameter("b") );
double dblC = Double.parseDouble(request.getParameter("c") );
//Zum Store zufügen:
geometricModelStore.addCalculation( dblA, dblB, dblC);
///////////////////////////////////////////////////////////////
// Berechnung über Remote-Interface.
Object objGeometricModelRef = initialContext.lookup ("java:comp/env/ejb/GeometricModel");
GeometricModelRemote geometricModel = (GeometricModelRemote) PortableRemoteObject.narrow(objGeometricModelRef, GeometricModelRemote.class);
double dblVolume = geometricModel.computeCuboidVolume( dblA, dblB, dblC);
double dblSurface = geometricModel.computeCuboidSurface( dblA, dblB, dblC);
%>
Volumen = <%=dblVolume%>, Oberfläche = <%=dblSurface%>
<br><br><br>
Ihre letzten Berechnungen: <br>
<%=geometricModelStore.getAllCalculations() %>
<%
}
%>
<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>
Besonderheit ist die Behandlung der Stateful Session Bean: damit die in ihr gespeicherten Werte zwischen den Aufrufen erhalten bleiben, wird sie nur beim ersten
Erzeugen aus dem JNDI geholt und danach in den SessionContext gespeichert. Bei allen weiteren Aufrufen wird die Instanz aus dem SessionContext wiederverwendet.
Deklaration der EJB-Referenzen in "web.xml":
"StatefulWeb" -> "WebContent" -> "WEB-INF" -> "web.xml" öffnen, im Element "web-app" wird folgendes eingefügt:
<ejb-ref>
<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>de.fhw.komponentenarchitekturen.knauf.stateful.GeometricModelRemote</remote>
</ejb-ref>
<ejb-ref>
<ejb-ref-name>ejb/GeometricModelStore</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>de.fhw.komponentenarchitekturen.knauf.stateful.GeometricModelStoreRemote</remote>
</ejb-ref>
Die JSP-Seite "GeometricModelTest.jsp" soll als Startseite möglich sein. Auch das stellen
wir über "web.xml" ein:
<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>GeometricModelTest.jsp</welcome-file>
</welcome-file-list>
Für die EJB-Referenzen müssen wir außerdem in "StatefulWeb\WebContent\WEB-INF" die Datei "jboss-web.xml" mit diesem Inhalt
anlegen:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC
"-//JBoss//DTD Web Application 5.0//EN"
"http://www.jboss.org/j2ee/dtd/jboss-web_5_0.dtd">
<jboss-web>
<context-root>StatelessWeb</context-root>
<!-- Referenz auf "GeometricModelBean" -->
<ejb-ref>
<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
<jndi-name>Stateful/GeometricModelBean/remote</jndi-name>
</ejb-ref>
<!-- Referenz auf "GeometricModelStoreBean" -->
<ejb-ref>
<ejb-ref-name>ejb/GeometricModelStore</ejb-ref-name>
<jndi-name>Stateful/GeometricModelStoreBean/remote</jndi-name>
</ejb-ref>
</jboss-web>
Testen des Webclients
Der Test erfolgt über diese URL: http://localhost:8080/StatefulWeb
Achtung: Die Stateful Session Bean ist im Session Context der Webanwendung abgelegt. Der Session Context bleibt uns so lange
zugeordnet wie wir den Browser offen haben (Erkennung erfolgt über ein Cookie), das heißt wenn wir eine neue Session und damit eine frische
Stateful Session Bean haben wollen müssen wir alle gerade offenen Instanzen des Browsers schließen.
Ohne Annotations
Jetzt der Weg ohne Annotations, nur mit Deployment-Deskriptoren:
Im Enterprise-Application-Projekt "Stateful" sieht der Deskriptor "application.xml" so aus:
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd"
id="Application_ID" version="5">
<display-name>
Stateful</display-name>
<module id="WebModule_1173543464015">
<web>
<web-uri>StatefulWeb.war</web-uri>
<context-root>StatefulWeb</context-root>
</web>
</module>
<module id="EjbModule_1173543463906">
<ejb>StatefulEJB.jar</ejb>
</module>
</application>
Die Datei "ejb-jar.xml" im EJB-Projekt muss so aussehen:
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar id="ejb-jar_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/ejb-jar_3_0.xsd">
<display-name>StatefulEJB</display-name>
<enterprise-beans>
<session>
<description>
<![CDATA[Stateless Session Bean für einfache geometrische Berechnungen.]]>
</description>
<display-name>GeometricModelBean</display-name>
<ejb-name>GeometricModelBean</ejb-name>
<business-remote>de.fhw.komponentenarchitekturen.knauf.stateful.GeometricModelRemote</business-remote>
<ejb-class>de.fhw.komponentenarchitekturen.knauf.stateful.GeometricModelBean</ejb-class>
<session-type>Stateless</session-type>
</session>
<session>
<description>
<![CDATA[Stateful Session Bean für die Speicherung der Berechnungs-Historie.]]>
</description>
<display-name>GeometricModelStoreBean</display-name>
<ejb-name>GeometricModelStoreBean</ejb-name>
<business-remote>de.fhw.komponentenarchitekturen.knauf.stateful.GeometricModelStoreRemote</business-remote>
<ejb-class>de.fhw.komponentenarchitekturen.knauf.stateful.GeometricModelStoreBean</ejb-class>
<session-type>Stateful</session-type>
<post-construct>
<lifecycle-callback-class>
de.fhw.komponentenarchitekturen.knauf.stateful.GeometricModelStoreBean
</lifecycle-callback-class>
<lifecycle-callback-method>postConstruct</lifecycle-callback-method>
</post-construct>
</session>
</enterprise-beans>
</ejb-jar>
Es gibt zwei Besonderheiten im Vergleich zum Stateless-Beispiel (fett markiert): die Deklaration der GeometricModelStoreBean als "Stateful"
und das Mapping des Lifecycle-Callbacks "PostConstruct" auf die Methode der Bean. Der Rest des Projekts ist absolut identisch mit dem Annotation-igen
Beispiel.
Die modifizierte Version des Projekts gibt es hier: StatefulNoAnnotation.ear.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen Stateful-Beispiel existieren !
Stand 28.10.2008
Historie:
28.10.2008: Erstellt aus Vorjahresbeispiel, angepaßt an JBoss 5.0 und Eclipse 3.4.