Seminar Softwaretechnik - Department of Information Systems

Transcrição

Seminar Softwaretechnik - Department of Information Systems
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.