Enterprise JavaBeans 2.1 - der letzte Schrei!

Geschäftsbohnen

Eberhard Wolff

Es steht wieder einmal ein Update der EJB Spezifikation geben: EJB 2.1 hat die Public Review-Phase überstanden und liegt mittlerweile als Proposed Final Draft vor. EJB 2.0 hat in der recht langen Public Review-Phase noch deutliche Änderungen erfahren, man denke nur daran, dass die fertig spezifizierten Dependent Objects wieder verschwunden sind und durch Entity Beans mit Local Interfaces ersetzt wurden. Allerdings hat EJB inzwischen einen recht hohen Reifegrad erreicht und dementsprechend ist – wie man auch an der Versionsnummer sieht – EJB 2.1 nur ein Abrunden von EJB 2.0 und keine große Umwälzung.

  • Integration von Web Services: Dieser Schritt ist wegen der großen Bedeutung des Themas und der bereits in den meisten Application Servern vorhandenen proprietären Lösungen auch dringend nötig.
  • Message-Driven Beans mit anderen Messaging Systemen als JMS: Hier kommen ebenfalls Web Services zum Einsatz, denn nun können auch SOAP-Nachrichten von einem Message-Driven Bean verarbeitet werden.
  • Container-Managed Timer Service: Da man in Enterprise JavaBeans keine Möglichkeiten zur Kontrolle über Threads hat, kann man Threads nicht unterbrechen und damit auch keine zeitbasierten Ereignisse implementieren, wie dies beispielsweise für Workflow-Systeme notwendig ist.
  • Die bekannten Schwächen der Enterprise JavaBeans Query Language (EJB QL) wurden behoben: Es gibt jetzt Funktionen zum Sortieren sowieso zusätzliche Aggregat-Funktionen. Außerdem gibt es zahlreiche kleinere Änderungen.

Da Web Services im Moment noch keine standardisierte Möglichkeit zur Verwaltung eines Zustandes über eine ganze Sitzung bieten, sind sie zustandslos genauso wie pures HTTP. Dementsprechend können nur Stateless Session Beans als Web Services angeboten werden. Eines der grundlegenden Prinzipien von Komponenten-Systemen wie EJBs ist, dass der Zugriff auf die Komponenten nur durch wohldefinierte Interfaces stattfindet, wie dies auch im Component Interface Pattern in [1] beschrieben ist. Grundsätzlich stellt ein Web Service lediglich neben Remote und Local Interface eine zusätzliche Möglichkeit zum Zugriff auf eine Enterprise JavaBean dar. Es wird also neben dem Remote und dem Local Interface das Web Service Endpoint Interface eingeführt. Allerdings gibt es beim Web Service Endpoint Interface kein Home Interface.

Normalerweise enthält das Home Interface Methoden, um Instanzen zu suchen oder neue zu erzeugen sowie Methoden, die auf alle Instanzen zugreifen müssen. Für Web Services macht ein solches Interface also keinen Sinn, da es wegen der Zustandslosigkeit keine individuellen Instanzen geben kann. Außerdem wäre die Rückgabe einer Referenz auf einen anderen Web Service ebenfalls problematisch. Somit werdem die Aufrufe eines Web Service-Client von einer beliebigen Instanz des Stateless Session Beans bearbeitet. Dies ist möglich, da alle Instanzen eines Stateless Session Beans aufgrund ihrer Zustandslosigkeit identisch sind. Das Web Service Endpoint Interface unterscheidet sich ansonsten technisch nicht sehr von dem Remote Interface: Es wird ebenfalls in Java als Interface definiert und muss den Vorschriften für ein JAX-RPC Interface entsprechen (siehe [2]). Somit muss auch dieses Interface java.rmi.Remote erweitern und jede Methode muss eine RemoteException werfen.

Die übertragenen Daten dürfen Basisdatentypen, Klassen wie String oder Date umfassen aber auch selbst entwickelte Klassen. Die selbst entwickelten Klassen müssen dabei entweder ihre Eigenschaften als JavaBeans Properties exportieren oder public deklariert haben. Außerdem können Arrays dieser Typen verwendet werden und auch Exceptions, die dann in SOAP Faults umgewandelt werden. Somit gibt es praktisch keine Einschränkungen für die Definition der Web Service Endpoint Interfaces. Allerdings darf das Interface keine Remote oder Local Interfaces verfügbar machen oder CMP Managed Collections.

