MokaByte 60 - Febbraio 2002 
JMS
La gestione dei messaggi in Java
di
Stefano Rossini
JMS è l'acronimo di Java Message Service ed indica un'insieme di API che permettono lo scambio di messaggi tra applicazioni Java. Prima di descrivere in dettaglio JMS è bene inquadrare il contesto del suo utilizzo; in particolare bisogna capire cosa si intende per messaggio, o meglio, cosa si intende per messaging system

MOM (Message Oriented Middleware)
Con il termine Messaging si definisce un meccanismo che permette la comunicazione asincrona tra client (detti peer-element) mediante scambio di messaggi caratterizzato da uno scarso grado di accoppiamento.
Data questa definizione si può considerare Messaging System un qualsiasi sistema che scambia pacchetti TCP/IP tra programmi client.
Piuttosto che creare un meccanismo di messaging specifico per ogni situazione che richieda lo scambio di dati, diversi vendors hanno creato un sistema che permette di scambiare messaggi tra applicazioni diverse in modo generico .
Questi sistemi sono chiamati Message Oriented Middleware (MOM).
Mediante l'infrastruttura middleware del MOM processi residenti su macchine diverse possono interagire tra loro mediante l'invio e ricezione di messaggi tipicamente asincroni.
E' importante notare che si è in presenza di un sistema di messaging peer-to-peer : i client interagiscono tra loro non in modo diretto bensì mediante l'interazione di un messaging server.
Il messaging server fa le veci del "postino" : riceve i messaggi dai produttori e li recapita ai relativi destinatari (consumatori)."
Grazie alla mediazione del messaging server i client risultano essere disaccoppiati, cioè non devono sapere nulla l'uno dell'altra.

Figura 1


Il disaccoppiamento nei MOM è una caratteristica molto importante ed è maggiore rispetto ai sistemi middleware basati su architettura ORB(Object Request Broker).Si pensi ad esempio a RMI : un'applicazione per poter comunicare con un oggetto remoto deve essere a conoscenza dei suoi metodi per poterli invocare.
Nei sistemi MOM i client non interagiscono direttamente tra di loro, nè il produttore nè il ricevitore devono essere a conoscenza l'uno dell'altro, l'unica cosa che entrambi devono sapere è il formato del messaggio e la "casella di posta" (destination) da usare.


Figura 2

Altra importante caratteristica offerta dai sistemi MOM è il concetto di Guaranteed Message Delivery (GMD) che garantisce la consegna del messaggio.
Al fine di prevenire una perdita di informazioni in caso di crash del message server, i messaggi devono essere resi persistenti (ad es: mediante JDBC) prima di essere recapitati ai consumatori.

 

Modelli di messaging
Dopo questa breve introduzione sul MOM vediamo i suoi due diversi modelli di messaging con i quali è possibile scambiarsi i messaggi : Point to point(PTP), Publish and Subcribe(Pub/Sub).

Point-To-Point
Nel modello PTP un client può mandare uno o più messaggi ad un altro client. La comunicazione, come già sopra spiegato, non avviene in modo diretto bensì condividendo una stessa destinazione dove si inviano e prelevano i messaggi. La destinazione nel modello Point-to-Point prende il nome di Queue (coda).
Piu' produttori e piu' consumatori possono condividere la stessa coda e ogni messaggio ha un solo consumatore; questo permette di fatto una comunicazione uno a uno. Non ci sono dipendenze temporali tra sender e receiver del messaggio e quindi il receiver può ricevere il messaggio anche se non era in ascolto sulla coda al momento dell'invio.
Il messaggio viene tolto dalla coda una volta che il ricevitore l'ha prelevato confermando (acknowledge) la ricezione al produttore. L'utilizzo del messaging PTP è da utilizzare nel caso si abbia l'esigenza che ogni messaggio spedito sia ricevuto (e confermato) da un solo consumatore.


Figura 3

 

Publish/Subscribe
Nel modello Publish/Subscribe (detto anche sistema di messaging event-driven), i client inviano messaggi ad un topic: una coda "speciale" in grado di inoltrare i messaggi a piu' destinatari.
Il Sistema si prende carico di distribuire i messaggi inviati ("pubblicati") da piu' publishers i quali sono automaticamente ricevuti da tutti i client sottoscritti ("subscribed"), cioè che si sono dichiarati interessati alla ricezione. I client possono essere dinamicamente o publisher o subscriber o entrambi (come si vedrà nell'esempio pratico del prossimo articolo).
Esiste una dipendenza temporale tra publisher e subscriber visto che il receiver può consumare il messaggio solo dopo essersi "sottoscritto" (subscription) al topic d'interesse dopo che il client ha effettivamente inviato il messaggio. Inoltre il receiver deve rimanere attivo se vuole continuare a ricevere.
Il modello Pub/Sub permette quindi una comunicazione uno a molti visto che ogni messaggio può avere piu' consumatori. Il messaggio una volta consumato da tutti i subscriber interessati viene tolto dal Topic. Le API JMS rilassano i vincoli temporali mediante il concetto di Durable Subscriptions. Le Durable subscriptions permettono di ricevere i messaggi anche se i subscribers non erano in ascolto poichè i messaggi vengono resi persistenti, ad esempio mediante memorizzazione su database.
Questi messaggi vengono inoltrati una ed una sola volta e non possono essere persi nè inoltrati una seconda volta.



