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:
Erzeugtes Projekt


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".
Stateful Session Bean
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.
Webclient, EJB-Referenz

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.