Im Deployment Descriptor des entsprechenden Enterprise JavaBeans wird das Web Service Endpoint Interface mit Hilfe des service-endpoint Tags definiert. Für die Methoden aus dem Web Service Endpoint Interface kann man auch Transaktionsattribute definieren. Dabei sind alle Attribute erlaubt, außer dem Attribut Mandatory. Das ist darauf zurückzuführen, dass das Übertragen von Transaktionskontexten bei Web Services noch nicht spezifiziert ist. So kann außerhalb des EJB-Systems keine Transaktion begonnen werden, wie dies für Mandatory notwendig wäre. In Bezug auf Sicherheitsattribute ist es nach der Spezifikation zwar erlaubt, den Zugang zu Methoden auf bestimmte Benutzer zu beschränken. Allerdings gilt auch hier, dass es auf der Web Services Seite noch keinen etablierten Standard gibt, um diese Information zu übertragen. Damit wird hier wohl in der Realität ein Benutzer definiert werden, unter dessen Identität die Aufrufe des Web Services angearbeitet werden. Man kann dann mit dem run-as Tag im Deployment Descriptor definieren, dass alle Web Services-Aufrufe dieser Identität zugeordnet werden sollen.

Dieses Verfahren wird auch schon für Message-driven Beans verwendet. Aus diesem Interface wird dann beim Deployment durch den Application Server ein Interface in WSDL 1.1 erzeugt. Zur Kommunikation wird SOAP 1.1 verwendet. Im wesentlichen werden hier die bereits aus JAX-RPC bekannten Ansätze verwendet: Man kommt mit den typischen Web Services-Technologien nicht in Berührung, sondern bleibt in der schönen und einfachen Java-Welt, dennoch können andere Entwickler die entstandenen WSDL-Dateien in ihre eigenen Entwicklungsumgebungen importieren, um die EJB-Web Services zu verwenden. In der Implementierung des Beans muss es ein ejbCreate() zur Erzeugung neuer Instanzen geben. Es werden dann vom Application Server je nach Bedarf neue Instanzen erzeugt, die zur Beantwortung von Aufrufen verwendet werden können. Dies entspricht dem Instance Pooling Pattern aus [1].

Ein weiteres Merkmal der Implementierung besteht darin, dass es möglich ist, aus dem SessionContext den MessageContext des Web Service-Aufruf zu ermitteln. Damit kann man Informationen mit sogenannten JAX-RPC Handlern austauschen. Diese bearbeiten SOAP-Nachrichten und können Dienste wie Verschlüsselung oder Logging implementieren. Interessanterweise haben damit Enterprise JavaBeans Zugriff auf technische Interna der Web Services Umgebung, die eigentlich versteckt sein sollten, denn ein wesentliches Ziel von Enterprise JavaBeans ist ja, dass Entwickler sich nicht mit technischen Details beschäftigen müssen, sondern sich auf die Business Logik konzentrieren können. Außerdem wird sich an dieser Stelle die Bean anders verhalten, wenn sie durch die Web Services-Schnittstelle oder durch das normale Remote Interface aufgerufen wurde. Somit sollte dieses Feature nur vorsichtig eingesetzt werden.

Natürlich muss es auch eine Möglichkeit geben, wie der Entwickler einer Enterprise JavaBean einen Web Service aufrufen kann. Dazu ist in der Spezifikation festgelegt, dass ein Web Service als zusätzliche Ressource im lokalen JNDI Context eingeblendet werden kann, wie dies auch schon für Datenbanken oder JMS-Verbindungen der Fall ist. Diese sollen im JNDI-Namenskontext java:comp/env/services zu finden sein. Zur Definition der Referenzen wird das service-ref Tag für den Deployment Descriptor eingeführt. Ein Blick auf ein Beispiel eines entsprechenden Deployment Descriptors (Listing 1) zeigt, dass auch hier wieder lediglich Java-Klassen sowie der Name im JNDI Context definiert werden. Diese Klassen werden natürlich typischerweise wieder mit einem wie in JAX-RPC definierten Compiler aus WSDL erzeugt. Dies ist Aufgabe des Deployer, der die Referenzen mit Leben füllen muss. Wie dies genau geschieht, ist Application Server abhängig und damit nicht in der Spezifikation festgelegt.


...
PortfolioBeanwolff.InvestmentBean
...

Referenz zum Börsen Ticker

service/Ticker

wolff.Ticker

...

