Kolumne: EnterpriseTales

JMS 2.0 – Eine neue Version nach über zehn Jahren!

Arne Limburg und Lars Röwekamp

Wenn eine Spezifikation über zehn Jahre kein Update erfährt, hat das seine Gründe. Wenn es nach zehn Jahren dann doch passiert, auch. Was hat es mit JMS 2.0 auf sich? Warum gibt es eine neue Version? Funktionieren alte JMS-Anwendungen weiter wie bisher? Für wen lohnt es sich frühzeitig auf diese Version zu setzen?

Zehn Jahre sind eine lange Zeit, erst recht in der Softwareentwicklung. Aber tatsächlich, die aktuell gültige Version 1.1. der JMS-Spezifikation trägt das Release-Datum 12.04.2002. Das ist jetzt über zehn Jahre her. Normalerweise bedeutet das für eine Java-Spezifikation, dass sie überhaupt nicht mehr verwendet wird und überflüssig geworden ist. Bei JMS ist das anders. JMS ist eine ausgereifte, wenn auch schwergewichtige Technologie, die in vielen Applikationslandschaften, vor allem zur Integration verschiedener Enterprise-Systeme, nach wie vor häufig zum Einsatz kommt. Der Grund, warum es seit zehn Jahren kein Update mehr gegeben hat, liegt auch genau darin begründet: Es war schlicht und einfach nicht notwendig. JMS 1.1 hatte keine größeren Fehler oder Lücken.

Modernisieren des API

Warum dann überhaupt eine neue Spezifikation? Tatsächlich ist es so, dass auch das JMS-API etwas in die Jahre gekommen ist. Z. B. herrschte damals die Meinung vor, dass alle Exceptions, die ein API wie JMS werfen könnte, „checked“ sein müssten, d. h. der nutzende Client muss auf jeden Fall einen Try-catch-Block um den Aufruf herum bauen. Das führt zu hässlichem Boilerplate-Code ohne Mehrwert (Listing 1).

Listing 1
@Resource(lookup = "jms/connectionFactory")
private ConnectionFactory connectionFactory;
@Resource(lookup = "jms/queue")
private Queue queue;

public void sendMessage(String message) {
  Connection connection = null;
  try {
    connection = connectionFactory.createConnection();
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    MessageProducer producer = session.createProducer(queue);
    TextMessage textMessage = session.createTextMessage(message);
    producer.send(textMessage);
  } catch (JMSException e) {
    // What do do here?
  } finally {
    try {
      if (connection != null) {
        connection.close();
      }
    } catch (JMSException e2) {
      // Nothing to do here
    }
  }
}

Eine deutliche Vereinfachung liefert hier das Java-7-Konstrukt try-with-resource, für das die teilnehmenden Ressourcen das Autocloseable-Interface implementieren müssen. Folgerichtig wird dies ab JMS 2.0 für Connection und Session der Fall sein, was den benötigten Exception-Handling-Code deutlich vereinfacht (Listing 2). Insbesondere fällt der unsägliche finally-Block komplett weg.

Listing 2
@Resource(lookup = "jms/connectionFactory")
private ConnectionFactory connectionFactory;
@Resource(lookup = "jms/queue")
private Queue queue;

public void sendMessage(String message) {
  try (Connection connection = connectionFactory.createConnection();
       Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)){
    MessageProducer producer = session.createProducer(queue);
    TextMessage textMessage = session.createTextMessage(message);
    producer.send(textMessage);
  } catch (JMSException e) {
    // What do do here?
  }
}

Auch im Design von Methodensignaturen hat sich in den letzten zehn Jahren einiges getan: 2002 gab es noch kein Java 5 und somit auch noch keine Java Enums, mit denen sich deutlich sprechendere APIs gestalten lassen. Ein schönes Beispiel dafür ist Connection.createSession(boolean transacted, int acknowledgeMode) zum Erzeugen einer JMS Session. Insbesondere der zweite Parameter schreit nach der Verwendung eines Enums anstelle von int-Konstanten. Noch verwirrender wird das API, wenn man die Semantik der beiden Argumente in Kombination betrachtet: Das erste Argument legt fest, ob die Session mit einer Transaktion verknüpft ist. Ist dies der Fall, wird das zweite Argument komplett ignoriert, was die Frage aufwirft, warum man es dann überhaupt angeben muss. Folgerichtig wird das Connection-Interface in JMS 2.0 um zwei Methoden erweitert: Die eine ist createSession()ohne Argumente, die verwendet werden kann, wenn bereits eine Transaktion existiert, mit der sich die Session verbinden soll (der Standardfall im Java-EE-Container) und die zweite ist createSession(int sessionMode) für den nicht transaktionalen Fall, in dem dann der AcknowledgeMode (der jetzt SessionMode heißt) festgelegt werden kann. Warum dieser kein Enum ist, sondern weiterhin aus int-Konstanten besteht, wird wohl das Geheimnis des Spec-Komitees bleiben. Aber da die Spec noch nicht final ist, ist auch hier das letzte Wort noch nicht gesprochen.

Bei den hier besprochenen API-Anpassungen handelt es sich um kleine Änderungen. Nach wie vor ist aber eine Menge Boilerplate-Code notwendig, um eine Nachricht zu verschicken (Listing 1): Holen der ConnectionFactory, Öffnen der Connection, Öffnen der Session, Erzeugen der Message, Verschicken der Message und (in JMS 1.1) alle geöffneten Objekte wieder korrekt schließen. Dass letzteres ab JMS 2.0 dank Autocloseable wegfällt, haben wir bereits gesehen. Dennoch bleibt viel Code übrig (Listing 2).

Für das Empfangen von Nachrichten hat die EJB-Spec hier bereits seit der Version 2.0 mit der Einführung von Message-driven Beans für eine deutliche Vereinfachung gesorgt, die mit der Einführung von EJB 3 und Annotations nochmal schlanker geworden ist. In JMS 2.0 soll nun auch das Senden von Nachrichten radikal vereinfacht werden. Das Schlüsselwort hier heißt „Simplified API“. Hauptziel ist es, besagten Boilerplate-Code auf ein Minimum zu reduzieren. Dies geschieht im Wesentlichen dadurch, dass Connection, Session und optional auch Message zu einem Objekt zusammengefasst werden: dem JMSContext. Diesen kann man sich per CDI injizieren lassen und dabei über die zusätzlichen Annotations @JMSConnectionFactory und @JMSPasswordCredential konfigurieren, welchen JMSContext man bekommen möchte.

Geschrieben von
Arne Limburg und Lars Röwekamp
Kommentare

Schreibe einen Kommentar

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