← Zur Uebersicht

Message-Driven Beans (MDB)

Beispiel: ClaimIntakeMDB.

Eine Message-Driven Bean ist ein vom Container verwalteter, asynchroner Nachrichten- Konsument. Sie implementiert kein Business-Interface (kein @Local/@Remote) - niemand ruft sie direkt auf. Stattdessen registriert der Container sie als Listener auf eine JMS-Destination (Queue oder Topic) und ruft onMessage(Message) auf, sobald eine Nachricht eintrifft.

Warum ueberhaupt eine eigene Bean-Art dafuer?

Man koennte JMS-Konsum auch "von Hand" programmieren (Connection, Session, MessageConsumer, eigener Thread). Das wuerde aber bedeuten, selbst fuer Verbindungsmanagement, Nebenlaeufigkeit (mehrere Nachrichten parallel verarbeiten), Fehlerbehandlung und - besonders wichtig - die Transaktionsanbindung zu sorgen. Eine MDB bekommt das alles vom Container: er haelt einen Pool von MDB-Instanzen vor, verteilt eingehende Nachrichten darauf, und startet (im Regelfall) fuer jede Nachricht automatisch eine Transaktion.

Konfiguration: @ActivationConfigProperty

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/novaris/claimsIntakeQueue"),
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")
})

Diese Properties sind bewusst als lose Key-Value-Paare statt als typisierte Annotation- Attribute modelliert - historisch, weil verschiedene JMS-Provider/Application-Server zusaetzliche, providerspezifische Properties brauchen koennen (WebSphere/IBM MQ hat z.B. eigene). destinationLookup referenziert den JNDI-Namen der Queue (siehe JndiNames.CLAIMS_INTAKE_QUEUE) - der eigentliche IBM-MQ-Warteschlangenname (NOVARIS.CLAIMS.INTAKE) wird erst in der Server-/Liberty-Konfiguration gebunden, siehe src/main/liberty/config/server.xml.

Transaktion und Redelivery

Standardmaessig laeuft onMessage(...) in einer containergesteuerten Transaktion, die die JMS-Zustellung selbst mit einschliesst. Wirft die Methode eine unchecked Exception, rollt der Container zurueck - die Nachricht gilt als nicht zugestellt und wird gemaess der Provider-/Queue-Konfiguration erneut ausgeliefert ("Redelivery").

Das ist ein zweischneidiges Schwert:

ClaimIntakeMDB.isLikelyPoisonMessage(...) demonstriert eine einfache Heuristik dagegen (Pruefung von JMSXDeliveryCount). In einer echten IBM-MQ-Produktivkonfiguration uebernimmt das ueblicherweise die Backout-Queue-Konfiguration der Queue selbst (die MQ-Attribute BOTHRESH/BOQNAME) - nach N gescheiterten Zustellversuchen verschiebt der Queue-Manager die Nachricht automatisch auf eine Backout-/Dead-Letter-Queue, ganz ohne Zutun des Java-Codes. Der Code-seitige Check in dieser Bean ist eine zusaetzliche, anwendungsseitige Sicherung, kein Ersatz dafuer.

Checked vs. unchecked Exceptions in onMessage

onMessage(Message) hat eine feste Signatur (Interface javax.jms.MessageListener) ohne throws-Klausel - eine checked Exception kann darin also gar nicht deklariert werden. ClaimIntakeMDB zeigt deshalb beide Reaktionsmuster im Kontrast:

Wohin ruft die MDB weiter?

ClaimIntakeMDB injiziert PolicyManagementLocal per @EJB und ruft eine ganz normale Business-Methode auf einer Stateless Session Bean auf - eine MDB darf (und tut hier) genauso Business-Logik in anderen Beans wiederverwenden wie jeder andere Aufrufer. Siehe die @TransactionAttribute(REQUIRES_NEW)-Begruendung auf PolicyManagementBean.recordClaimAgainstPolicy(...) in docs/10-ejb-konzepte/03-transaktionen-cmt-bmt.md.

⌂ Cockpit