Java Message Service o JMS è un insieme di API che consentono lo scambio di messaggi tra applicazioni java distribuite sulla rete. La versione 2.0 della specifica è definita sotto il Java Community Process come JSR 343. Con il termine messaging si intende un meccanismo che consente la comunicazione asincrona tra client remoti in modalità loosely coupled.
La specifica non include la specifica di come i client devono cooperare per realizzare un servizio critico (load balancing e fault tolerance), non specifica come i client possono notificare gli errori e neppure offre servizi di privacy ed integrità dei dati.
L’architettura
Le componenti caratteristiche di un sistema conforme alla specifica JMS sono:
- JMS Client: programmi che si possono comportare come producer, inviano messaggi o consumer, ricevono messaggi;
- JMS provider: sistema di messaggistica che implementa JMS e realizza delle funzionalità aggiuntive per l’amministrazione e il controllo della comunicazione attraverso messaggi;
- Administered Object: sono oggetti JMS preconfiguarti, creati da un amministratore ad uso dei client. Incapsulano la logica specifica del JMS provider nascondendola ai client, garantendo maggiore portabilità al sistema complessivo. Possono essere:
- Connection Factory: oggetto utilizzato da un client per realizzare la connessione con il provider;
- Destination (queue/topic): oggetto utilizzato da un client per specificare la destinazione dove il messaggio è spedito/ricevuto.
Le Connection Factory e le Destination sono legati a nomi simbolici ed identificati attraverso un nome JNDI (Java Naming and Directory Service). I servizi JNDI sono tipicamente resi disponibili dall’Application Server per ottenere gli oggetti remoti registrati. Il nome degli oggetti JMS è gestito dall’amministratore e non dai client, come avviene in RMI. L’applicazione che intende usare JMS deve adoperare JNDI per ottenere i riferimenti a tali oggetti.
Lo schema seguente, preso in prestito dalla documentazione di ActiveMQ, mostra i componenti appena descritti.
Il Modello di Messaggistica
I possibili modelli di messaggistica disponibili in JMS sono due: point-to-point e publish/subscribe. Un JMS provider standalone può implementarne solamente uno o entrambe, ma uno conforme alla specifica J2EE deve implementarli entrambe.
Point-to-point (PTP)
Questa tipologia di comunicazione si fonda sul concetto di queue (coda) che il producer (chiamato sender) utilizza per indirizzare i propri messaggi. Le code conservano tutti i messaggi inviati fino a quando il consumer (chiamato receiver) lo legge oppure il messaggio scade.
Un messaggio può essere consumato da un solo receiver, il quale può leggerlo in qualsiasi momento indipendentemente dallo stato che ha il produce al momento della lettura o dallo stato che esso aveva quando il producer ha inserito il messaggio nella coda.
Più producer e più consumer possono condividere la stessa coda, ma ogni messaggio può essere letto da un solo consumer il quale deve inviare la notifica dell’avvenuta ricezione e processamento del messaggio (acknowledgement).
I messaggi sono consegnati nell’ordine di invio ed il provider garantisce che siano consegnati una ed una sola volta.
Il modello PTP si rivela utile quando è necessario garantire che un messaggio arrivi ad un solo destinatario che notifichi la corretta ricezione.
Publish/Subscribe (Pub/Sub)
Questa tipologia di comunicazione si fonda sul concetto di topic, una sorta di bacheca. I client, che sono generalmente anonimi, si distinguono in publisher (i producer) e subscriber (i consumer).
Il messaggio pubblicato dai publisher viene preso in carico dal sistema il quale si occupa di conservarlo fino a quando tutti i subscriber, che si siano dichiarati interessati, lo hanno ricevuto.
In questo tipo di comunicazione esiste una relazione temporale per cui un subscriber può ricevere solamente messaggi inviati dal publisher dopo la sottoscrizione, così come può continuare a riceverne solamente mantenendo attiva tale sottoscrizione.
Non vi è garanzia sull’ordine di ricezione dei messaggi, così come non vi è garanzia che il messaggio sia ricevuto una sola volta.
Il modello pub/sub prevede due tipi di sottoscrizione:
- Topic-based: le informazioni vengono suddivise in argomenti (topic o subject) e le sottoscrizioni/pubblicazioni vengono effettuate scegliendo come discriminante un argomento (letteralmente, topic);
- Content-based: utilizza dei filtri (message selector) per una selezione più accurata delle informazioni da ricevere sul topic.
Configurazione di WildFly
WildFly utilizza HornetQ come sistema di messaggistica, il quale utilizza il file system per la persistenza dei dati. La sua configurazione in WildFly avviene attraverso il file standalone.xml
, nel quale di default il sistema non è abilitato. Per farlo è sufficiente aggiungere l’estensione:
1 |
<extension module="org.jboss.as.messaging"/> |
1 2 3 4 5 |
<subsystem xmlns="urn:jboss:domain:messaging:2.0"> <hornetq-server> ... </hornetq-server> </subsystem> |
In alternativa utilizziamo il file di configurazione standalone-full.xml
in cui il sottosistema di messaging è configurato e pronto all’utilizzo. Per i nostri scopi utilizziamo tale file ed aggiungiamo due code che vanno ad aggiungersi a quelle di default:
1 2 3 4 5 6 7 8 9 |
<jms-destinations> .... <jms-queue name="JBQ"> <entry name="java:/jms/queue/javaboss"/> </jms-queue> <jms-topic name="JBT"> <entry name="java:/jms/topic/javaboss"/> </jms-topic> </jms-destinations> |
1 2 |
INFO [org.jboss.as.messaging] JBAS011601: Bound messaging object to jndi name java:/jms/queue/javaboss INFO [org.jboss.as.messaging] JBAS011601: Bound messaging object to jndi name java:/jms/topic/javaboss |
Il Progetto
Creiamo un progetto Maven di base e poi aggiungiamo la facet Dynamic Web Module. Quindi inseriamo le seguenti dipendenze:
1 2 3 4 5 6 7 8 9 10 11 12 |
<dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> |
Implementiamo la classe JBQWriter
per l’invio di messaggi verso la coda JBQ
come EJB di tipo singleton nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
@Singleton public class JBQWriter { @Resource(name = "java:/jms/queue/javaboss") private Queue jbq; @Resource(mappedName = "java:/JmsXA") private ConnectionFactory cf; private Connection connection; public void sendMessage(String txt) { try { connection = cf.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer publisher = session.createProducer(jbq); connection.start(); TextMessage message = session.createTextMessage(txt); publisher.send(message); } catch (Exception exc) { exc.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (JMSException e) { e.printStackTrace(); } } } } } |
name
identifica la risorsa attraverso il suo nome JNDI. Nella classe è utilizzata in due occasioni:
- per l’iniezione della coda JBQ identificata dal JNDI name
java:/jms/queue/javaboss
; - per l’iniezione della classe
ConnectionFactory
utilizzata per la creazione della connessione verso il provide JMS.
In modo simile è implementato l’EBJ JBQReader
per la lettura della coda JBQ, che si presenta quindi nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
@Singleton public class JBQReader { @Resource(mappedName = "java:/jms/queue/javaboss") private Queue jbq; @Resource(mappedName = "java:/JmsXA") private ConnectionFactory cf; private Connection connection; public String readMessage() { try { connection = cf.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createConsumer(jbq); connection.start(); Message msg = consumer.receive(5000); if ( msg != null ) { return msg.getBody(String.class); } return null; } catch (Exception e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (JMSException e) { e.printStackTrace(); } } } return null; } } |
L’interfaccia
L’interazione con i due EJB avviene attraverso due servlet SendMessageServlet
e ReadMessageServlet
, che troverete nel progetto, ed una semplice interfaccia web da cui è possibili inserire il messaggio in coda e leggerlo attraverso due pulsanti, come mostrato nella figura seguente.
E’ interessante notare che HornetQ mantiene la persistenza dei messaggi. Proviamo infatti ad eseguire le seguenti operazioni:
- avviare WildFly;
- inseriamo più messaggi nella coda JBQ;
- spegnere WildFly e riavviarlo;
- leggere i messaggi dalla coda.
Nonostante il riavvio dell’Application Server i messaggi inseriti in coda sono regolarmente letti al passo 4.
Codice Sorgente
Il codice sorgente del progetto è disponibile qui jms.