Westfälische Wilhelms-Universität Münster Ausarbeitung Message-Driven Beans und Java Message Service (JMS) im Rahmen des Seminars „Softwaretechnik“ Denis Westermeyer Themensteller: Prof. Dr. Herbert Kuchen Betreuer: Dipl.-Wirt.Inform. Christoph Lembeck Institut für Wirtschaftsinformatik Praktische Informatik in der Wirtschaft Inhaltsverzeichnis 1 Einleitung................................................................................................................... 3 2 Grundlagen ................................................................................................................ 4 3 2.1 Enterprise JavaBeans ........................................................................................ 4 2.2 Message-oriented Middleware.......................................................................... 5 Java Message Service ................................................................................................ 6 3.1 Eigenschaften.................................................................................................... 6 3.2 Übermittlungsarten ........................................................................................... 7 3.2.1 3.2.2 3.2.3 4 Publish-and-Subscribe .................................................................................. 7 Point-to-Point................................................................................................ 8 Request/Reply............................................................................................... 9 3.3 Nachrichtenarten............................................................................................... 9 3.4 Java Message Service API.............................................................................. 10 Message-Driven Beans ............................................................................................ 12 4.1 Abgrenzung von anderen Beantypen.............................................................. 12 4.2 Aufbau einer Message-Driven Bean............................................................... 13 4.2.1 4.2.2 4.2.3 4.2.4 Vorbereitungen ........................................................................................... 13 Implementierung der MDB......................................................................... 14 Bereitstellen des Deployment Descriptors.................................................. 15 Implementierung des Clientprogramms...................................................... 17 4.3 Lebenszyklus .................................................................................................. 18 4.4 Mögliche Problemquellen............................................................................... 19 4.5 Fortgeschrittene Konzepte .............................................................................. 20 5 Zusammenfassung ................................................................................................... 21 6 Literaturverzeichnis ................................................................................................. 22 2 Kapitel 1: Einleitung 1 Einleitung Enterprise JavaBeans haben sich im Laufe der letzten Jahre immer mehr als Lösung für unternehmensspezifische Software positioniert. Neue Systeme fußen häufig auf Altsystemen (Legacy-Systemen), mit denen sie in Kontakt bleiben. Außerdem wird durch die Netzbasierte Infrastruktur der Nachrichtenaustausch zwischen verschiedensten Anwendungen nötig. Das ist sowohl synchron mittels Remote Procedure Calls als auch asynchron möglich. Der asynchrone Nachrichtenaustausch wird durch Message-oriented Middleware realisiert, welche die eigentliche Nachrichtenübermittlung übernimmt. Auf dieser Basis setzt der Java Message Service auf, der wiederum die Grundlage für den Nachrichtenaustausch mit Enterprise JavaBeans bildet. Ein recht neuer Beantyp, die Message-Driven Bean, kann nicht nur Nachrichten vom Java Message Service entgegennehmen, sondern auch selbst versenden. Auf den folgenden Seiten sollen zunächst ein paar Grundlagen vorgestellt werden, bevor anschließend der Umgang mit dem Java Message Service sowie Message-Driven Beans gezeigt wird. 3 Kapitel 2: Grundlagen 2 Grundlagen 2.1 Enterprise JavaBeans Enterprise JavaBeans (EJB) sind eine Javabasierte Middleware für objektorientierte Anwendungen und gehören damit in die Schicht der Geschäftslogik einer Drei- oder Vierschichtenarchitektur. Die Komponenten (Beans) werden vom so genannten Container bereitgestellt. Dieser Container (z.B. JBoss, IBM Websphere…) bietet einige Dienste an. Darunter fallen unter anderem die Verwaltung und Suche von Beans mit dem Java Naming and Directory Interface (JNDI), die Ressourcenverwaltung, Transaktionen oder der Zugriff auf entfernte Objekte. EJBs bestehen aus mehreren Beantypen, die im Folgenden kurz vorgestellt werden sollen [SE204]. Entity Beans Entity Beans bewahren den Zustand über eine Sitzung hinaus, das heißt, Daten werden in einer Datenbank hinterlegt, die später wieder eindeutig einem Client zugeordnet werden können. Darüber hinaus kapseln sie die persistenten Daten, indem sie den indirekten Zugriff auf eine Datenbank und die darin enthaltenen Daten zur Verfügung stellen. Nur Entity Beans können direkt auf die Datenbank zugreifen, was Vorteile beispielsweise hinsichtlich der Sicherheit und Integrität der Daten bietet. Session Beans Session Beans bilden den eigentlichen Geschäftsvorfall ab. Sie sind nicht persistent und bieten den Zugriff auf sich per RMI-IIOP (Java Remote Method Invocation over the Internet Inter-ORB-Protocol) an. Es gibt zwei Ausprägungen bei Session Beans. Die eine ist zustandslos und kann, nachdem sie einen Geschäftsvorfall für einen Client abgearbeitet hat, sofort von einem anderen Client genutzt werden. Die andere Art ist zustandsbehaftet und behält ihren Zustand während der gesamten Sitzung bei, nicht aber darüber hinaus. Damit könnte beispielsweise der Warenkorb eines Onlineshops abgebildet werden. 4 Kapitel 2: Grundlagen Message-Driven Beans Die Message-Driven Beans (MDBs) sind ein Novum seit EJB Version 2.0. Diese Beans können via Java Message Service (JMS) Nachrichten empfangen und daraufhin Aktionen unternehmen. Sie sollen im Verlauf der Arbeit noch genauer betrachtet werden. Jede Bean mit Ausnahme der MDB besitzt ein Remote Interface, das die Geschäftsmethoden bereitstellt sowie ein Home Interface, welches Verwaltungsoperationen anbietet, beispielsweise das Erzeugen oder Löschen einer Bean. Die Beanklasse implementiert die Verwaltungs- und Geschäftsmethoden. Zudem muss jede Bean im so genannten Deployment Descriptor beschrieben werden. Der Deployment Descriptor ist ein XML-Dokument (eXtensible Markup Languange), das die Konfiguration einer Bean unter anderem bezüglich Persistenz, Transaktionen und Primärschlüssel enthält. Der gesamte Programmcode wird in einer .jar-Datei abgelegt und später beim Deployment vom Container wie im Deployment Descriptor angegeben konfiguriert und bereitgestellt. 2.2 Message-oriented Middleware Message-oriented Middleware (MOM) bietet den Nachrichtenaustausch zwischen Programmen an [JMS01 S. 7f], ähnlich den Remote Procedure Calls (RPC). Allerdings wird der Nachrichtenversand asynchron vorgenommen, was den Vorteil bietet, dass der Sender der Nachricht nicht erst auf die Aktion des Empfängers warten muss, demzufolge geblockt wird, wie das bei den synchronen RPC-Aufrufen der Fall ist. Der Sender kann sofort andere Aufgaben wahrnehmen, nachdem die Nachricht verschickt wurde. Da es keine von Seiten Suns implementierte Nachrichtenübermittlung abgesehen von synchroner Kommunikation durch RMI gibt, bietet dies jeder Hersteller über eine eigene API (Application Programming Interface) an. Damit sind die auf der einen Plattform entwickelten Javaprogramme auf der eines anderen Herstellers nicht unbedingt lauffähig, da dieser meist eine eigene nicht kompatible API bereitstellt [MEJB02 S. 203]. 5 Kapitel 3: Java Message Service 3 Java Message Service 3.1 Eigenschaften Aus dem Grund, dass Programme, welche für eine MOM des einen Herstellers programmiert wurden, nicht unbedingt auch auf der MOM eines anderen laufen, hat Sun mit den Herstellern der Entwicklungsumgebungen den Java Message Service entwickelt. Dieser setzt auf den Herstellerspezifischen Middleware-Systemen auf, aber bietet dem Programmierer oder Entwickler eine einheitliche Schnittstelle. Vergleichbar mit JMS ist die Java Database Connectivity (JDBC), welche ebenfalls eine einheitliche Schnittstelle bietet, in diesem Fall auf relationale Datenbanken, oder aber RPC [JMS01 S. 9f]. Beide abstrahieren die jeweils darunter liegenden herstellerspezifischen APIs der zugrunde liegenden Systeme und ersparen dem Programmierer die Mühe, sich in viele verschiedene Systeme einzuarbeiten. Einer der großen Vorteile beim Versenden von Nachrichten mittels JMS ist der, dass diese asynchron verarbeitet werden. Das bedeutet, dass der Versender nicht auf eine Antwort warten muss. Bei Java RMI liegt der Fall anders, dort muss eine Antwort erfolgen, was den Versender von der Verfügbarkeit des EJB Servers abhängig macht. Fällt dieser in der Zwischenzeit aus, so wartet der Versender mitunter sehr lange auf eine Antwort. Natürlich kann man auch Timeouts einsetzen, die den Versender davor bewahren lange Zeit auf eine nie erfolgende Antwort zu hoffen. Trotzdem würde der Sender einige Zeit abwarten, bevor er fortfahren könnte. Bei JMS ist der Ablauf so, dass der Versender sich direkt nach dem Versand an den JMS-Server anderen Aufgaben zuwenden kann. Der JMS-Server ist dann dafür verantwortlich, die Nachricht dem richtigen Empfänger zuzuschicken. Ein weiterer Vorteil liegt darin, dass Sender und Empfänger nicht zwangsläufig zur gleichen Zeit laufen müssen. Ist der Empfänger zwischenzeitig nicht erreichbar, so werden auflaufende Nachrichten vom JMS-Server gespeichert und sobald der Empfänger wieder bereit ist, verschickt [EJB02 S. 372]. Das ist allerdings nur der Fall, wenn die MOM die Auslieferung garantieren (guaranteed delivery) kann. Auch ein Ausfall des JMS-Servers kann aufgefangen werden, wenn der Versender seine abgehenden Nachrichten ebenfalls so lange aufbewahrt, bis der Server wieder verfügbar ist (store-and-forward). Eine andere Spielart ist die Bestätigung der Konsumierung einer 6 Kapitel 3: Java Message Service Nachricht (certified message delivery). Dabei wird eine Bestätigung an den Versender geschickt [MEJB02 S. 204] Da der JMS-Server zwischen die miteinander kommunizierenden Systeme geschaltet wird, weiß der Versender auf der einen Seite nicht, wer seine Nachricht empfangen wird und auf der Seite kann auch der Empfänger nicht nachvollziehen, wer diese Nachricht verschickt hat. Dieses mögliche sicherheitstechnische Problem kann man zwar durch eine Authentifizierung gegenüber dem JMS-Server abmildern, aber trotz dieser wird der Sicherheitskontext der Nachricht nicht übermittelt. Das ist aber so gewollt, da Sender und Empfänger häufig in verschiedenen Sicherheitszonen arbeiten. 3.2 Übermittlungsarten 3.2.1 Publish-and-Subscribe Das Prinzip des Publish-and-Subscribe (Pub/Sub) ist ähnlich dem des Radios. Die Radiostationen senden auf verschiedenen Kanälen. Jeder Nutzer kann sich eine Radiofrequenz aussuchen und die dortige Sendung empfangen. Wechselt ein Nutzer die Frequenz oder schaltet sein Radio ab, so verpasst er natürlich alles, was dort in der Zwischenzeit auf dem Kanal passiert. Dass mehrere Produzenten auf einem Kanal senden, kann man sich wie Live-Reportagen vorstellen, die an verschiedenen Orten stattfinden können und nacheinander gesendet werden. Pub/Sub arbeitet nach dem Push-Prinzip und bildet eine n:n-Verbindung zwischen den Sendern (Produzenten) und Empfängern (Konsumenten) ab. Die Produzenten und Konsumenten kennen sich nicht, denn zwischen beiden sitzt der Mittelsmann, also das MOM-System, auf dem der JMS-Server aufbaut. Beide greifen aber auf denselben virtuellen Kanal zu, der Topic (oder Thema) genannt wird. Jeder potentielle Konsument kann sich zu einem Thema einschreiben und erhält dann, sobald eine Nachricht zu diesem Thema versandt wird, eine Kopie davon. Normalerweise sind Produzenten und Konsumenten voneinander unabhängig, da die Nachrichten über das dazwischen liegende MOM-System verschickt werden. Es gibt aber Fälle, wo die eben beschriebenen certified message delivery oder guaranteed delivery erwünscht sind. [EJB02 S. 373 und MEJB02 S. 204f] 7 Kapitel 3: Java Message Service Produzent1 Konsument1 Nachricht1 Nachricht1 Nachricht2 Topic Nachricht2 Nachricht1 Nachricht2 Produzent2 Konsument2 Abbildung 1: Pub/Sub mit 2 Produzenten und 2 Konsumenten Am Beispiel [Abbildung 1] kann man erkennen, dass der Produzent1 seine Nachricht etwas früher als Produzent2 schickt, da die Sprechblase etwas weiter am Kopf des Pfeils zum MOM sitzt. Diese Nachricht wird daher auch vom MOM als erstes an beide Konsumenten dieses Topics gesendet. Erst im Anschluss wird die 2. Nachricht verschickt. 3.2.2 Point-to-Point Beim Point-to-Point (PTP) gibt es im Vergleich zum Pub/Sub nicht mehrere Konsumenten pro Nachricht, sondern die eingehenden Nachrichten werden in einer Queue gesammelt und nur jeweils an einen Konsumenten versandt. Ein eingeschriebener Konsument muss das System fragen (Pull), ob eine neue Nachricht vorhanden ist. Wenn dem so ist, dann versendet es meist nach dem First-in-first-outPrinzip (FIFO) die an erster Stelle in der Queue befindliche Nachricht an den Konsument und löscht diese aus der Queue. Es kann aber auch nach anderen Prinzipien vorgegangen werden. Denkbar wäre z.B., dass Nachrichten eine gewisse Priorität besitzen, die dann die Reihenfolge abbildet. Nach dem Versand einer Nachricht kann kein anderer Konsument diese mehr abholen. Dadurch ist es möglich mit mehreren parallelen Servern die Verarbeitung im Gegensatz zum Pub/Sub deutlich zu beschleunigen [MEJB02 S. 226], da bei letzterem jede Nachricht auf jedem Server verarbeitet werden muss. Als Beispiel könnte man sich ein Musikportal vorstellen. Ein Kunde, der eine bestimmte Musikdatei herunterladen möchte, schickt eine Nachricht an den JMS8 Kapitel 3: Java Message Service Server. Der schickt diese an einen gerade freien Server (Load-Balancing), der im Anschluss die Datei dem Kunden zusendet, was wiederum über den JMS-Server mittels PTP erfolgen könnte. Es besteht die Möglichkeit, auch einen Push- anstelle des Pull-Services anzubieten [EJB02 S. 373], der jedoch viele Vorteile des PTP-Prinzips vergibt. Produzent1 Konsument1 Nachricht1 Nachricht1 Queue Nachricht2 Nachricht2 Produzent2 Konsument2 Abbildung 2: PTP mit 2 Produzenten und 2 Konsumenten Im Beispiel [Abbildung 2] gibt es wieder zwei Produzenten, die jeweils eine Nachricht abschicken und wiederum wurde Nachricht1 zuerst versandt. Die Queue liefert hier nach dem FIFO-Prinzip aus, sodass Konsument1, der als erstes nach neuen Nachrichten fragt, Nachricht1 zugesandt bekommt. Danach wird Nachricht1 aus der Queue gelöscht und sobald Konsument2 anfragt, wird ihm die Nachricht2 geschickt. 3.2.3 Request/Reply Request/Reply wird seltener genutzt, da es im Grunde dasselbe Verhalten wie RMI anbietet. Der Produzent muss somit mit dem weiteren Programmablauf so lange warten, bis er eine Antwort vom Konsumenten erhält. Häufig wird dieses Prinzip mit Hilfe der beiden oben genannten Methoden Pub/Sub bzw. PTP implementiert. [MEJB02 S. 206] 3.3 Nachrichtenarten Eine Nachricht ist ein Objekt das aus zwei Teilen besteht; dem Header und dem Body. Im Header werden Informationen zum Versand und Metadaten angegeben, der Body enthält die eigentliche Nachricht, die mehrere Formen wie beispielsweise Text oder 9 Kapitel 3: Java Message Service Byteströme haben kann. In der JMS API werden einige Arten von Nachrichteninterfaces definiert, die einmal kurz vorgestellt werden sollen [JMS01 S. 42ff]. TextMessage ist ein einfacher java.lang.String. Die MapMessage enthält Schlüssel/Wert-Paare (z.B. „Name“, „Anton“). Dabei enthalten die Schlüssel Strings und die Werte primitive Datentypen. Die Werte können dann über den Schlüssel ausgelesen werden. Eine BytesMessage enthält ein Array mit primitiven Bytes und die Interpretation dieser Daten muss der Empfänger übernehmen. Das Interface kann beispielsweise benutzt werden, um Dateien im Rohformat zu übermitteln (z.B. MP3Dateien). Die StreamMessage ist ähnlich der ByteMessage. Sie enthält aber einen Strom primitiver Datentypen. Die ObjectMessage enthält ein serialisierbares Java Objekt. Das Message-Interface enthält keine Nutzdaten und dient meist nur dazu, Ereignisse (Events) auszulösen. Hier werden nur die Headerdaten und keine Nutzdaten gesendet. 3.4 Java Message Service API Um als Client den JMS-Dienst nutzen zu können, sind einige Schritte notwendig, die im Folgenden vorgestellt werden sollen [MEJB02 S. 206f]. 1. JMS-Treiber finden und einbinden Der produktspezifische JMS-Treiber wird über JNDI lokalisiert und eingebunden. Der Treiber wird ConnectionFactory genannt. 2. JMS-Verbindung aufbauen Mit Hilfe der ConnectionFactory wird eine Verbindung (Connection) zum JMSProvider aufgebaut. Die Connection managt die zugrunde liegende Netzwerkverbindung. 3. JMS-Sitzung eröffnen Eine Sitzung wird mit dem Hilfsobjekt Session zum empfangen bzw. versenden von Nachrichten benötigt und mit Hilfe der Connection aufgebaut. Außerdem können damit Nachrichten in Transaktionen gekapselt werden. 4. Suche des Topics bzw. der Queue Eine JMS Destination bezeichnet den Kanal, über den Nachrichten empfangen bzw. gesendet werden können. Dieser wird mittels JNDI ermittelt. 10 Kapitel 3: Java Message Service 5. Erstellen eines Produzenten bzw. Konsumenten Sollen Nachrichten gesendet werden, so wird ein Produzent (Producer) erstellt. Im umgekehrten Fall wird ein Konsument (Consumer) aufgesetzt. Die Erstellung wird mittels der Session und Destination vorgenommen. 6. Senden bzw. empfangen der Nachricht Um eine Nachricht mit dem Produzenten zu verschicken, muss die Nachricht erst aufgebaut werden, bevor der Versand stattfinden kann. Im Empfangsfalle muss nach Erhalt der Nachricht durch den Konsumenten geschaut werden, was in der Nachricht steht. Die oben kursiv geschriebenen Interfaces werden im Anwendungsfall des Topics oder der Queue durch die der Art der Nachrichtenübermittlung entsprechenden Interfaces [Tabelle 1] ersetzt. Eltern-Interface Pub/Sub PTP ConnectionFactory QueueConnectionFactory TopicConnectionFactory Connection QueueConnection TopicConnection Destination Queue Topic Session QueueSession TopicSession MessageProducer QueueSender TopicPublisher MessageConsumer QueueReceiver, QueueBrowser TopicSubscriber Tabelle 1: Interfaceausprägungen für Pub/Sub und PTP [MEJB02 S. 208] 11 Kapitel 4: Message-Driven Beans 4 Message-Driven Beans 4.1 Abgrenzung von anderen Beantypen Message-Driven Beans verarbeiten JMS-Nachrichten [MEJB02 S. 212] und können als einziger Beantyp selbst JMS-Nachrichten erstellen. MDBs haben im Unterschied zu den bisherigen Beantypen kein Home-, LocalHome-, Remote- oder Local-Interface. Es ist darum nicht möglich, eine MDB über RMI zu erreichen und eine bestimmte Handlung ausführen zu lassen. Die einzige Möglichkeit ihnen Aufgaben zu geben besteht darin, ihnen eine JMS-Nachricht zukommen zu lassen. Dabei kann eine MDB entweder Nachrichten eines Topics oder einer Queue empfangen. Rückgabewert MDBs haben keinen Rückgabewert. Der Grund liegt darin, dass MDBs und der Produzent der Nachricht voneinander unabhängig sein sollen, der Produzent demnach schon mit seinem Programm fort fährt und nicht auf eine Rückgabe wartet. Trotzdem ist es möglich, diesem eine Bestätigung (certified message delivery) zukommen zu lassen. Exceptions Einer MDB ist es nicht möglich, Exceptions auszulösen, die an den Produzenten zurückgeschickt werden, da dieser, wie oben schon beschrieben, nicht auf Ergebnisse der MDB wartet. Eine MDB kann nur System-Exceptions nutzen, die vom Container entgegengenommen und weiterverarbeitet werden. Dabei sollte darauf geachtet werden, dass im Falle einer Exception die ejbRemove()-Methode nicht mehr aufgerufen wird. Folglich sind eventuelle Aufräumarbeiten vor dem Werfen der Exception vorzunehmen. Geschäftsmethode MDBs haben nur eine Geschäftsmethode, die onMessage() heißt. Da sie nicht weiß, was für einen Nachrichtentyp sie übermittelt bekommt, muss sie zuerst überprüfen, ob sie den Nachrichtentyp übermittelt bekommen hat, den sie erwartet. Dies wird meist mit dem instanceOf-Operator realisiert. Stimmt der empfangene Nachrichtentyp mit dem erwarteten überein, so wird die eingegangene Nachricht in den entsprechenden Typ umgewandelt (gecastet). Wenn der Typ falsch ist, terminiert die Methode häufig einfach nur. Ein Problem ist, dass nicht schon zur Compile-Zeit feststeht, welchen 12 Kapitel 4: Message-Driven Beans Nachrichtentyp sie übermittelt bekommen wird. Das ist bei den anderen Beans möglich, da dort nur durch die Parameter einer Methode definierten Datentypen genutzt werden dürfen. Zustandslosigkeit MDBs sind zustandslos. Ansonsten könnten Nachrichten nicht mehr an einen Cluster mehrerer Container mit baugleichen MDB-Instanzen geschickt werden, da eine Nachricht möglicherweise nur an eine bestimmte Instanz mit einem gegebenen Zustand geschickt werden darf. Da es aber keinen Zustand für sie gibt, ist es dem Container möglich, alle auf Vorrat gehaltenen MDB-Instanzen auch gleichwertig zu behandeln. Insofern ist es nicht relevant, welche Instanz welche Nachricht bekommt. Dauerhafter vs. zeitweiser Konsum MDBs können sich dauerhaft oder nur zeitweise als Konsumenten zu einem Topic oder einer Queue anmelden. Der Container übernimmt stellvertretend die Einschreibung, damit er die eingehenden Nachrichten den im Pool befindlichen MDBs zuteilen kann. Er nimmt dabei jede Nachricht, auch die eines Topics nur ein Mal stellvertretend für seine MDB-Instanzen ab, sodass dieselbe Nachricht nicht von mehreren Instanzen eines Containers konsumiert werden können. Sollen Nachrichten mehrfach verarbeitet werden, so ist ein zweiter Container mit eigenen Instanzen derselben MDB notwendig. Der Vorteil der dauerhaften Einschreibung liegt darin, dass die Nachrichten auch bei einer Nichtverfügbarkeit des Servers, auf dem die MDB liegt, vom JMS-Server gespeichert werden und sobald er wieder verfügbar ist, abgeholt werden können (PTP) oder aber zugesandt werden (Pub/Sub). Handelt es sich nur um eine zeitweise Einschreibung, so gehen alle in der Zwischenzeit aufgelaufenen Nachrichten verloren. [MEJB02 S. 213, EJB02 S. 377] 4.2 Aufbau einer Message-Driven Bean 4.2.1 Vorbereitungen Um den Aufbau und die Interaktion der einzelnen Komponenten besser darstellen zu können soll hier das Beispiel eines Internetradios angeführt werden. Die Nutzer können in einem Webformular einen beliebigen Titel und/oder Interpreten angeben, der dann, 13 Kapitel 4: Message-Driven Beans wenn er in der Datenbank gefunden wurde in eine Playlist aufgenommen und abgespielt wird, sobald er an der Reihe ist. Für die Implementierung einer MDB werden zwei Interfaces benötigt. Das MessageListener-Interface stellt die onMessage()-Methode zur Verfügung. Das Interface MessageDrivenBean bietet die beiden Methoden ejbRemove() und setMessageDrivenContext() an. Es werden hier separate Interfaces benutzt, da man sich vorbehalten möchte, in Zukunft auch andere Nachrichten als die eines JMS-Servers mit MDBs zu verarbeiten. Diese Nachrichtenarten, wie z.B. SMTP (Simple Mail Transport Protocol), werden nicht die JMS-spezifische Methode onMessage() benutzen, sondern eigene Methoden mitbringen. Deshalb wurde das Interface MessagDrivenBean von der Art der Nachricht durch den MessageListener entkoppelt [EJB02 S. 383]. 4.2.2 Implementierung der MDB Nun folgt der Code für eine MDB, der das oben geschilderte Beispiel eines Internetradios illustrieren soll. package beispiele; import javax.ejb.*; import javax.jms.*; // Die Internet-Radio-Bean public class RadioBean implements MessageDrivenBean, MessageListener { protected MessageDrivenContext kontext; // Übergibt der Bean-Instanz den aktuellen Kontext. public void setMessageDrivenContext(MessageDrivenContext ctx) { kontext = ctx; } // MDB Initialisieren. public void ejbCreate() { // Ausgabe zum Mitloggen System.err.println("Es wurde ejbCreate() aufgerufen"); } // Die einzige Geschäftsmethode der MDB public void onMessage(Message msg) { // Es dürfen hier nur Textnachrichten eintreffen. if (msg instanceOf TextMessage) { TextMessage nachricht = (TextMessage) msg; try { String text = nachricht.getText(); // Nun muss hier der Code für das Aufschlüsseln der // Textnachricht in die Teile Interpret und Titel // folgen. System.err.println("Aufschlüsseln erfolgreich."); b.w. 14 Kapitel 4: Message-Driven Beans // Im Anschluss daran wird nach // den eingegebenen Schlüsselwörtern gesucht. System.err.println("Suche nach: " + text); // Wird ein passendes Stück gefunden, System.err.println(text + " gefunden."); // so wird es in die Playlist aufgenommen System.err.println(text + "aufgenommen"); } catch(JMSException e) { e.printStackTrace(); } } } // Löschen der Bean public void ejbRemove() { System.err.println("Es wurde ejbRemove() aufgerufen"); } } Programmcode 1: Implementierung einer MDB Die Geschäftsmethode [Programmcode 1] versucht, eine eingegangene Textnachricht, die das Format „Titel=’<Titel>’, Interpret=’<Interpret>’“ haben soll, weiter zu verarbeiten. Zunächst soll nach dem Titel und Interpreten gesucht werden. Ist die Suche erfolgreich, so wird der Titel in die Playlist eingereiht. Die Suche könnte durch eine zustandslose Session Bean realisiert werden, die den Primary Key des Musikstücks oder Null bei einer erfolglosen Suche zurückgibt. Anschließend könnte die MDB den Primary Key des Musikstücks als JMS-Nachricht selber an eine Queue senden, die eine Playlist mit gültigen Titeln darstellt. Damit wäre der Auftrag der MDB erledigt. Sollten nicht erfolgreiche Suchen oder ungültige Textnachrichten eintreffen, so soll einfach terminiert werden, ohne Exceptions zu werfen, die sowieso nur System-Exceptions sein dürften. Die Nachrichten in der Queue, welche die Playlist darstellt, wird von einer anderen MDB in Empfang genommen, die nur eine einzige Instanz haben darf, da die Titel ja nicht parallel abgespielt werden sollen. Sie ruft dann die zum Primary Key gehörende Entity Bean über RMI auf, die beispielsweise die Methode abspielen() bieten soll. 4.2.3 Bereitstellen des Deployment Descriptors Es sind nur wenige Tags für den Deployment Descriptor einer MDB im Gegensatz zu anderen Beantypen anwendbar. Dafür gibt es aber auch einige Tags, die speziell für MDBs vorgesehen sind [EJB02 S. 386ff, MEJB02 S. 219ff]. 15 Kapitel 4: Message-Driven Beans <message-selector> Der Message-Selector definiert, welche Eigenschaften Nachrichten haben müssen, die von dieser Bean verarbeitet werden sollen. Der Container kann dann schon im Vorfeld unerwünschte Nachrichten aussortieren und infolgedessen den Overhead verringern und die Performanz steigern. Das folgende Beispiel zeigt, dass nur Nachrichten, die dem Typ „TextMessage“ entsprechen, angenommen werden: „<message-selector> JMSType=“TextMessage“ </message-selector>“. Es können auch kompliziertere SQLähnliche Statements verwendet werden. <acknowledge-mode> Der <acknowledge-mode> muss angegeben werden, wenn die Bean selbst Transaktionen steuert (bean-managed transactions). Denn im Gegensatz zu containermanaged transactions wird beim Rollback (Wiederherstellung des Zustands, der vor der gescheiterten Transaktion galt) einer Transaktion nicht die gerade ausgelieferte Nachricht zurück in die Queue gelegt, da die Konsumierung der Nachricht außerhalb der Transaktion liegt. Deshalb muss im Fall der bean-managed transactions dem Container mitgeteilt werden, dass er Nachrichten bestätigen muss. Beim Autoacknowledge wird der Erhalt einer Nachricht bestätigt, wenn die onMessage()Methode ordnungsgemäß ausgeführt wurde. Wird Dups-ok-acknowledge gewählt, so kann der Container selbst entscheiden, wann er den Erhalt einer Nachricht bestätigt. Das kann dazu führen, dass der JMS-Server annimmt, die Nachricht sei nicht angekommen und schickt diese nochmals. Deswegen empfiehlt sich diese Einstellung nur, wenn man mit doppelten Nachrichten umgehen kann. <message-driven-destination> Der <destination-type>-Tag gibt an, ob es sich um ein Topic (javax.jms.Topic) oder eine Queue (javax.jms.Queue) handeln soll, bei der sich die Bean einschreibt. Sofern es sich um ein Topic handelt, kann noch ein zusätzliches <subscription-durability>-Tag benutzt werden, das entweder durable oder nondurable sein darf, und angibt, ob die Bean dauerhaft oder nur zeitweise eingeschrieben sein soll. 16 Kapitel 4: Message-Driven Beans Im Folgenden ein beispielhafter Deployment Descriptor <ejb-jar> <enterprise-beans> <!-- Für jede MDB, die in der ejb-jar-Datei enthalten ist, muss der <message-driven>-Eintrag definiert werden. --> <message-driven> <ejb-name>Internet-Radio-Bean</ejb-name> <!—- Der komplette Pfad zur Bean-Klasse --> <ejb-class>Beispiele.RadioBean</ejb-class> <!—- Zeigt den Transaktionstyp an --> <transaction-type>Container</transaction-type> <message-driven-destination> <!—- Gibt an, ob Topic oder Queue abonniert wird --> <destination-type>javax.jms.Queue</destination-type> </message-driven-destination> </message-driven> </enterprise-beans> </ejb-jar> Programmcode 2: Deployment Descriptor einer Message-Driven Bean Es fällt auf, dass nur angegeben wird, welche Art der Nachrichtenübermittlung gewählt wird. Nicht erwähnt ist, welche genaue Destination verwendet werden soll. Dies wird bewusst so gemacht, damit Beans zwischen Applikationsservern portierbar bleiben, da die Namen der Destinationen bei jedem Server unterschiedlich lauten [MEJB02 S. 223]. 4.2.4 Implementierung des Clientprogramms Der Client kann wie unten implementiert werden. Die einzelnen Schritte werden analog zum weiter oben [Kap. 3.4] beschriebenen Vorgehen durchgeführt. Da der Client einen Musikwunsch abschicken kann, dieser aber nicht von mehr als einer Bean bearbeitet werden soll, wird PTP genutzt, also eine Queue benötigt. import javax.naming.*; import javax.jms.*; import java.util.*; public class RadioClient{ public static void main (String[] args) throws Exception { // 1. Initialisierung des Kontextes, also des JNDI Context kontext = new InitialContext(System.getProperties()); // 2. JMS-Treiber finden und einbinden QueueConnectionFactory fabrik =(QueueConnectionFactory) kontext.lookup("javax.jms.QueueConnectionFactory"); // 3. JMS-Verbindung aufbauen QueueConnection verbindung = fabrik.createQueueConnection(); b.w. 17 Kapitel 4: Message-Driven Beans // 4. JMS-Sitzung eröffnen QueueSession sitzung = verbindung.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); // 5. Suche der Destination (hier: Queue) via JNDI Queue inetradio = (Queue) kontext.lookup("Internet-Radio"); // Erstellen des Nachrichtenproduzenten QueuePublisher versender = sitzung.createPublisher(inetradio); // 6. Versand einer Nachricht, hier eines Musikwunsches. TextMessage nachricht = session.createTextMessage(); nachricht.setText("Titel=’SuperSound’, Interpret=’Rambazamba’"); versender.publish(nachricht); } } Programmcode 3: Der Internetradio Client 4.3 Lebenszyklus MDBs haben einen sehr einfachen Lebenszyklus. Eine MDB befindet sich in einem von zwei Zuständen [Abbildung 3]. Der erste Zustand „Does Not Exist“ gilt, wenn die Bean noch nicht instanziiert wurde. Um in den Zustand „Pooled“ zu gelangen, muss der Container der Bean zuerst eine neue Instanz der Bean erzeugen, ihr danach den aktuellen Kontext übergeben und anschließend mit ejbCreate() der Bean die Möglichkeit geben, selbst weitere Aktionen vorzunehmen. newInstance() setMessageDrivenContext() ejbRemove() ejbCreate() onMessage() Abbildung 3: Lebenszyklus einer Message-Driven Bean [MEJB02 S. 217 bzw. EJB02 S. 395] Beim Start des Applikationsservers werden meist schon einige Instanzen einer MDB generiert und im Pool vorgehalten, damit der aufwändige Prozess der Instanziierung nicht erst dann beginnen muss, wenn die erste Nachricht eintrifft. Kommen so viele Nachrichten auf einmal, dass der Vorrat an Instanzen nicht mehr ausreicht, so wird der 18 Kapitel 4: Message-Driven Beans Pool um weitere aufgestockt. Sobald zu viele Instanzen für zu wenige Nachrichten bestehen, werden überflüssige Instanzen dem Pool entnommen und zerstört, damit nicht unnötig viele Ressourcen belegt werden [EJB S. 395]. Diesen Auf- und Abbau kontrolliert der Container. Bei den Anbietern, die keinen Pool mit MDB-Instanzen vorhalten, werden diese meist für jede Nachricht generiert und wieder zerstört [EJB02 S. 395]. Die gepoolten Instanzen sind immer bereit, Nachrichten zu verarbeiten. Wird nun einer Instanz eine Nachricht übergeben, so kann sie während der Verarbeitung keine weiteren Nachrichten übernehmen. Sobald aber die onMessage()-Methode terminiert, der Verarbeitungsprozess demnach abgeschlossen ist, steht sie sofort wieder bereit, um neue Nachrichten anzunehmen. Bei jeder neuen Nachricht wird der Bean der aktuelle Kontext vom Container übergeben. Eine Instanz gelangt erst in den DoesNotExist-Status, wenn der Server diese nicht mehr benötigt. Dabei wird die ejbRemove()-Methode aufgerufen, die einige Aufräumarbeiten übernehmen kann. Allerdings ist die Lebensdauer einer MDB häufig sehr lang, sodass sie erst beim Herunterfahren des Servers oder wenn das Deployment zurückgezogen wird in den DoesNotExist-Status versetzt wird. 4.4 Mögliche Problemquellen Im Folgenden werden einige Probleme angesprochen, die bei der Verwendung von MDBs auftreten können [MEJB02 S. 225ff]. Es kann zum wiederholten Versand von bereits empfangenen Nachrichten kommen, wenn man den Container Transaktionen kontrollieren lässt (container-managed transactions). Der erneute Versand der Nachrichten kann auftreten, wenn die Transaktion, in welcher die Nachricht unter anderem an die MDB ausgeliefert wird, wieder und wieder fehlschlägt und jedes Mal ein Rollback durchgeführt wird. Ein Beispiel wäre ein Musikshop im Internet, wenn ein Nutzer sich gerade neu angemeldet hat, aber die Nachricht, diesen Nutzer anzulegen, noch nicht verarbeitet wurde, dieser aber schon die ersten Musikstücke herunterladen möchte. Seinen Bestellungen kann deshalb kein existierender Nutzer zugeordnet werden. Dabei wird die Nachricht wieder in die Queue des JMS-Servers eingereiht und sofort wieder abgeschickt. Das kann zu erheblichen Performanzverlusten bis hin zum Stillstand des 19 Kapitel 4: Message-Driven Beans Systems führen. Abhilfe kann z.B. die bean-managed transaction sein oder das Einfügen nicht bearbeiteter Nachrichten in eine spezielle Queue, die dann später abgearbeitet wird oder eine Nachricht wird, nachdem eine sie schon zum x. Mal nicht verarbeitet werden kann, beispielsweise gelöscht, um das System nicht unnötig zu belasten. Implizit wurde hier noch ein zweites Problem aufgezeigt, das in der nicht garantierten sequentiellen Abarbeitung von Nachrichten liegt. Der Kunde konnte im obigen Beispiel schon Musik bestellen, obwohl der Anmeldevorgang intern noch gar nicht abgeschlossen war. Der Container versucht zwar, darauf zu achten, aber spätestens bei einem Cluster mit mehreren konkurrierenden Containern ist es nicht mehr einfach zu gewährleisten, dass alle Nachrichten in richtiger Reihenfolge bearbeitet werden. 4.5 Fortgeschrittene Konzepte Es sollen nun ein paar Konzepte vorgestellt werden, die über die eigentliche Funktionalität von MDBs hinausgehen. Antworten an den Versender von Nachrichten sind laut der Spezifikation der EJBs nicht vorgesehen. Um dies aber trotzdem zu ermöglichen, natürlich wiederum asynchron, bietet es sich an, mit zwei Queues oder Topics zu arbeiten. Die eine Destination wird temporär geschaltet, um den Versand der Bestätigungen zu organisieren. Die andere wird wie gewohnt gehandhabt. Der Client erstellt eine temporäre Destination, die für die Dauer der Verbindung (Connection) aufrechterhalten wird. Dann verschickt der Client die Anfrage-Nachricht mit der Information der temporären Destination an den regulären Kanal, von wo aus sie an alle Konsumenten versand wird. Diese schicken dann an die temporäre Destination ihre Antwort. Diese Methodik ähnelt der certified message delivery, die schon im Kapitel 3.1 angesprochen wurde. Ein anderes Konzept wäre Load-Balancing mittels PTP zu realisieren, das beispielhaft schon im Kapitel 3.2.2 angesprochen wurde und eine optimale Auslastung der Netzinfrastruktur ermöglicht, da kein Applikationsserver außer Acht gelassen bzw. überlastet wird, denn diese melden sich selbstständig, sobald sie neue Aufgaben erfüllen können. 20 Kapitel 5: Zusammenfassung 5 Zusammenfassung Auf den vorhergehenden Seiten wurde zunächst der Java Message Service vorgestellt, der es ermöglicht unabhängig von der Message-oriented Middleware asynchron Nachrichten zu versenden und damit den Sender unabhängig von der Verfügbarkeit und Geschwindigkeit des Empfängers zu machen. Der Sender kann damit sofort mit seiner Arbeit fortfahren, ohne auf eine Antwort oder das Ende der Bearbeitung einer gestellten Aufgabe abzuwarten, wie es beispielsweise bei dem synchronen RMI der Fall wäre. Der JMS-Server bietet dazu verschiedenste Nachrichtentypen an, die mit unterschiedlichen Konzepten der Nachrichtenverteilung versand werden können. Die Message-Driven Beans wurden im Anschluss behandelt und können aufgrund einer eingehenden Nachricht vom JMS-Server bestimmte Aktionen durchführen. Dabei bedienen sie sich ihrer einzigen Geschäftsmethode, die Nachricht zu verarbeiten und möglicherweise selber neue Nachrichten an den JMS-Server zu versenden, was keine andere Enterprise JavaBean kann. Ein EJB Container kann dabei mehrere MDBInstanzen vorhalten, die parallel eingehende Nachrichten verarbeiten. Dadurch kann eine Nebenläufigkeit ermöglicht werden, ohne diese explizit zu programmieren. MDBs gestatten somit, dass sich verschiedenste Java-Anwendungen bzw. Anwendungen, die ebenfalls auf den JMS-Server zugreifen, gegenseitig Nachrichten zukommen lassen können, ohne aber ihren weiteren Verlauf von der Zielapplikation abhängig zu machen. 21 6 Literaturverzeichnis [EJB02] Richard Monson-Haefel: Enterprise JavaBeans, 3rd ed., O'Reilly 2002. [JMS01] Richard Monson-Haefel, David A. Chappell: Java Message Service, 1st ed., O'Reilly 2001. [JMS02] Kim Haase: Java Message Service API Tutorial, Sun Microsystems Inc., 2002. Link: http://java.sun.com/products/jms/tutorial/. [MEJB02] Ed Roman: Mastering Enterprise JavaBeans, 2nd ed., John Wiley, 2002. [SE204] Herbert Kuchen: Vorlesung Software Engineering II, Kap. 2a S. 33ff, Institut für Wirtschaftsinformatik - Lehrstuhl Praktische Informatik in der Wirtschaft an der westfälischen Wilhelms-Universität Münster, 2004. Link: http://danae.uni-muenster.de/lehre/kuchen/SS04/SE2k2b.pdf.