Figura 4


L'utilizzo di messaging Publish/Subscribe è da utilizzare quando si ha la necessità che ogni messaggio sia ricevuto da piu' consumatori. Le destinazioni sono accessibili in modo concorrente e possono definire anche dei filtri per esprimere le condizioni da rispettare affinché un messaggio venga recapitato ad una certa destinazione.

 

Cos'è JMS ?
Java Message Service (JMS) è un'insieme di API Java che permette di creare, spedire, ricevere e leggere messaggi. JMS è stato sviluppato da SUN insieme ai maggior produttori di sistemi MOM per definire un'interfaccia comune indipendente dalla specifica implementazione del sistema di messaging in modo analogo a quanto avviene con JDBC e JNDI. L'applicazione Java JMS risulta così essere indipendente, oltre che dal sistema operativo, anche dalla specifica implementazione del sistema di messaging.


Figura 5

Mediante JMS è possibile interagire con sistemi MOM esistenti quali MQSeries di IBM, TIB/Rendezvous di TIBCO Software, Fiorano, ...

 

Gli attori JMS
In un'applicazione JMS gli attori coinvolti sono ConnectionFactory, Destination, Connection, Session, MessageProducer, Message e MessageConsumer come rappresentato in figura 6.


Figura 6

Di seguito viene descritto brevemente il compito di ciascun attore JMS.

Administered objects
Le Destination (Queue/Topic) ed i Connection Factory sono detti Administered Objects perché incapsulano le specifiche implementazioni dei Provider JMS.
Gli Administered objects sono vendor-dependent, cioè la loro specifica implementazione varia da provider a provider e per questo motivo non sono gestiti all'interno del programma Java.
Le interfacce JMS permettono allo sviluppatore Java di astrarsi dai dettagli implementativi della specifica versione di MOM permettendo il riutilizzo del codice al variare dell'implementazione JMS rispettando appieno la filosofia Java.
La creazione del connection factory e delle destination è compito dell'amministratore JMS che deve inserirli all'interno di un contesto JNDI in un opportuno namespace in modo da potere essere recuperati da qualsiasi client mediante le normali API JNDI.
Un'applicazione JMS client, per inviare messaggi (Sender/Publisher) o per riceverli (Receiver/Subscriber), utilizza i servizi JNDI per ottenere un oggetto di tipo ConnectionFactory e uno o piu' oggetti destination (Queue o Topic).

Connection Factory
E' l'Administered Object utilizzato dal client per ottenere una connessione con il message server.
Nel caso point-to-point si utilizza l'interfaccia QueueConnectionFactory :

Context ctx = new InitialContext();
QueueConnectionFactory
QueueConnectionFactory
(QueueConnectionFactory)ctx.lookup("MyQueueConnectionFactory");

mentre nel caso di Publish/Subscriber si utilizza l'interfaccia TopicConnectionFactory

TopicConnectionFactory topicConnectionFactory =
(TopicConnectionFactory)ctx.lookup("MyTopicConnectionFactory");

Destination
E' l'Administered Object che rappresenta l'astrazione di una particolare destinazione.
Anche in questo caso il client identifica la destinazione mediante l'utilizzo delle API JNDI.
Nel caso point-to-point la destinazione è rappresentata dall'interfaccia Queue

Queue queue = (Queue) jndiContext.lookup("MyQueue");

mentre nel caso di Publish/Subscriber mediante l'interfaccia Topic

Topic topic = (Topic) jndiContext.lookup("MyTopic");

Connection
Rappresenta l'astrazione di una connessione attiva con un particolare JMS provider e si ottiene dall'oggetto Connection Factory.
Nel caso point-to-point si ottiene un reference d'interfaccia QueueConnection invocando il metodo createQueueConnection sull'oggetto Connection Factory :

QueueConnection queueConnection;
queueConnection = queueConnectionFactory.createQueueConnection();

analogamente nel caso di Publish/Subscriber si ottiene un reference d'interfaccia TopicConnection invocando il metodo createTopicConnection sull'oggetto Connection Factory

TopicConnection topicConnection =
topicConnectionFactory.createTopicConnection();

