Advanced Java Services | Stateless Session Beans 3.0 |
Einführung
Eine stateless Session Bean ist eine Bean die keine clientspezifischen Daten speichert und so eine quasibeliebige
Anzahl von Clients bedienen kann. Sie hat nur die zwei Zustände "Does not Exist" bzw. "Ready". In letzterem Zustand
kann Sie die Businessmethoden abarbeiten.
Eine Hello World EJB 3.0 stateless Sessionbean
Wir werden dieses Beispiel ohne Netbeans und ohne Eclipse entwickeln. Stattdessen arbeiten wir mit einem einfachen
Editor, etwa mit Textpad. So wird das Zusammenspiel der einzelnen Komponenten am deutlichsten. Der Übergang zu
Eclipse sollte dann kein Problem darstellen. Des weiteren verzichten wir der Einfachheit halber darauf die
Javaklassen bzw. Interfaces in Packages zu legen. Als Übung können Sie das Beispiel so abändern, daß Sie packages
einsetzen. In der Praxis werden die Klassen immer in packages gelegt.
Unser Beispiel besteht aus drei Dateien, dem Remoteinterface, der eigentlichen Beanklasse und dem
Client der den Dienst dieser EJB über das Protokoll "iiop" (Internet Inter-ORB Protocol) in Anspruch nehmen will.
Zu diesem Protokoll gibt es eine kurze Information auf Wikipedia.
Das Remoteinterface
import javax.ejb.Remote; @Remote public interface SayHello { public String sayHello(); }
Das business-interface ist in 3.0 ein ganz normales Interface geworden. Eine Ableitung vom Interface
javax.ejb.EJBObject ist nicht mehr notwendig. Wir markieren das Interface mit einer Annotation als
remote interface. Zu jeder Annotation gibt es ein entsprechendes Import. Zum kompilieren braucht man das Archiv
javaee.jar (siehe oben). Wir legen dieses jar-datei in das lib/ext-Verzeichnis des JDK und in das lib/ext-Verzeichnis
des JRE. Für die Entwicklung brauchen wir die Datei im JDK, zum Ausführen greift dann die JVM auf die Datei im JRE zu.
Die Beanklasse
import javax.ejb.Remote; import javax.ejb.Stateless; // annotations ersetzen deployment descriptor (teilweise) // deployment descriptor kann aber immer noch verwendet werden // manche einstellungen kann man im deployment descriptor überschreiben @Stateless(name="SayHello", mappedName="ejb/SayHelloJNDI") @Remote(SayHello.class) public class SayHelloBean implements SayHello { String message; public SayHelloBean() { System.out.println("constructor SayHelloBean() called"); message = "Hello EJB 3.0"; } @Override public String sayHello() { return message; } // optional @Remove public void ejbRemove() // name der methode ist jetzt beliebig { System.out.println("ejbRemove called"); } }
Die Beanklasse ist nun das, was man im EJB3.0 Jargon ein "POJO" nennt, ein "plain old java object".
Ein Import von javax.ejb.SessionBean ist nicht mehr notwendig. Die Methoden können bei Bedarf über Annotations
vereinbart werden. Zum Kompilieren braucht man das Archiv javaee.jar (siehe oben) und das Interface SayHello.class .
Erstellen des Bean-Archivs und der Deployvorgang
Das Remotinterface und die Beanklasse werden zusammen mit einem META-INF-Verzeichnis zu einer ear-Datei gepackt.
Durch den vereinfachten Aufbau dieses Archivs kann es auch die Endung .jar besitzen.
Das META-INF Verzeichnis enthält eine Manifestdatei MANIFEST.MF und optional einen Deploymentdescriptor
im XML-Format namens ejb-jar.xml. Die beiden folgenden Screenshots zeigen den Aufbau eines ear-Archivs
nach EJB 3.0 .
Die Datei ejb-jar.xml ist durch die Verwendung von Annotations bei einfachen Beans überflüssig. Wir nehmen
trotzdem eine leere Datei auf um die vollständige Struktur des Archivs zu zeigen.
Eine leere ejb-jar.xml Datei
<?xml version="1.0" encoding="UTF-8" ?> <ejb-jar> <enterprise-beans> </enterprise-beans> </ejb-jar>
Noch ein "wenig" simpler ist der Aufbau der Datei MANIFEST.MF
Die Datei MANIFEST.MF
Manifest-Version: 1.0
Die Datei MANIFEST.MF ist eine ASCII-Datei und enthält tatsächlich nur den einen oben angegebenen Eintrag.
Erstellen des Archivs mit einem Packer
Da ein ear-Archiv lediglich eine zip-Datei ist, können wir unser Archiv etwa mit dem Packer "Winrar" erstellen.
Wir markieren den gesamten Inhalt des Verzeichnisses "sayHelloBean" und öffnen über das Kontextmenü den
Eintrag "Add to Archive". Mit zwei weiteren Mausklicks erstellt uns Winrar im selben Verzeichnis eine Datei
"sayHelloBean.zip". Die Endung ".zip" ändern wir nun einfach in ".ear". Fertig.
Erstellen des Archivs mit dem Java Konsoltool jar(.exe)
Das Konsiltool jar ist dem Unix Konsoltool tar nachempunden. Wir öffnen ein Konsolfenster und
begeben uns in das Verzeichnis sayHelloBean. Der Aufruf von jar cvf meineBiene.ear *
erstellt dann ein Enterprisearchiv mit dem Namen meineBiene.ear.
Der Hauptname des ear-Archivs ist übrigens völlig beliebig, wir könnten unsere Datei genauso "wrzlbrmpft.ear" nennen.
Bei der Verwendung von Eclipse können wir das Erstellen des Archivs zur Gänze an Eclipse delegieren.
Automatisches deployen
Das fertige Archiv bringen wir nun zum Server und legen es ins Autodeployverzeichnis von Glassfish.
<SUNHOME>/SDK/domains/domain1/autodeploy
Glassfish kontrolliert periodisch den Inhalt dieses Verzeichnis und versucht neu hinzugekommene ear-Dateien (oder jar-Dateien
oder war-Dateien) einzusetzen ( to deploy = anwenden, aufstellen, einsetzen). Gelingt dies wird eine leere Datei mit dem
Namen "meineBiene.ear_deployed" angelegt.
Gelingt es nicht, so heißt die entsprechende Datei "meineBiene.ear_deployFailed"
Sieht man die Meldung "meineBiene.ear_deployed" auftauchen, so ist immerhin dieser Vorgang gelungen. Das heißt aber
noch nicht, daß die Bean wirklich gestartet worden ist, also ein Objekt der Beanklasse (in diesem Falle SayHelloBean) angelegt
worden ist. Man muß nun zum Client zurückgehen und diesen starten.
Die log-Datei server.log
Zum Austesten von Beans sollte man immer die Datei server.log im Verzeichnis <SUNHOME>/SDK/domains/domain1/logs geöffnet haben und die Einträge kontrollieren. Hier erscheinen die Fehlermeldungen falls der Deployvorgang mißlingt. Ein gelungener Deployvorgang wird folgendermaßen dokumentiert:
[#|2010-07-08T10:50:17.593+0200|INFO|sun-appserver2.1|javax.enterprise.system.core.classloading|_ThreadID=10; _ThreadName=main;sayHelloBean;|LDR5010: All ejb(s) of [sayHelloBean] loaded successfully!|#]
Der iiop-Client
import java.util.*; import javax.naming.*; public class SayHelloClient { public static void main(String[] args) { System.out.println("about to create initialcontext"); String url = "iiop://<server-ip-adress>" ; String jndiName = "ejb/SayHelloJNDI"; Properties env = new Properties(); env.put("org.omg.CORBA.ORBInitialHost","<server-ip-adress>"); // default ist localhost !! //env.put("org.omg.CORBA.ORBInitialPort","3700"); // ist default env.put("java.naming.factory.initial","com.sun.enterprise.naming.SerialInitContextFactory"); //env.put("java.naming.factory.url.pkgs","com.sun.enterprise.naming"); // hier noch nicht notwendig //env.put("java.naming.provider.url","com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl"); // hier noch nicht notwendig //env.put("java.naming.factory.state",url); // hier noch nicht notwendig try { Context ctx = new InitialContext(env); // NamingException System.out.println("initial context received"); SayHello remote = (SayHello)ctx.lookup(jndiName); // NamingException if( remote != null) System.out.println("remote class = " + remote.getClass().getName() ); else throw new NullPointerException("lookup failed"); // der wrapper hat den typ _SayHello_Wrapper und hat das remote interface implementiert, // man kann also ohne cast einfach die businessmethoden rufen System.out.println(remote.sayHello()); } catch(NamingException ex) { System.out.println("NamingException " + ex); } catch(Exception ex) { System.out.println("Exception " + ex); } // ende try - catch } // end main }
Mit dem Propertiesobjekt setzt man die Umgebung, die die JVM braucht. Hier deponiert man die notwendige
Information, damit nach dem Aufbau der TCP/IP-Verbindung das Protokoll iiop darübergelegt werden kann und
die Suche nach dem Objekt mit dem Namen "ejb/SayHelloJNDI" gestartet werden kann (lookup). Einstuegspunkt
ist dann ein Objekt der Klasse SerialInitContextFactory. Von diesem werden zahlreiche weitere Objekte
instanziiert um den reibungslosen Ablauf der Verbindung sicherzustellen.
Der Client sollte sich anstandslos kompilieren lassen. Allerdings erzeugt der Aufruf der Klasse zunächst einen Fehler:
about to create initialcontext NamingException javax.naming.NoInitialContextException: Cannot instantiate class: com.sun.enterprise.naming.SerialInitContextFactory [Root exception is java.lang.ClassNotFoundException: com.sun.enterprise.naming.SerialInitContextFactory]
Offensichtlich fehlt die Klasse "com.sun.enterprise.naming.SerialInitContextFactory". Und sie ist nicht
die einzige. Es fehlen noch eine ganze Reihe von Klassen. Sie befinden sich in drei Archiven, die beim Installieren
von Glassfish im Verzeichnis "SDK/lib" abgelegt werden, also auf der Serverseite. Es handelt sich dabei um die
folgenden drei Archive:
Hier sieht man den Preis, den man für die Vereinfachung der Entwicklung von Enterprise Java Beans 3.0 zahlt.
Früher brauchte man für einen Client lediglich zwei Stubklassen, sowie das Remote- und das Home-Interface.
Diese Information war für den Client ausreichend um sich über iiop mit einem Beanserver zu verbinden. Wir
brauchen allerdings nicht alle Klassen dieser drei oben angegeben jars. Ich habe die notwendigen Klassen
in ein eigenes Archiv zusammengefaßt. Sicher kann man dieses Archiv noch optimieren. Ich habe dieses Extrakt
mit einigen Stateless und Stateful Session Beans getestet, kann aber nicht garantieren, daß es nicht doch Situationen
gibt, in denen man mehr Klassen aus den drei oben erwähnten Packages braucht.
Das Konzentrat steht unter dem Namen "appserv-client.jar" zum Herunterladen bereit.
appserv-client.jar
Man sollte es wie schon bei "javaee.jar" in beide ext-Verzeichnisse legen. Die
Java-Umgebung (JRE) verfügt unter /lib/ext über ein Verzeichnis für externe Java-Archive.
Darüber hinaus enthält das JDK neben dem Compiler eine eigene Java-Umgebung (JRE) in der es auch ein Verzeichnis
/lib/ext gibt.
Wird der Javainterpreter von der Konsole aufgerufen, so entstammt er der JRE und greift dementsprechend
auf das ext-Verzeichnis der JRE zu. Auch die Entwicklungsumgebung Eclipse verwendet den Interpreter aus der JRE.
Textpad hingegen benützt beim Ausführen der Javaprogramme den Interpreter der in JDK eingelagerten JRE.
Hinweis
Für Message-Driven-Beans sind zwei weitere Archive auf Clientseite notwendig. Man lese hierzu erst das
Kapitel über JMS (Java Message Service).
Die Konsolausgabe des Client
Wie man sieht, ist der Verbindungsaufbau gelungen und die Bean hat uns eine Antwort geschickt.
System.out-Meldungen der Bean landen in der log-Datei server.log
System.out-Meldungen der Bean werden nicht auf die Serverkonsole ausgegeben sondern landen in der log-Datei:
[#|2010-07-08T11:14:20.937+0200|INFO|sun-appserver2.1|javax.enterprise.system.stream.out|_ThreadID=16; _ThreadName=p: thread-pool-1; w: 7;|constructor SayHelloBean() called|#]
Dadurch kann man Fehler mit Hilfe der Datei server.log aufspüren. Man sieht hier auch, daß der Deployvorgang nicht automatisch das Erstellen eine Beanobjekts nach sich zieht. Erst die Clientanfrage führt zur Instanziierung des Beanobjekts.