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.
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.
@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.
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.
onMessageonMessage(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:
PolicyNotFoundException, checked, aus policyManagement.recordClaimAgainstPolicy(...))
wird gefangen und geloggt, nicht weitergeworfen - ein erneuter Zustellversuch wuerde am
Ergebnis nichts aendern, die referenzierte Police existiert schlicht nicht.JMSException beim Lesen der Nachricht) wird bewusst in eine
EJBException (unchecked) uebersetzt und weitergeworfen - das IST ein Fall fuer
Redelivery, der Container soll die Transaktion zurueckrollen.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.