Session
L'interfaccia Session si ottiene dall'oggetto Connection e rappresenta il canale di comunicazione con una destinazione e permette la creazione sia di produttori che di consumatori di messaggi.
La creazione della sessione permette di specificare il controllo della transazione e la modalità di acknowledge.
Nel caso point-to-point la Session è identificata mediante l'interfaccia QueueSession :

QueueSession queueSession = queueConnection.createQueueSession(
                            false,Session.AUTO_ACKNOWLEDGE);

nel caso di Publish/Subscriber mediante l'intefaccia TopicSession :

TopicSession topicSession = topicConnection.createTopicSession
                            (false,Session.AUTO_ACKNOWLEDGE);

Messages
I messaggi JMS sono costituiti da tre parti : un'intestazione (message header), una serie di campi contenti proprietà (property fields) ed il corpo (message body).


Figura 7

Il message header è costituito esattamente da 10 campi che devono essere obbligatoriamente presenti. Tali campi sono usati sia dai client che dai provider al fine di identificare ed inoltrare il messaggio.
Tra questi è presente l'identificatore univoco del pacchetto (JMSMessageID), il nome della Queue o Topic a cui il pacchetto è destinato(JMSDestination), la priorità (JMSPriority), il timestamp del tempo d'inoltrto(JMSTimestamp), l'ID per collegare i messaggi JMS tra di loro (JMSCorrelationID).
I property fields sono coppie di nome e valore e sono utili per costruire "filtri" a livello applicativo.
La caratteristica di questi campi è che possono essere esaminati mediante i message selectors.
Quando un consumatore si connette al server puo' definire un message selector il quale esamina l'header e i property fields (non il body message) del pacchetto JMS di una specifica destinazione sia essa una Queue o un Topic.
Questo permette alle applicazioni JMS di utilizzare i message selector per specificare quali messaggi è interessata a ricevere utilizzando una sintassi condizionale sottoinsieme del linguaggio SQL-92.
Il filtraggio avviene a livello di server e permette di inoltrare ai client lungo la rete i messaggi strettamente necessari o utili risparmiando così la banda del canale.
Un message selector non è altro che un oggetto di classe String che può contenere boolean literals (TRUE, true, FALSE and false), operatori logici(NOT, AND, OR),aritmetici(+,-,*,/),di comparazione(=, >, >=, <, <=, <> ),di ricerca(IS,LIKE,IN,NOT IN) ecc .. [JAPI].
Esempi di message selector :

"squadra = 'Pittsburgh Steelers'"
"squadra <> 'Dallas Cowboys'"
"age NOT BETWEEN 20 and 32"
"type = `FIAT' AND color = `blue' AND weight >= 100"
"NewsType = 'Opinion' OR NewsType = 'Sports'"

I property field sono valorizzabili mediante i metodi setObjectProperty e getObjectProperty.
Il metodo setObjectProperty accetta in ingresso valori di classe Boolean, Byte, Short, Integer, Long, Float, Double e String e se si cerca di utilizzare qualsiasi altra classe viene generata l'eccezione JMSException.
Le proprietà possono essere lette mediante i metodi get<Class>Property; nel caso di proprietà inesistente i metodi getStringProperty e getObjectProperty ritornano null, per le altre classi viene sollevata una NullPointerException.
Infine le proprietà possono essere cancellate mediante il metodo clearProperties, lasciando il messaggio JMS con il campo PropertyFields vuoto.

 

Body Message
I messaggi JMS possono essere di varie tipi, ognuno dei quali mette a disposizione gli appositi metodi get e set.

JMS non supporta uno specifico body per messaggi XML; in questi casi è utilizzabile il text message.
I messaggi vengono creati mediante i metodi dell'interfaccia Session dalla quale estendono sia l'interfaccia QueueSession che TopicSession.
L'interfaccia mette a disposizione i metodi create relativi ad ogni tipologia di message : createTextMessage,createObjectMessage,createMapMessage,createBytesMessage e createStreamMessage prevedendo per ognuno di loro la versione overloaded che prevede come parametro in ingresso un oggetto di inizializzazione.
Per creare messaggi nel caso di tipologia Point-to-point è sufficiente invocare il metodo create opportuno sulla QueueSession, mentre nel caso di Publish/Subscribe sull'oggetto TopicConnection come mostrato negli esempi che seguono :

TextMessage txtMsg, txtMsg;
txtMsg = queueSession.createTextMessage();
txtMsg.setText("Hello JMS PTP World!");
txtMsg2 = queueSession.createTextMessage("Hello JMS PTP World!");
ObjectMessage objMsg = queueSession.createObjectMessage();
ObjectMessage objMsg = topicSession.createObjectMessage();

 

MessageProducer
Il MessageProducer è l'oggetto che permette l'invio dei messaggi verso una particolare destinazione.
Utilizzando gli oggetti Session e Destination creati, si possono inizializzare uno o piu' message producer.
Nel caso point-to-point il Message Producer per inviare messaggi sulla Queue è referenziato mediante l'interfaccia QueueSender :