Bereits wenn man die EJB 2.0 Spezifikation aufmerksam gelesen hat, konnte einem klar werden, dass Message-Driven Beans (MDBs) neben JMS bald auch andere Arten von Message Oriented Middleware (MOM) unterstützen würden. In EJB 2.1 ist dieser Schritt vollzogen, und so kann ein Message-Driven Bean neben JMS auch mit SOAP oder genauer gesagt mit JAXM (Java API for XML Messaging) entwickelt werden. Konkret bedeutet dies, dass ein MDB wie bisher auch das javax.ejb.MessageDrivenBean Interface implementieren muss. Dieses Interface enthält jedoch nur die setMessageDrivenContext() und die ejbRemove()-Methode und definiert nicht, wie die Business Logik aufgerufen werden soll. Soll diese Logik über JMS zugreifbar sein, so bleibt alles wie bei EJB 2.0: In diesem Fall muss zusätzlich das javax.jms.MessageListener Interface implementiert werden. Soll jedoch JAXM und damit SOAP zum Einsatz kommen, so muss entweder das javax.xml.messaging.OneWayListener Interface implementiert werden oder das javax.xml.messaging.ReqRespListener Interface. Bei dem OneWayListener kann man nur auf eine Nachricht reagieren, im anderen Fall kann man auch eine Antwort zurückschicken.

Eine Antwort auf eine Nachricht war bisher mit Message-Driven Bean nicht so einfach möglich und stellt auch einen gewissen Bruch zum asynchronen Charakter der bisherigen MDBs dar. Ein Hinweis noch zum Abstraktionsgrad: Während man bei den oben erwähnten Web Services Endpoint Interfaces seine Java-Welt nicht verlässt, muss man für die JAXM-Programmierung tatsächlich SOAP Nachrichten verarbeiten. Das bedeutet zwar nicht, dass man einen XML-Datenstrom parsen muss, aber immerhin muss man die Nachricht selbst in die einzelnen Bestandteile zerlegen und diese interpretieren. Man bekommt also geparste XML-Daten, auf die man dann entsprechend reagieren muss.

Die Sicherheits- und Transaktionskonzepte der MDBs sind unabhängig davon, ob JMS oder JAXM verwendet wird: In beiden Fällen wird kein Sicherheits- und Transaktionskontext übertragen. Somit mussten für JAXM keine zusätzlichen Probleme gelöst werden. Verwirrend scheint auf den ersten Blick, dass hier noch eine weitere Lösung für Web Services angeboten wird. Dies liegt darin begründet, dass die JAXM und JAX-RPC APIs in EJB integriert werden sollten und JAXM eben eher eine Message Oriented Middleware ist und damit eher zu Message-Driven Beans passt. JAX-RPC ist näher an RMI und bietet sich dadurch als ein zusätzliches Interface für Session Beans an.

public void ejbTimeout(Timer timer) {
String infoObject = (String) timer.getInfo();
// hier kann man jetzt endsprechend der Information etwas tun

if (...) {
timer.cancel();
// damit wird der Timer nie wieder ausgelöst werden
}

}

Möglicherweise wäre es beim Timer Service sinnvoll gewesen, neben diesem Ausprogrammieren eines regelmäßigen Aufrufes auch eine Definition im Deployment Descriptor vorzusehen. Dann könnte ein Administrator dies entsprechend konfigurieren. Allerdings müsste man dann im Deploymenttool die entsprechenden Informationsobjekte eingeben können oder die Werte auf Basisdatentypen o.ä. beschränken, genau so, wie dies schon bei Environment Entries der Fall ist.

Wie sind nun die Garantien für den Timer Service? Zunächst müssen die Timer – wenig überraschend – activate/passivate oder load/store der Beans-Zyklen überleben. Schon interessanter ist, dass Timer auch noch nach Server-Abstürzen abgearbeitet werden müssen. Allerdings sind die Garantien in Bezug auf den Aufrufzeitpunkt relativ schwach. Falls nämlich gerade der entsprechende Enterprise JavaBean mit der Abarbeitung einer Business Methode beschäftigt ist, kann kein ejbTimeout() aufgerufen werden, denn innerhalb einer Bean kann immer nur ein Thread aktiv sein. Dies kann dazu führen, dass Aufrufe zu spät erfolgen. Wenn mehrere Timer für eine Bean definiert sind, kann es sogar zu Aufrufen in falscher Reihenfolge kommen oder es kann sein, dass ein Timer noch ausgeführt wird, obwohl er kurz zuvor gecancelt wurde. Die ejbTimeout()-Methode hat übrigens keinen Client-Sicherheitskontext, so dass man für die Sicherheitseinstellungen wieder sinnvollerweise das run-as Tag des Deployment Descriptors bemüht.

