Programmation avec l'API JMS
Généralités
figure JMS API |
Les éléments de base d' une application JMS sont:
- Les Objets administrés (l'interface ConnectionFactory et l'interface Destination),
- L'objet java.jms.Connection,
- l'objet javax.jms.Session,
- l'objet javax.jms.JMSContext combine les connexions et les sessions dans un seul objet,
- l'objet MessageProducer (producteur de messages) est utilisé pour envoi de message(s),
- l'objet MessageConsumer (consommateur de message) est utilisé pour recevoir des messages,
- l'interface javax.jms.Message
- QueueBrowser est un objet qui permet à une application de naviguer à travers les messages présents dans une file d'attente de messages (queue messages).
Les objets administrés JMS
Les deux composants de l'API JMS à savoir "Destination" et "ConnectionFactory" (fabriques de connexions), sont appelés objets administrés; ces objets sont implémentés différemment d' un fournisseur JMS (Provider JMS) à l'autre; pour les utiliser, on les configure dans un fichier. Les applications clientes JMS ont accès à ces objets administrés via des interfaces portables (elles peuvent fonctionner avec peu ou pas de changement du tout dans différents mise en œuvre de l'API JMS). C'est l'administrateur qui configure les objets administrés dans un "JNDI namespace" et les clients JMS y accèdent par Injection de ressource (voir autre cours pour la différence entre injection de ressources - Ressource Injection- et Injection de Dépendence - Dependency Injection -).
Avec le Serveur d'application GlassFish, on utilise la commande "asadmin create-jms-resource" ou la Console d'Administration pour créer les objets administrés JMS sous forme de ressources de connecteur. On peut aussi spécifier les ressources dans un fichier glassfish-resources.xml.
Fabriques de connexion (Connection Factories)
Une fabrique de connexions ConnectionFactory (Connection Factory) est un objet utilisé pour créer une connexion d'avec un "JMS Provider"; cet objet encapsule un ensemble de paramètres de configuration; chaque "fabrique de connexion" est soit une instance d'une interface ConnectionFactory, soit d'une interface QueueConnectionFactory ou d'une interface TopicConnectionFactory. Au début d'un programme Client JMS, on injecte généralement une ressource de "fabrique de connexion" et le serveur d'application Java EE offre une connexion JMS logique JNDI "java:comp/DefaultJMSConnectionFactory" comme suit:
@Resource(lookup = "java:comp/DefaultJMSConnectionFactory") private static ConnectionFactory connectionFactory;
Ou bien, on initialise un contexte avec l' objet Context qui permet d'utiliser l'un des interfaces qu'on a précisé un peu plus haut (voir le code ci-dessous)
Context context = new = InitialContext();
// 1. si on utilise ConnectionFactory
ConnectionFactory connectionFactory =
(ConnectionFactory) context.lookup ("ConnectionFactory");
//2. si on utilise QueueConnectionFactory
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) context.lookup ("QueueConnectionFactory");
//3. si on utilise TopicConnectionFactory
TopicConnectionFactory topicConnectionFactory =
(TopicConnectionFactory) context.lookup ("TopicConnectionFactory");
JMS Destination
Pour l' API JMS, une destination est un objet java implémenté à partir de l'interface javax.jms.Destination; cet objet représente l'endroit logique où une application cliente JMS dépose ses messages si elle est productrice de messages et où elle récupère des messages si elle est consommatrice de messages (voir figure JMS API). Si on est dans une communication point à point, la destination c'est la file d'attente de messages (queue) par contre si on est dans un système de communication de type subscrib/publication, la destination s'appelle Topic (sujet). Une application peut utiliser les deux systèmes de communication à la fois.
Avec le serveur d'application Glassfish, on crée une Destination en le spécifiant dans un espace de nom JNDI; chaque Destination se réfère à une ressource physique et non logique.
On peut créer un objet Destination de façon explicite; toutefois, si une application juge nécessaire d'en avoir une, elle la crée automatiquement et la détruit si les ressources relatives à cette destination sont détruites.
Le code ci-dessous spécifie deux Destination; une file d'attente de messages (queue) et un Topic (sujet); les noms des ressources sont mappées sur les ressources des Destinations créées dans le "JNDI namespace":
@Resource(lookup = "jms/MyQueue") private static Queue queue; @Resource(lookup = "jms/MyTopic") private static Topic topic;
Exemple:
voir l'exemple d'utilisation des ressources Destination et Connection.
L'objet Connection
Un objet javax.jms.Connection encapsule une connexion virtuelle avec un provider JMS. On l'utilise pour créer un ou plusieurs objets Session. Les connexions sont implémentées par l'interface javax.jms.Connection. On crée un objet Connection en se servant de l' interface ConnectionFactory:
@Resource(lookup = "jms/ConnectionFactory") private static ConnectionFactory connectionFactory; // ici on a l'objet ConnectionFactory
Connection connection = connectionFactory.createConnection(); /ensuite on crée l' objet Connection
Avant de terminer l' application, il faut toujours penser à clore la connexion; ainsi on libère toutes les ressources qui étaient utilisées par la connexion;
connection.close();
Session
Une session est un objet java qui implémente l'interface javax.jms.Session; elle fonctionne comme un "single-thread" Contexte et elle permet de créer des messages, des consommateurs ou des producteurs de messages. On crée un objet Session via un objet JMSContext; l'objet Session donne la possibilité de regrouper un ensemble d'opérations (envoi/réception et lecture de messages, ...) comme pour une opération transactionnelle.
L'interface javax.jms.Session dispose des méthodes comit() et rollback(). On peut utiliser ces méthodes dans n'importe quelle application Cliente JMS. Pour créer un objet javax.jms.Session on peut procéder comme suit:
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Ici on vient de créer un objet Session non transactionnelle; le premier argument de la méthode "connection.createSession" le précise; si on veut créer une session transactionnelle on donne au premier paramètre connection.createSession la valeur "true" comme suit:
Session session = connection.createSession(true, 0);
Quand on utilise l'objet Session pour traiter des messages sous forme transactionnelle tous les traitements sont faits ensemble ou pas (ce qui est logique).
Exemple: (à venir...)
Les objets JMSContext
Comme on l'a vu ci-dessus, un objet JMSContext permet de créer à la fois un objet javax.jms.Session et un objet javax.jms.Connection; cet objet JMSContext permet d'avoir un objet Connection actif associé à un "Provider JMS " et un objet context "single-thread-single" pour l'envoi et la réception de messages. On utilise l'objet JMSContext pour créer les objets suivants:
- MessageProducer,
- MessageConsumer,
- Message,
- QueueBrowser,
- Temporary queues and topics.
Pour créer un objet JMSContext on fait appel à la méthode createContext de l' objet ConnectionFactory :
JMSContext context = connectionFactory.createContext();
Quand elle est appelée ainsi sans argument à partir d'une application cliente Java EE ou d'une application cliente Java SE , ou à partir d' une application web Java EE ou un conteneur d'EJB, et quand durant cet appel il n'y a pas de transaction JTA en cours, la méthode "connectionFactory.createContext" crée une Session non transactionnelle avec comme paramètre "JMSContext.AUTO_ACKNOWLEDGE" (voir la signification des ses paramètres dans un autre cours). Quand elle est appelée sans argument à partir d'un conteneur web ou un conteneur EJB, et quand durant cet appel il y a une transaction JTA en cours, elle crée une Session transactionnelle .
A partir d'une application cliente Java EE ou d'une application cliente Java SE, la méthode "connectionFactory.createContext" peut être appelée avec l'argument "JMSContext.SESSION_TRANSACTED" pour créer une Session transactionnelle comme suit:
JMSContext context = connectionFactory.createContext(JMSContext.SESSION_TRANSACTED);
(voir l' utilisation l'utilisation de JMS pour les transactions local pour avoir des détails) .
On peut tout à fait utiliser JMContext en spécifiant d'autres modes de reconnaissance que celles définies par défaut. Quand on utilise un objet JMSContext, les messages sont délivrés aussitôt qu'un consommateur est disponible et son utilisation dans un "try-with-resources" block ne nécessite pas une fermeture explicite. Par contre si on n'utilise pas un "try-resources" block on doit fermer la session de façon explicite.
Exemple: (à venir)
...
Producteur de messages MessageProducer
Un producteur de messages est un objet créé par un objet JMSContext ou un objet Session et qui est utilisé pour envoyer des messages à une destination donnée. Un producteur de messages créé par JMSContext implémente l'interface JMSProducer; on procède en général de la manière suivante:
try (JMSContext context = connectionFactory.createContext();) {
JMSProducer producer = context.createProducer();
...
Pour éviter de multiplier les déclarations de variables pour se servir de JMSProducer (qui ne consomme pas beaucoup de ressources) on peut procéder, à chaque fois qu'on veut envoyer de message , de la façon suivante par exemple:
context.createProducer().send(dest, message);
On peut créer le message et le mettre auparavant dans une variable comme on l' a fait ci-dessus ou l'insérer directement comme paramètre de la méthode send().
Exemple: (à venir...)
...
Consommateur de messages MessageConsumer
Un MessageConsumer (consommateur de message) est un objet créé par un objet session et utilisé pour recevoir des messages envoyés à une destination; Cet objet MessageConsumer implémente l' interface MessageConsumer (classe et interface portent le même nom), permet à un client JMS de s'enregistrer à une Destination, gère les messages à délivrer aux consommateurs inscrits pour recevoir des messages de la "Destination". On peut aussi, comme on l'a dit ci-dessus, se servir d'un objet session pour créer un objet MessageConsumer pour un objet Destination (un objet Queue ou un objet Topic).
MessageConsumer consumer = session.createConsumer(dest); MessageConsumer consumer = session.createConsumer(queue); MessageConsumer consumer = session.createConsumer(topic);
On peut créer un producteur de messages avec JMSContext et celui-ci implémente à son tour l'interface JMSConsumer; la façon la plus simple de le faire est de se servir de la méthode JMSContext.createConsumer comme suit :
try (JMSContext context = connectionFactory.createContext();) { JMSConsumer consumer = context.createConsumer(dest);
...
...
JMS Message Listeners
Un MessageListener est un objet qui implémente l'interface MessageListener; cet objet qui a une seule méthode à savoir la méthode onMessage agit comme un "Listner d'événements" pour les messages ( voir EventListener) i.e qu' il agit sur les messages de façon asynchrone. Dans la méthode onMessage on définit ce qui doit être fait lorsque le message arrive à destination. Dans une application cliente Java EE ou un une application cliente Java SE, on enregistre le "message listener" avec un consommateur spécifique en se servant de la méthode setMessageListener; par exemple, si on définit une classe appelée Listener qui implémente l' interface MessageListener on peut procéder de la façon suivante:
Listener myListener = new Listener();
consumer.setMessageListener(myListener);
...
...
JMS Message Selectors
Si on développe une application qui doit filtrer les message dont elle a besoin, on se sert de " JMS Messages Selector";
Il est possible d’opérer un filtrage sur les messages à l’aide du selecteur de messages ( Messages selectors). Le consommateur de messages pourra spécifier au fournisseur JMS quels sont les messages pour lesquels il porte de l’intérêt. Un message selector est un objet String contenant une expression conditionnelle. Sa syntaxe est tirée de la norme SQL92. Ainsi, pour choisir tous les messages ayant leur propriété NewsType égale à Sport ou Opinion, un message selector aura la valeur suivante :
NewsType = 'Sports' OR NewsType = 'Opinion'
Un message selector peut être spécifié lors de la création du consommateur de messages avec les méthodes createConsummer() et createDurableSubscriber(). Le consommateur ainsi créé ne recevra que les messages qui correspondront au message selector spécifié. Les messages ne correspondant pas au message selector ne seront pas consommés.
...
Les Messages JMS
Si dans une application on utilise l' API JMS, c'est pour produire des messages qui seront utilisés par d'autres applications. Un message possède un format de base très simple et facilement utilisable pour toute application JMS.
En effet un message JMS est composé de trois grandes parties:
- l'en-tête,
- le "properties",
- le corps du message.
Seul l'en-tête dans un message est obligatoire; les autres parties sont optionnelles. Le corps représente le contenu du message lui-même. JMS définit des types particuliers de corps de message à instancier selon les cas; on a 5 types de message en tout et cela va du basic TextMessage au plus complet ObjectMessage.
L'en-tête d'un message
L’en-tête d'un message est la partie la plus importante; il contient des informations telles que l’ID de message, sa date d’expiration, ou sa priorité. L'en-tête d'un message est composé d'un certain nombre de champs dont certains sont optionnels....
JMSDestination : destination du message
JMSDeliveryMode : sûreté de distribution
NON_PERSISTENT : faible
PERSISTENT : garantie supplémentaire qu’un message ne soit pas perdu
JMSExpiration: calculé à partir du TTL (Time To Live) depuis la prise en charge
JMSMessageID : identifiant du Message founi par le provider
JMSTimestamp : date de prise en charge du message par le provider
JMSCorrelationID : identifiant d’un lien avec un autre Message (Request/Reply)
JMSReplyTo : identifiant de la Destination pour les réponses
JMSType : identifiant du type de message défini par l’émetteur
JMSRedelivered : n’a du sens que si le récepteur n’acquitte pas immédiatement la réception (Transaction)
JMSPriority : priorité (de 0 à 9) du message
Message properties
...
Message body
L' API JMS definit six types de messages différents. A chaque type de messages correspond un type de corps de messages et ainsi on peut envoyer et recevoir de différents formes de messages .
...
Controlling Message Acknowledgment
.......
Specifying Options for Sending Messages
......
Specifying Message Persistence
......
Setting Message Priority Levels
.......
Allowing Messages to Expire
.......
Specifying a Delivery Delay
....
Using JMSProducer Method Chaining
...
Creating Temporary Destinations
......
Using JMS Local Transactions
.....
Envoyer des messages de façon asynchrone