Detta inlägg ingår i serien Spring från början och behandlar några av de delar inom Spring som hanterar mottag och publicering av JMS-meddelanden.

Typiskt när det gäller JMS så vill man kunna:

  • Publicera meddelanden
  • Ta emot meddelanden asynkront

Om man inte använder Spring så brukar man lösa detta genom att:

  • Publicera meddelanden via JMS-API:et
  • Ta emot meddelanden via MDB (Message Driven Beans)

Publicering av meddelanden

Problemet med att använda JMS-API:et för att publicera meddelanden är att det blir mycket onödig kod som måste skrivas. Checkade exceptions måste hanteras, en rad objekt såsom connection factory, session, destinations etc. måste sparas undan, om JMS-kopplingen går ner så måste hantering för ”re-connect” byggas etc.

Klassen JmsTemplate är den centrala klassen för publicering av meddelanden. Denna klass baseras på ”templating” som tidigare beskrivits i inlägget om Spring JDBC.

Följande exempel visar hur publicering av meddelanden ser ut om man inte använder JmsTemplate utan går direkt mot JMS API:et

Publicering via JMS-API

public class RawJmsMessageSender {
    private boolean isTopic;
    private ConnectionFactory connectionFactory;

    public void sendMessage(String destinationName, String messageText) {
        try {
            Connection connection = connectionFactory.createConnection();
            Session session = connection.createSession(
                    true, Session.AUTO_ACKNOWLEDGE);
            Destination destination = createDestination(
                    session, destinationName);
            MessageProducer producer =
                    session.createProducer(destination);
            TextMessage message = session.createTextMessage();
            message.setText(messageText);
            producer.send(destination, message);
        } catch (JMSException e) {
            // Do error handling here...
            throw new IllegalStateException("Unable to send message", e);
        }
    }

    private Destination createDestination(
            Session session,
            String destinationName) throws JMSException {

        if (isTopic()) {
            return session.createTopic(destinationName);
        } else {
            return session.createQueue(destinationName);
        }
    }

    public boolean isTopic() {
        return isTopic;
    }

    public void setTopic(boolean isTopic) {
        this.isTopic = isTopic;
    }

    public void setConnectionFactory(
            ConnectionFactory connectionFactory) {

        this.connectionFactory = connectionFactory;
    }
}

Springkonfiguration för RawJmsMessageSender

<bean
  id="rawJmsMessageSender"
  class="se.cygni.sample.spring.jms.RawJmsMessageSender">

    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

I exemplet antas att en ConnectionFactory finns tillgänglig. Klassen RawJmsMessageSender kan alltså skicka JMS-meddelanden på en kö eller topic. Exemplet ovan fungerar inte så bra i verkligheten eftersom en ny connection, session och destination skapas varje gång. Ett runtime exception som är helt otypat (IllegalStateException) skapas när ett fel uppstår.

Ovanstående exempel kan förenklas genom att använda JmsTemplate. På köpet får man dessutom möjlighet att använda en bättre felhantering via Springs JMS-exception hierarki och betydligt mindre kod. Kolla in följande för att se hur JmsTemplate kan användas.

Publicering med JmsTemplate

public class JmsTemplateMessageSender {
    private JmsTemplate jmsTemplate;

    public void sendMessage(
            String destinationName, final String messageText) {

        jmsTemplate.send(destinationName, new MessageCreator() {
            public Message createMessage(
                    Session session) throws JMSException {

                return session.createTextMessage(messageText);
            }
        });
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }
}

Springkonfiguration med JmsTemplate

<bean
  id="jmsTemplateMessageSender"
  class="se.cygni.sample.spring.jms.JmsTemplateMessageSender">

    <property name="jmsTemplate" ref="jmsTemplate"/>
</bean>

<bean
  id="jmsTemplate"
  class="org.springframework.jms.core.JmsTemplate">

    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

JmsTemplate tillhandahåller en rad template-metoder och i exemplet ovan används send(String, MessageCreator). De eventuella exceptions som kan slängas hanteras av JmsTemplate. JmsTemplate kan konfigureras för att använda både köer och topics och många av de attribut som kan konfigureras på sessionen kan konfigureras direkt på template-objektet. Förutom send-metoden finns andra template-metoder, kolla exempelvis in dokumentationen för convertAndSend som kan konvertera en POJO till ett Message via en MessageConverter.

Mottag av meddelanden

Vid asynkront mottag av JMS-meddelanden måste interfacet javax.jms.MessageListener implementeras. Message Driven Beans (MDB) implementerar detta interface och när MDB:er används hanterar applikationsservern trådning och transaktionshantering av MDB:n. Om man inte har någon applikationsserver kan Springs Message Driven POJOs (MDP) vara till hjälp för att ta emot meddelanden.

MDP:s implementeras med hjälp av en så kallad ListenerContainer som hanterar trådning och pollar kön/topicen efter meddelanden. De tre varianter som finns är:

  • SimpleMessageListenerContainer

    Den enklaste av de tre containrarna. Denna container startar ett förutbestämt antal JMS-sessioner vid uppstart av containern.
  • DefaultMessageListenerContainer

    Ett mellanting mellan SimpleMessageListenerContainer och ServerSessionMessageListenerContainer som erbjuder vissa konfigurationsmöjligheter. Framför allt så kan denna container ingå i transaktioner som hanteras externt via ex. en TransactionManager.
  • ServerSessionMessageListenerContainer

    Denna container kan konfigureras med extern transaktionshantering och användning av ServerSessionPool SPI för hantering av JMS-sessioner.

Nedanstående exempel visar hur man enkelt kan sätta upp en MDP som lyssnar på en kö som finns tillgänglig i JNDI-trädet.

JMS-mottagare

public class JmsMessageReceiver implements MessageListener {
    public void onMessage(Message message) {
        TextMessage textMessage = (TextMessage) message;
        System.out.println("Received message: " + textMessage);
    }
}

Springkonfiguration för JMS-mottagare

<bean
  id="jndiTemplate"
  class="org.springframework.jndi.JndiTemplate">

    <property name="environment">
        <props>
            <prop key="java.naming.factory.initial>vendor stuff</prop>
        </props>
    </property>
</bean>

<bean
  id="jmsMessageReceiver"
  class="se.cygni.sample.spring.jms.JmsMessageReceiver" />

<bean
  id="jmsContainer"
  class="org.springframework.jms.listener.DefaultMessageListenerContainer">

    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="jmsMessageReceiver" />
    <property name="concurrentConsumers" value="5" />
</bean>

<bean
  id="destination"
  class="org.springframework.jndi.JndiObjectFactoryBean"
  depends-on="connectionFactory">

    <property name="jndiName" value="myQueue" />
    <property name="jndiTemplate" ref="jndiTemplate" >
</bean>

I exemplet ovan används fem trådar som specificeras via attributet concurrentConsumers. Dessa trådar kommer kontinuerligt att polla JMS-kön som är specificerad via bönan destination. När ett meddelande hittas kommer det att vidarebefordras till jmsMessageReceiver-instansen som då måste vara trådsäker.

För mer information om Spring JMS kolla in den officiella dokumentationen.