In Bezug auf Transaktionen wird empfohlen, RequiresNew zu verwenden. Falls die Transaktion in ejbTimeout() zurückgerollt wird, wird die Methode nochmal aufgerufen. Das kann natürlich dazu führen, dass die Methode unendlich oft aufgerufen wird, ein Problem, das bereits aus JMS-Transaktionen bekannt ist: Es ist also sinnvoll, die Transaktion nur dann zurückzurollen, wenn berechtigte Hoffnung besteht, dass der entsprechende Fehler nur vorübergehend ist, also beispielsweise von einem kurzzeitig ausgefallenen Server herrührt. Natürlich ist eine weitere interessante Frage, was passiert, wenn die Transaktion zurückgerollt wird, in der der Timer erzeugt wurde. Die Antwort ist, dass der Timer dann wieder gelöscht wird. Ähnliches gilt, wenn der Timer in einer zurückgerollten Transaktion gecancelt wurde: In diesem Fall wird dann das Canceln wieder rückgängig gemacht.

Erweiterungen in EJB QL

Die EJB Query Language wurde in EJB 2.0 eingeführt, um die Möglichkeit zu schaffen, Anfragen auf Entity Beans mit Container-Managed Persistence unabhängig vom verwendeten Application Server und der verwendeten Datenbank zu definieren.

In EJB 2.1 sind in diesem Bereich im wesentlichen kleine Verbesserungen und Präzisierungen vorgenommen worden. Hier sollen allerdings nur die zwei wirklich neuen Features erläutert werden. Zum einen gibt es jetzt Aggregat Funktionen wie sie aus SQL bekannt sind: COUNT, AVG, MAX, MIN und SUM wurden eingeführt. Diese Funktionen pictureen eine Ergebnismenge auf einen einzelnen Wert ab.

  • COUNT gibt die Anzahl der Resultate an und kann auf einzelne Felder oder ganze Tabellen angewendet werden. Als Ergebnis wird ein long zurückgegeben.
  • SUM und AVG können auf Zahlenwerte angewendet werden. AVG gibt dabei den Durchschnitt als ein double zurück und SUM die Summe als einzigen Wert in einer Collection des Wrappers des entsprechenden Basisdatentyps zurück.
  • MAX und MIN können auf alle sortierbaren Datentypen (Zahlen, String, Zeichen, Dates) angewendet werden. Dabei wird wiederum eine Collection mit einem Element zurückgegeben.

Dass bei MAX, MIN und SUM diese eigenartigen Datentypen zurückgegeben werden, hängt mit folgendem zusammen: Ohne die Anwendung dieser Funktion würde auch eine Collection von Werten zurückgegeben werden. Die Anwendung dieser Funktionen führt dann zu dem Sonderfall, dass in der Collection nur ein einzelner Wert existiert. Über die Eleganz dieser Lösung kann man streiten.

Ebenfalls neu eingeführt wurde die ORDER BY-Klausel, die ebenfalls aus SQL bekannt sein sollte. Mit Hilfe dieses Ausdrucksteils können Ergebnisse einer EJB QL-Anfrage sortiert werden. Dabei sind im wesentlichen zwei Fälle zu unterscheiden: Wenn in der SELECT-Klausel eine komplette Entity Bean steht, also entweder OBJECT(x) verwendet wird oder eine Container-Managed Relation referenziert wird, kann im ORDER BY-Teil ein beliebiges Feld diese Bean verwendet werden. Der andere Fall ist, dass eines oder mehrere Felder einer Bean mit Container-Managed Persistence im SELECT ausgewählt wird. In diesem Fall darf nur eines dieser Felder im ORDER BY auftauchen. Es ist also nicht möglich, nur den Namen eines Kunden im SELECT auszuwählen und dann nach der Kundennummer zu sortieren, da dieses Feld nicht im SELECT-Teil vorkommt. Es gibt bei ORDER BY die Möglichkeit, aufsteigend (ASC) oder absteigend zu sortieren (DESC). Null Werte sind dabei entweder am Anfang oder Ende zu finden: Der Application Server-Hersteller hat hier die Auswahl.

Geschrieben von
Eberhard Wolff
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.