Introduzione
Con gli articoli Code JMS (parte 1) e 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.
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:
1 2 |
@Resource(lookup = "jms/distination") private static Destinatio queue; |
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
.
E quindi recuperare le relative istanze nel modo seguente:
1 2 3 4 5 |
@Resource(mappedName = "java:/jms/queue/javaboss") private Queue queue; @Resource(mappedName = "java:/jms/topic/javaboss") private Topic topic; |
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
.
Il client può avere iniettato il connection factory come risorsa identificata da uno specifico JNDI name, nel modo seguente:
1 2 |
@Resource(mappedName = "java:/JmsXA") private ConnectionFactory connectionFactory; |
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:
1 |
Connection connection = connectionFactory.createConnection(); |
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.
Per poter inviare o ricevere messaggi attraverso la connessione creata è necessario che il client invochi il metodo start()
sulla stessa.
1 |
connection.start(); |
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.
1 |
connection.close(); |
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.
1 |
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); |
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()
:
1 2 |
MessageProducer producer = session.createProducer(destination/queue/topic); producer.send(message); |
Eventualmente è possibile create un producer senza destinazione e ritardare la sua definizione sino al momento dell’invio del messaggio:
1 2 |
MessageProducer producer = session.createProducer(null); producer.send(dest, message); |
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.
1 |
MessageConsumer consumer = session.createConsumer(destination/queue/topic); |
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:
1 2 3 4 |
connection.start(); Message m1 = consumer.receive(); Message m2 = consumer.receive(1000); // attende 1000 millisecondi, ovvero 1 secondo connection.start(); |
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à:
1 2 |
TextMessage message = session.createTextMessage(); message.setText("...."); |
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à:
1 2 3 4 5 |
Message msg = consumer.receive(); if (msg instanceof TextMessage) { TextMessage tmsg = (TextMessage) m; System.out.println("Reading message: " + tmsg.getText()); } |
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.