Il Modello di Programmazione delle API JMS

Introduzione

Con gli articoli Code JMS (parte 1)Code JMS (parte 2) abbiamo introdotto i concetti principali relativi alla tecnologia delle code JMS. In questo post riassumiamo brevemente il loro modello di programmazione.

Nella figura seguente, prestata dal sito ufficiale della oracle, riconosciamo gli elementi applicativi che caratterizzano le API JMS (che sono parte della specifica J2EE), e che sono descritti nel seguito.

jms api programming model

Destination

Una destination è un oggetto che un client utilizza per specificare la destinazione dei messaggi che produce o l’origine dei messaggi che consuma. Nel dominio di messaggistica P2P sono chiamati queue, mentre nel dominio pub/sub sono chiamati topic.

Le destination sono generalmente configurate in un Application Server ed identificate attraverso un JNDI name ed iniettate in un oggetto Destination nel modo seguente:

In questo modo si ottiene un oggetto che può essere utilizzato sia per le queue che per i topic. In alternativa è possibile specializzare la destinazione in un oggetto di tipo Queue o Topic.

queue topic destination

E quindi recuperare le relative istanze nel modo seguente:

Connection Factory

Il connection factory è l’oggetto che un client (producer o consumer) utilizza per creare una connessione al provider. Esso incapsula un insieme di parametri di configurazione di connessione che sono stati definita da un amministratore. Le sue istanze possono essere una implementazione di una interfaccia ConnectionFactory, QueueConnectionFactory o TopicConnectionFactory.

jms connection factory

Il client può avere iniettato il connection factory come risorsa identificata da uno specifico JNDI name, nel modo seguente:

Connection

Una connessione JMS incapsula un collegamento virtuale con un provider JMS. Ad esempio potrebbe rappresentare un socket TCP/IP aperta tra un client e un demone del fornitore del servizio. Nella specifica è rappresentata attraverso un oggetto che implementa l’interfaccia Connection e che è ottenuto per mezzo di un connection factory, nel modo seguente:

Un oggetto di tipo Connection può essere specializzata in una istanza dell’interfaccia QueueConnection, per il modello P2P, o dell’interfaccia TopicConnection, per il modello publish and subscribe.

jms connection

Per poter inviare o ricevere messaggi attraverso la connessione creata è necessario che il client invochi il metodo start() sulla stessa.

Quando l’applicazione termina deve chiudere tutte le connessioni create. La mancata chiusura di una connessione può causare il mancato rilascio delle risorse nel provider JMS. La chiusura di una connessione comporta anche la chiusura delle sue sessioni e dei message producer e consumer associati.

Session

Una sessione JMS è un contesto attraverso il quale è possibile produrre o consumare messaggi. Fornisce un contesto transazionale attraverso il quale è possibile raggruppare operazioni di lettura o scrittura di messaggi in una unità di lavoro atomica.

Una sessione implementa l’interfaccia Session e la connessione JMS fornisce un metodo factory attraverso il quale è possibile ottenerne una istanza.

Il primo argomento indica se la sessione deve utilizzare o meno una transazione locale, mentre il secondo indica la modalità di acknowledge. In generale in un container J2EE o EJB i due parametri sono ignorati in quanto il container (attraverso JTA) si occupa di generare il contesto transazionale in base al quale invia gli acknowledgement. In tale caso è utilizzabile la versione del metodo senza parametri.

Message Producer

Un message producer è un oggetto che viene creato a partire da una sessione ed è utilizzato per l’invio di messaggi a una destinazione. Esso implementa l’interfaccia MessageProducer ed è collegato ad una specifica destinazione. Attraverso il producer è poi possibile inviare il messaggio, creato in precedenza, invocando il metodo send():

Eventualmente è possibile create un producer senza destinazione e ritardare la sua definizione sino al momento dell’invio del messaggio:

Message Consumer

Un message consumer è un oggetto che viene creato a partire da una sessione ed è utilizzato per la ricezione dei messaggi inviati a una destinazione. Esso implementa l’interfaccia MessageConsumer e consente ad un client JMS di registrare (sul provider) il suo interesse a ricevere messaggi da una destinazione. Il provider JMS gestisce la consegna dei messaggi da una destinazione ai consumatori iscritti sulla destinazione.

In alternativa può essere utilizzato il metodo createDurableSubscriber() che registra una sottoscrizione durevole ed ovviamente è utilizzabile esclusivamente in caso di topic.

Dopo aver creato un message consumer esso diventa attivo, e lo si può utilizzare per ricevere i messaggi. È possibile poi utilizzare il metodo close() per renderlo inattivo. Il recapito dei messaggi non inizia fino a quando non si avvia la connessione creata invocando il metodo start(). Dimenticare di avviare la connessione è uno dei più comuni errori di programmazione delle code JMS.

Si utilizza il metodo di receive() per consumare un messaggio in modo sincrono. Il client è bloccato fino a quando un messaggio non è restituito dal provider. Per ritornare anche in caso di assenza di messaggi è possibile specificando un timeout:

Se si desidera ricevere i messaggi in modo asincrono è necessario implementare un message listener.

Message

Scopo ultimo di una applicazione che utilizza JMS è l’invio e la ricezione di messaggi. tali messaggi hanno una formato semplice ma flessibile che ben si adatta a qualsiasi tipologia di applicazione. Un messaggio JMS si compone di tre parti: un header, un insieme di proprietà ed un body.

Gli header sono un insieme di proprietà predefinite utilizzate per l’identificazione e l’instradamento del messaggio. Esempi di header sono: JMSMessageID, identificatore univoco del messaggio, e JMSDestination che identifica la coda o il topic di destinazione. Ogni header ha un metodo set ed uno get definito nell’interfaccia Message, ma alcuni sono utilizzabili dal client mentre altri sono impostati per default. Si veda la tabella How JMS Message Header Field Values Are Set.

È inoltre possibile creare e impostare delle proprietà personalizzate per i messaggi inviati, se si ha bisogno di valori aggiuntivi rispetto a quelli forniti dai campi header. È possibile utilizzare tali proprietà per garantire la compatibilità con altri sistemi di messaggistica, oppure per creare selettori di messaggi.

La specifica JMS prevede 5 tipologie differenti di message body, che consentono di inviare e ricevere dati in formati diversi. Tali tipologie sono ottenute come specializzazione dell’interfaccia Message e sono:

TextMessage Rappresenta un contenuto di tipo java.lang.String (es. un XML)
MapMessage Rappresenta un insieme di coppie chiave-valore, in cui la chiave è di tipo stringa mentre i valori sono di un qualsiasi tipo primitivo java.
BytesMessage Rappresenta uno streamd i byte.
StreamMessage Rappresenta un stream di valori primitivi inseriti/letti sequenzialmente.
ObjectMessage  Rappresenta un qualsiasi oggetto serializzabile (che implementa java.io.Serializable).

L’oggetto Session fornisce metodi specifici per creare ciascuna tipologia di messaggio. Ad esempio per un TextMessage il codice sarà:

Il consumatore riceve un oggetto generico di tipo Message e spetta a lui eseguire il cast al tipo di messaggio specifico. Ad esempio per estrarre il testo del messaggio prodotto sopra il codice sarà:

Codice Sorgente

Per un esempio completo di lettura e scrittura di messaggi verso una coda o un topic si rimanda agli articoli citati in introduzione.