QueueSender queueSender = queueSession.createSender(queue);

mentre nel caso di Publish/Subscriber l'invio dei messaggi verso il Topic può avvenire mediante l'intefaccia TopicPublisher :

TopicPublisher topicPublisher= topicSession.createPublisher(topic);

Una volta creato il produttore, basta invocare il metodo send sull'oggetto QueueSender nel caso PTP mentre nel caso Pub/Sub il metodo publish sull'oggetto topicPublisher; entrambi i metodi send richiedono in ingresso un parametro d'interfaccia Message e presentano versioni overloaded [JAPI].

queueSender.send(message);
topicPublisher.publish(message);

 

MessageConsumer
Il MessageConsumer rappresenta un oggetto in grado di ricevere messaggi.
Analogamente a quanto avviene per il Message Producer bisogna indicare l'oggetto Session e la Destination d'interesse per inizializzare correttamente l'oggetto Message Consumer.

QueueReceiver queueReceiver = queueSession.createReceiver(queue);
TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);

Nel caso Publish/Subscribe è inoltre possibile creare le DurableSubscriber mediante il metodo createDurableSubscriber sull'oggetto Topic Session.
Nella ricezione in modo asincrono il client definisce un message listener (una classe che implementa l'interfaccia javax.jms.MessageListener) e ogni qualvolta un messaggio arriva alla destinazione d'interesse, il JMS provvede ad invocare il metodo di callback onMessage.
Si deve creare come primo passo un listener

TopicListener topicListener = new TextListener();

e passarlo come argomento al metodi setMessageListener dell'oggetto Receiver

QueueReceiver.setMessageListener(topicListener);
topicSubscriber.setMessageListener(topicListener);

Connesso il listener ci si pone in ascolto di ricevere e processare i messaggi invocando il metodo start sull'ogetto Connection :

queueConnection.start();
topicConnection.start();

La classe Listener è una normale classe Java che implementa l'interfaccia MessageListener ridefinendo perciò il metodo onMessage.
In tale metodo si mette la logica applicativa per gestire il contenuto del messaggio ricevuto; si effettua il controllo del tipo del messaggio ricevuto e se è del tipo corretto se ne processa il contenuto.

public class TextListener implements MessageListener {
    public void onMessage(Message message) {
    try {
        if (message instanceof TextMessage) {
        TextMessage txtMsg = (TextMessage) message;
        System.out.println("Ricevuto : " + txtMsg.getText());
        ...
        if (message instanceof ObjectMessage) {
            objMsg = (ObjectMessage) message;
            MyMsgClass msg = (MyMsgClass)obj.getObject();
            // MyMsgClass è una classe proprietaria serializzabile
        }
        ...
    }

}

I prodotti di Messaging sono intrinsecamente asincroni in quanto produttore e consumatore del messaggio risultano disaccoppiati, nonostante questo è bene specificare che un MessageConsumer può ricevere i messaggi anche in modo sincrono.
Nella ricezione dei messaggi in modo sincrono il consumatore richiede esplicitamente alla destinazione di prelevare il messaggio (fetch) invocando il metodo receive.
Il metodo receive appartiene all'interaccia javax.jms.MessageConsumer (dalla quale estendono sia QueueReceiver che TopicReceiver) ed èsopsensivo, cioè rimane bloccato fino alla ricezione del messaggio, a meno che non si espliciti un timeout finito il quale il metodo termina :

public Message receive() throws JMSException
public Message receive(long timeout) throws JMSException

Esempio di ricezione sincrona :

while(<condition>) {
    
Message m = queueReceiver.receive(1000);
    if( (m!=null)&&(m instanceof TextMessage) {
        message = (TextMessage) m;
        System.out.println("Rx :" + message.getText());
    }
}

Finite le operazione di gestione del messaggio ricevuto, si effettua il cosiddetto codice di "pulizia" chiudendo la connessione JMS mediante il metodo close.

queueConnection.close();
topicConnection.close();

 

Conclusioni
In questo articolo si è introdotto l'argomento dei MOM e si è presentata una panoramica sulle API JMS.
Nel prossimo articolo verrà presentato un esempio completo di applicazione JMS che utilizza il modello Publish/Subscribe e la modalità di ricezione asincrona.

 

Bibliografia
[JMST] JMS tutorial - http://java.sun.com/products/jms/tutorial/index.html
[JAPI] J2SDKEE API Documentation
[JMSS] JMS Specification (versione 1.0.2b) - http://java.sun.com/products/jms/docs.html
[BWLS] P. Gomez, P. Zadrozny - "Java 2 Enterprise Editino with BEA Weblogic Server", Wrox "001
[CPJMS] P. Campanelli - "Java Message Service", Computer Programming N. 105

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it