Développons en Java
v 2.40   Copyright (C) 1999-2023 .   
31. Le scripting 33. L'API Service Provider (SPI) Imprimer Index Index avec sommaire Télécharger le PDF

 

32. JMX (Java Management Extensions)

 

chapitre    3 2

 

Niveau : niveau 4 Supérieur 

 

JMX est l'acronyme de Java Management Extensions. Historiquement, cette API se nommait JMAPI (Java Management API). La version 5.0 de Java a ajouté l'API JMX 1.2 dans la bibliothèque de classes standard.

JMX est une spécification qui définit une architecture, une API et des services pour permettre de surveiller et de gérer des ressources en Java. JMX permet de mettre en place, en utilisant un standard, un système de surveillance et de gestion d'une application, d'un service ou d'une ressource sans avoir à fournir beaucoup d'efforts.

JMX permet de construire et de mettre en oeuvre une solution de gestion de ressources sous une forme modulaire grâce à des composants. Son but est de proposer un standard pour faciliter le développement de systèmes de contrôle, d'administration et de supervision des applications et des ressources.

JMX peut permettre de configurer, gérer et maintenir une application durant son exécution en fonction des fonctionnalités développées. Il peut aussi favoriser l'anticipation de certains problèmes par une information sur les événements critiques de l'application ou du système.

Java SE version 5.0 intègre JMX 1.2 et JMX Remote API 1.0. Il existe aussi une implémentation téléchargeable indépendamment pour J2SE 1.4

La plupart des principaux serveurs d'applications Java EE utilisent JMX pour la surveillance et la gestion de leurs composants.

Ce chapitre contient plusieurs sections :

 

32.1. La présentation de JMX

JMX est une spécification : pour la mettre oeuvre, il faut obligatoirement utiliser une implémentation : Sun propose une implémentation de référence, mais il est aussi possible d'utiliser JBossMX ou MX4J par exemple.

JMX propose une API standard qui permet de gérer, de contrôler et de surveiller des applications, des composants, des services et même la JVM ou des périphériques dans la plate-forme Java.

Ainsi les utilisations possibles de JMX sont nombreuses, par exemple :

JMX est architecturé en couches ce qui permet de séparer les responsabilités des différents composants utilisés. Ceci permet aussi d'assurer une meilleure extensibilité.

L'architecture de JMX est composée de trois niveaux :

L'architecture de JMX permet de faire de l'administration en locale ou à distance. JMX offre un accès distant pour permettre à une application de gestion d'interagir avec l'application instrumentée grâce à l'agent et aux MBeans.

JMX est spécifiée dans plusieurs JSR :

La version 1.1 des spécifications de JMX ne détaille que la partie Instrumentation et Agent.

Tout programme peut bénéficier de l'instrumentation par JMX, simplement en ajoutant du code aux classes existantes. Cette instrumentation concerne des propriétés, des opérations et des événements qui peuvent être exposés aux agents.

JMX est largement utilisée notamment dans les serveurs d'applications par exemple.

La plupart des applications et notamment celles exécutées côté serveur ont besoin d'être administrées pour :

L'avantage de JMX est de normaliser l'API de management et de proposer une exploitation de cette API grâce à des agents qui utilisent différents protocoles pour dialoguer avec les serveurs de MBeans.

Depuis l'intégration de JMX dans la version 1.5 de Java, l'utilisation de JMX par n'importe quelle application est possible en standard. Les fonctionnalités de base de JMX étant faciles à mettre oeuvre, il devient aisé de surveiller et administrer une application depuis un client JMX distant.

JMX est un framework à usage général pour la surveillance et l'administration de ressources ou d'applications : il ne propose par exemple aucune structure de données spécifique au monitoring ou à la gestion.

L'API JMX est regroupée dans le package javax.management et ses sous-packages :

JSR

Packages

JSR 003

javax.management
javax.management.loading
javax.management.modelmbean
javax.management.monitor
javax.management.openmbean
javax.management.relation
javax.management.timer

JSR 160

javax.management.remote
javax.management.remote.rmi

JSR 174

java.lang.management


La page web officielle relative à JMX est à l'url https://www.oracle.com/java/technologies/javase/javamanagement.html

 

32.2. L'architecture de JMX

L'architecture de JMX se compose de plusieurs niveaux :

L'architecture de l'API JMX est composée des trois premières parties. Les couches instrumentation et agent sont spécifiées par la JSR 003.

La couche Remote management ou distributed services est spécifiée par la JSR 160.

La technologie JMX propose une architecture en trois couches pour permettre à des applications de gérer des ressources :

L'élément principal du niveau agent est un objet de type « MBean Server » : son rôle est de gérer et de mettre en oeuvre les MBeans qui se sont enregistrés auprès de lui.

Le découpage de l'architecture de l'API en trois couches permet une meilleure répartition des rôles et réduit la complexité des fonctionnalités des différentes couches.

Chacune des trois couches propose des objets avec des interfaces bien définies.

L'élément principal du niveau instrumentation est un objet de type MBean.

La mise en oeuvre d'un MBean standard implique plusieurs étapes :

Cette architecture permet de rendre la façon dont une ressource est instrumentée indépendante de l'infrastructure de gestion utilisée. Cette indépendance est assurée par des connecteurs (connectors) qui permettent à une application de gestion de dialoguer avec un agent JMX. Ce connecteur peut utiliser différents protocoles.

Ceci permet d'intégrer de façon standard la surveillance et la gestion de ressources en Java avec des applications de monitoring et de gestion existantes pour peu que ces applications possèdent un connecteur respectant les spécifications JMX ou qu'il existe un adaptateur pour le protocole utilisé.

 

32.3. Un premier exemple

Ce premier exemple va utiliser Java SE 5.0 pour créer un MBean de type standard, instancier un serveur de MBean, enregistrer le MBean dans le serveur et interagir avec le MBean grâce à l'outil Jconsole du JDK.

 

32.3.1. La définition de l'interface et des classes du MBean

Il faut définir l'interface du MBean : son nom doit obligatoirement être composé du nom de sa classe suivi de MBean

Exemple :
package fr.jmdoudoux.dej.jmx;

public interface PremierMBean {
  
  public String getNom(); 

  public int getValeur(); 
  public void setValeur(int valeur); 

  public void rafraichir();
}

Il faut définir le MBean qui doit implémenter l'interface définie

Exemple :
package fr.jmdoudoux.dej.jmx;

public class Premier implements PremierMBean {

  private static String nom = "PremierMBean";
  private int valeur = 100;
  
  public String getNom() {
    return nom;
  }

  public int getValeur() {
    return valeur;
  }

  public synchronized void setValeur(int valeur) {
     this.valeur = valeur;
  }

  public void rafraichir() {
    System.out.println("Rafraichir les donnees");
  }

  public Premier() {
  }
}

Il faut définir une application qui va créer un serveur de MBeans, instancier le MBean et l'enregistrer dans le serveur.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.lang.management.ManagementFactory;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

public class LancerAgent {

  public static void main(String[] args) {
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      Premier mbean = new Premier();

      mbs.registerMBean(mbean, name);

      System.out.println("Lancement ...");
      while (true) {

        Thread.sleep(1000);
        mbean.setValeur(mbean.getValeur() + 1);
      }
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
    }
  }
}

 

32.3.2. L'exécution de l'application

Il faut compiler la classe et l'exécuter en demandant l'activation de l'accès distant aux fonctionnalités de JMX

Exemple :
java -Dcom.sun.management.jmxremote fr.jmdoudoux.dej.jmx.LancerAgent

Il est alors possible d'accéder à l'agent en utilisant par exemple l'outil JConsole fourni avec le JDK à partir de sa version 5.0 ou Visual VM fourni avec le JDK à partir de sa version 6.0

Sous Windows, il faut ouvrir une nouvelle boîte de commandes et lancer la commande jconsole du JDK.

Il faut sélectionner « Local Process » puis la JVM dans laquelle le serveur MBean est en cours d'exécution et cliquez sur le bouton « Connect »

JConsole tente de se connecter au processus de la JVM.

JConsole permet d'obtenir des informations sur la JVM et de gérer les MBeans.

Il faut sélectionner dans l'arborescence le MBean concerné.

La branche Attributes permet de voir les différents attributs.

La branche « Operations » permet d'invoquer les méthodes du MBean.

Il faut sélectionner la méthode et cliquer sur le bouton.

 

32.4. La couche instrumentation : les MBeans

La couche instrumentation assure l'instrumentation de ressources grâce aux MBeans.

L'instrumentation consiste à écrire des MBeans qui vont permettre d'instrumenter des ressources.

Pour être instrumentée, une ressource doit être une classe Java ou être encapsulée dans une classe Java. C'est notamment le cas si la ressource est un appareil. L'instrumentation se fait au moyen de la définition d'une interface qui décrit les fonctionnalités d'instrumentation proposées et une classe de type MBean qui implémente cette interface.

Rien n'est imposé quant à la granularité de la ressource à instrumenter : celle-ci peut aller d'une simple classe jusqu'à une application dans son intégralité.

L'instrumentation d'une ressource se fait grâce à un Managed Bean ou MBean. Il existe plusieurs types de MBean.

 

32.4.1. Les MBeans

Un MBean est l'élément de la spécification JMX le plus bas : son rôle est d'assurer la communication avec la ressource à gérer. MBean est l'abréviation de Managed Bean.

Un MBean a pour rôle de permettre la gestion et le dialogue avec une ressource. La nature d'une telle ressource peut être variée : application, composant, service, dispositif, appareil électronique, etc ...

Pour gérer des ressources grâce à JMX, il faut tout d'abord instrumenter la ressource en développant une classe qui sera le MBean.

Un MBean est une classe Java respectant les spécifications JMX qui implémente une interface particulière. Un MBean possède les caractéristiques suivantes :

Un MBean est accessible par son interface qui peut proposer :

Remarque : il n'est pas recommandé de surcharger des méthodes exposées par un MBean.

Un MBean est plus qu'une interface puisqu'il doit contenir le code permettant les interactions avec la classe ou l'appareil qu'il doit instrumenter et/ou surveiller.

En plus de l'instrumentation, un MBean peut émettre des notifications en réponse à des événements. Le modèle de notifications proposé par JMX pour les MBeans repose sur le modèle des événements Java. Ces notifications permettent aux MBeans et à l'agent qui les gère de notifier certains événements à un ou plusieurs abonnés.

 

32.4.2. Les différents types de MBeans

Les MBeans sont répartis en deux grandes familles : standard MBeans et dynamic MBeans.

Il existe quatre types de MBeans plus ou moins complexes à développer :

Les MBeans standard et dynamic publient tous les deux dynamiquement leur interface :

Les Dynamic MBeans publient les méthodes de l'interface du MBean en fournissant une description de chacune des méthodes exposées aux clients JMX.

Les Dynamic MBeans utilisent des classes qui encapsulent les métadonnées d'un MBean. Ces métadonnées permettent de décrire la structure et les fonctionnalités du MBean (constructeurs, attributs, opérations et notifications). Elles comprennent un nom, une description et des caractéristiques.

 

32.4.3. Les MBeans dans l'architecture JMX

Les composants MBeans sont gérés par un serveur de MBeans.

Chaque MBean enregistré dans le serveur de MBeans d'un agent JMX expose son interface aux applications de gestion. Un MBean est accédé de l'extérieure grâce à l'agent dans lequel il est enregistré.

Lorsque l'on crée un MBean, il n'est pas utile de se soucier du type d'agent ou d'application de gestion qui va solliciter le MBean : un MBean n'a pas besoin d'avoir de référence sur le serveur qui gère son cycle de vie.

 

32.4.4. Le nom des MBeans

Chaque MBean possède un ObjectName qui doit être choisi judicieusement car il permet de l'identifier. Un ObjectName est un objet de type javax.management.ObjectName.

Un ObjectName encapsule le nom d'un MBean ou un motif de recherche de noms de MBean.

Un ObjectName est composé de deux parties :

La syntaxe d'un ObjectName est de la forme [nomDomain]:propriete=valeur[,propriete=valeur]*

Le contenu d'un ObjectName devrait permettre de facilement déterminer le rôle du MBean.

Exemple :

fr.jmdoudoux.dej.jmx:type=MaRessourceBean,name=maRessource

Le domaine est une chaîne de caractères arbitraire facultative. Si le domaine est vide alors cela implique l'utilisation du domaine par défaut du serveur de MBeans dans lequel l'ObjectName est utilisé.

Il est recommandé de préfixer le nom de domaine par le nom du package Java pour éviter les collisions de noms entre MBeans.

Le nom de domaine ne doit pas contenir de caractères "/" qui est réservé pour les hiérarchies de serveurs de MBean. Le domaine ne peut pas contenir de caractère ":" puisqu'il est utilisé comme séparateur entre le domaine et les propriétés.

Si le domaine contient au moins un caractère "*" ou "?" alors l'ObjectName est un motif pour la recherche de MBeans. Ces deux caractères ne peuvent pas être utilisés dans le nom d'un MBean.

La liste de propriétés doit obligatoirement contenir au moins une propriété sous la forme clé=valeur. Chaque propriété de la liste est séparée par un caractère virgule. Le nom de la propriété doit respecter les conventions de nommage des entités Java.

Chaque ObjectName d'un même type devrait avoir le même ensemble de propriétés. Les valeurs de ces propriétés vont permettre de différencier plusieurs instances d'un même type. Parmi ces propriétés, il est fréquent de trouver la propriété name.

Hormis la clé type, toutes les autres clés peuvent être librement utilisées. Les clés sont généralement utilisées par les clients JMX pour afficher une représentation graphique hiérarchique du MBean.

La JSR 77 définit une propriété j2eeType qui possède un rôle similaire. Les propriétés type et j2eeType peuvent être utilisées simultanément.

Attention : les espaces contenus dans l'ObjectName sont tous significatifs et les ObjectNames sont sensibles à la casse.

L'ordre des propriétés n'est pas significatif.

La valeur d'une propriété peut être entourée de double quotes surtout si elle contient des caractères spéciaux comme le caractère virgule par exemple.

Exemple :

fr.jmdoudoux.dej.jmx:type=MaRessourceBean,name=maRessource, taille="200"

fr.jmdoudoux.dej.jmx:type=MaRessourceBean,name=maRessource, taille="200,250"

Le caractère * peut aussi être utilisé dans la liste de propriétés sur l'ObjectName : dans ce cas il concerne un motif de recherche.

Exemple :

fr.jmdoudoux.dej.jmx:type=MaRessourceBean,*

 

32.4.5. Les types de données dans les MBeans

Les MBeans peuvent être amenés à manipuler des types de données plus ou moins complexes pour différents besoins :

 

32.4.5.1. Les types de données complexes

Il est fréquent d'avoir besoin des types de données complexes qui encapsulent les informations d'une entité.

Il est parfois possible si ces données sont des attributs du MBeans de les séparer en différents attributs de types communs. Cependant ceci n'est pas possible pour différentes autres situations :

L'utilisation de ces types complexes va inévitablement poser des problèmes avec certains clients génériques comme l'outil JConsole ou l'adaptateur de protocole HTML car naturellement ces types de données ne sont pas connus.

JMX propose également au travers des spécifications des Open MBeans une solution pour utiliser des types complexes de façon portable.

 

32.5. Les MBeans standard

Ce sont les plus simples des MBeans. Il suffit de définir une interface dont le nom est composé du nom de la classe du MBean et du suffixe MBean.

Les fonctionnalités de base de JMX sont faciles à mettre en oeuvre dans un MBean standard puisqu'il suffit d'écrire une interface qui décrit les fonctionnalités du MBean et de fournir une implémentation dans une classe qui respecte les conventions Java Bean.

Dans un MBean standard, l'interface du MBean ne peut pas être modifiée à l'exécution : JMX propose d'autres types de MBeans pour répondre à ce besoin.

 

32.5.1. La définition de l'interface d'un MBean standard

Un MBean peut contenir des accesseurs (getters et/ou setters) pour des attributs et des méthodes qui pourront être invoquées pour réaliser des actions.

Ainsi un MBean peut proposer :

Dans un MBean standard, ces différents éléments sont définis de façon statique dans une interface. Cette interface peut donc contenir :

Exemple :
package fr.jmdoudoux.dej.jmx;

public interface PremierMBean {
  
  public String getNom(); 

  public int getValeur(); 
  public void setValeur(int valeur); 

  public void rafraichir();
}

Dans un MBean standard, tous les constructeurs publics sont exposés automatiquement même le constructeur par défaut si celui-ci est créé par le compilateur. Ainsi un client JMX peut instancier un MBean en utilisant un des constructeurs publics.

Les méthodes d'un MBean peuvent être des getters et setters sur des attributs ou des opérations qui permettront de réaliser certains traitements. Les méthodes de types getter et setter doivent respecter les conventions de nommage des Java beans. Il n'est pas possible de surcharger un getter ou un setter.

Si les conventions de nommage des Java beans ne sont pas respectées ou si une incohérence est détectée entre le type utilisé pour le getter et le setter, une exception sera levée lors de l'utilisation du MBean.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;

public interface SecondMBean {
  
  public int getValeur() throws IOException; 
  public void setValeur(String valeur) throws IOException; 

}

Lors d'une tentative d'enregistrement d'un MBean implémentant cette interface, une exception de type NotCompliantException est levée.

Exemple :
javax.management.NotCompliantMBeanException: Getter and setter for Valeur have inconsistent
types

Chaque méthode publique qui n'est pas identifiée comme étant un getter ou un setter d'une propriété est considérée comme une opération exposée par le MBean. Une opération peut avoir un nombre quelconque de paramètres et éventuellement avoir une valeur de retour.

Les spécifications JMX préconisent de ne pas surcharger les méthodes exposées des MBeans.

Une méthode d'un MBean peut lever des exceptions qui devront être gérées par le client JMX. Il est recommandé pour un MBean de ne lever que des exceptions fournies en standard par la plate-forme Java SE. Si un MBean lève une exception non standard, l'application qui utiliserait ce MBean lèvera une exception de type ClassNotFoundException.

C'est une best practice que chaque méthode déclarée dans l'interface d'un bean standard indique qu'elle peut lever l'exception java.io.IOException. Ceci impose la prise en compte de cette exception notamment lors de l'utilisation d'un proxy sinon une erreur de communication lèvera une exception de type UndeclaredThrowableException qui encapsulera l'exception de type IOException.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;

public interface PremierMBean {
  
  public String getNom() throws IOException; 

  public int getValeur() throws IOException; 
  public void setValeur(int valeur) throws IOException; 

  public void rafraichir() throws IOException;

}

Si le MBean n'est accédé que dans la JVM dans laquelle il s'exécute, il n'est pas utile de déclarer qu'une méthode peut lever une exception de type IOException. Mais généralement, l'accès aux MBeans se fait de façon distante. Pour simplifier l'utilisation locale, il est possible de définir une interface fille qui redéfinit les méthodes mais sans déclarer la levée de l'exception.

Exemple :
package fr.jmdoudoux.dej.jmx;

public interface PremierLocalMBean {
  
  public String getNom(); 

  public int getValeur(); 
  public void setValeur(int valeur); 

  public void rafraichir();
}

 

32.5.2. L'implémentation du MBean Standard

La classe du MBean doit implémenter l'interface du MBean. Le nom de la classe doit obligatoirement être identique à celui de l'interface sans le suffixe MBean. Si ce n'est pas le cas, une exception de type javax.management.NotCompliantMBeanException est levée.

Seules les propriétés et opérations définies dans l'interface seront utilisables par l'agent JMX. L'agent JMX va utiliser l'introspection pour déterminer la liste des propriétés et des opérations supportées par le MBean en recherchant l'interface du MBean.

Pour transformer une classe Xyz en MBean standard, il faut :

Afin d'assurer une séparation des rôles, il est préférable de placer dans deux classes distinctes la ressource gérée ou l'encapsulation de cette ressource et la classe du MBean qui va gérer la ressource. Il suffit pour cela que le MBean possède une référence sur la classe de la ressource, généralement passée dans le constructeur du MBean.

 

32.5.3. L'utilisation d'un MBean

Chaque MBean doit être enregistré dans un serveur de MBeans avec un identifiant unique sous la forme d'un nom d'objet (ObjectName).

Un ObjectName est composé d'un nom de domaine et d'attributs sous la forme de paires clé/valeur. La combinaison du nom de domaine et des attributs doit obligatoirement être unique dans un serveur de MBeans.

Les méthodes déclarées dans l'interface du MBean seront accessibles aux clients JMX.

Les constructeurs publics sont toujours accessibles aux clients JMX par introspection.

Les propriétés accessibles aux clients JMX sont exposées grâce aux getters et setters définis dans l'interface du MBean.

Il n'y a rien qui puisse empêcher l'utilisation des MBeans directement sans passer par JMX puisque ce sont de simples Java beans.

 

32.6. La couche agent

Si une ressource est instrumentée par un MBean, alors la gestion du MBean est assurée par un agent JMX : il assure donc la gestion et l'exploitation de différents MBeans.

Le niveau agent est essentiellement composé d'un agent JMX qui possède plusieurs responsabilités :

Le composant principal d'un agent JMX est un serveur de MBeans. Un serveur de MBeans enregistre et gère des MBeans. Généralement un agent JMX s'exécute dans la JVM où s'exécutent les MBeans qu'il gère mais ce n'est pas une obligation. L'agent permet à une application d'interagir avec les MBeans par son intermédiaire en utilisant des connecteurs ou des adaptateurs de protocoles.

Un serveur de MBeans est un registre pour MBeans : il gère le cycle de vie des MBeans qui s'enregistrent auprès de lui. Le serveur de MBeans donne à des applications tierces un accès aux MBeans en exposant leurs interfaces.

Les MBeans peuvent être instanciés et enregistrés dans le serveur de MBeans par un autre MBean ou par l'agent. Chaque MBean s'enregistre avec un identifiant unique de type ObjectName. Cet identifiant est fourni par le développeur, c'est donc lui qui doit être le garant de son unicité dans un même serveur de MBeans.

Un agent JMX permet donc à une application de gestion d'invoquer les fonctionnalités des MBeans : il assure la communication entre les MBeans et les interfaces de gestions grâce à plusieurs entités : un serveur de MBeans et un ou plusieurs adaptateurs de protocoles ou connecteurs.

Par défaut un agent ne possède pas de possibilités de communications. Les connecteurs et adaptateurs de protocoles permettent à un agent de communiquer en utilisant un protocole tel que HTTP, SNMP, ...

L'implémentation par défaut propose un adaptateur de protocole pour HTML, qui permet de disposer, pour n'importe quel agent JMX, d'une console d'administration accessible depuis un navigateur internet. C'est sur ce principe que fonctionne la console JMX fournie en standard avec JBoss par exemple.

Un agent JMX peut se voir ajouter des fonctionnalités dynamiquement sous la forme de services. Plusieurs services sont fournis en standard et il est possible de développer ses propres services. Généralement, ils sont fournis sous la forme de MBeans et sont donc administrables par JMX et le serveur de MBeans qui les gère.

 

32.6.1. Le rôle d'un agent JMX

Un agent JMX sert d'intermédiaire entre les MBeans et un client JMX (généralement une application de gestion) : il assure l'indépendance entre la ressource gérée et l'application de gestion distante.

Pour gérer le MBean et permettre son accès, il faut l'enregistrer dans un agent JMX. C'est le serveur de MBeans qui est le composant principal de l'agent et assure la gestion du cycle de vie des MBeans qui se sont enregistrés auprès de lui.

L'agent JMX va utiliser l'introspection sur l'interface pour déterminer les fonctionnalités offertes par un MBean standard.

La communication entre ce serveur et les applications clientes se fait grâce à des adaptateurs de protocoles ou des connecteurs qui assurent la communication de façon indépendante du MBean.

L'agent JMX propose aussi plusieurs services offrant différentes fonctionnalités.

Un agent JMX s'exécute généralement dans la JVM où sont exécutés les MBeans mais ce n'est pas une obligation.

 

32.6.2. Le serveur de MBeans (MBean Server)

Le MBean Server compose le coeur de l'agent : il gère les MBeans qui se sont enregistrés auprès de lui grâce à un identifiant unique (Object Name). Le serveur de MBeans est alors en charge de la gestion de ces MBeans.

Ce serveur permet de gérer le cycle de vie des MBeans (ajout, modification ou suppression) et de permettre leur utilisation de manière locale ou distante. C'est le coeur du système de gestion proposé par JMX.

Un serveur de MBeans possède plusieurs fonctionnalités notamment :

 

32.6.3. Le Mbean de type MBeanServerDelegate

Lorsqu'un serveur de Mbeans est instancié, un MBean de type MBeanServerDelegate est automatiquement instancié et enregistré dans le serveur avec un ObjectName qui vaut « JMImplementation:type=MBeanServerDelegate ».

Cet MBean permet d'obtenir des informations sur le serveur MBean sous la forme de plusieurs attributs en lecture seule : MBeanServerId, SpecificationName, SpecificationVersion, SpecificationVendor, ImplementationName, ImplementationVersion et ImplementationVendor.

Le plus utile de ces attributs est MBeanServerId puisqu'il fournit l'identifiant du serveur de MBeans.

C'est aussi lui qui est responsable des notifications de type jmx.mbean.created et jmx.mbean.deleted émises par le serveur de MBeans notamment lors de l'enregistrement ou la suppression de MBeans du serveur.

 

32.6.3.1. L'enregistrement d'un MBean dans le serveur de MBeans

Chaque MBean doit être associé à une instance d'un objet de type ObjectName lors de l'enregistrement dans le serveur de MBeans. Un objet de type ObjectName sert d'identifiant unique et doit respecter les spécifications JMX :

Pour utiliser un MBean, il faut donc l'enregistrer dans le serveur de MBeans d'un agent JMX. Le plus simple pour réaliser cette opération est de suivre deux étapes :

 

32.6.3.2. L'interface MBeanRegistration

L'interface javax.management.MBeanRegistration peut être implémentée par un MBean pour définir des callbacks qui seront invoqués par le serveur de MBeans durant le cycle de vie du MBean. Ces callbacks fournissent un mécanisme de contrôle sur le processus d'enregistrement et de désenregistrement du MBean.

Elle définit quatre méthodes :

Méthode

Rôle

ObjectName preRegister(MBeanServer mbs, ObjectName name)

Invoquée juste avant que le MBean ne soit enregistré dans le serveur de MBeans

void postRegister()

Invoquée juste après que le MBean soit correctement enregistré dans le serveur de MBeans

void preDeRegister()

Invoquée juste avant que le MBean ne soit désenregistré du serveur de MBeans

void postDeRegister()

Invoquée juste après que le MBean soit correctement désenregistré du serveur de MBeans

 

32.6.3.3. La suppression d'un MBean du serveur de MBeans

La méthode unregisterMBean() permet de supprimer un MBean du serveur de MBeans : elle attend en paramètre l'identifiant du Mbean sous la forme de son ObjectName

Une fois cette méthode invoquée, le serveur ne possède plus de référence sur l'instance du MBean.

Remarque : la destruction d'un serveur de MBeans entraîne la destruction des MBeans qu'il contenait.

 

32.6.4. La communication avec la couche agent

Les agents ne communiquent pas directement avec la couche services distribués : cette communication est assurée par des connecteurs et/ou des adaptateurs de protocoles. Ceci permet d'utiliser plusieurs protocoles comme HTTP ou SNMP.

Les adaptateurs de protocoles permettent d'accéder à un agent JMX en utilisant un protocole donné : ils adaptent les échanges avec l'agent en utilisant le protocole pour lequel ils ont été écrits.

Généralement les adaptateurs de protocoles ont uniquement une partie serveur qui se charge d'adapter les échanges au format du protocole utilisé.

Un connecteur implique obligatoirement une partie côté client et une partie sur l'agent : il permet un échange entre un client JMX et un agent JMX en utilisant un protocole particulier. Ces échanges sont assurés par une partie du connecteur côté client et une autre côté serveur.

L'implémentation de référence propose un adaptateur de protocole pour HTML qui permet d'avoir un accès à un agent JMX avec un simple navigateur (attention : ce connecteur n'est pas fournie avec le JDK).

La mise en oeuvre des connecteurs et des adaptateurs de protocoles est détaillée dans une des sections suivantes.

 

32.6.5. Le développement d'un agent JMX

Le développement d'un agent comporte plusieurs étapes :

Remarque : généralement les services, les connecteurs et les adaptateurs de protocoles sont implémentés sous la forme de MBeans qu'il est possible d'enregistrer dans le serveur de MBeans pour permettre leur administration.

 

32.6.5.1. L'instanciation d'un serveur de MBeans

Pour instancier un serveur de MBeans, il faut utiliser directement ou indirectement une fabrique de type MBeanServerFactory. Cette fabrique ne possède aucune instance car toutes les méthodes qu'elle propose sont statiques.

La fabrique peut conserver en interne des références sur les instances de type MBeanServer qu'elle crée.

Elle propose plusieurs méthodes pour obtenir ou manipuler une instance de type MBeanServer notamment :

Méthode

Rôle

MBeanServer createMBeanServer()

Renvoyer une nouvelle instance d'un objet qui implémente l'interface MBeanServer associé au nom de domaine par défaut (DefaultDomain).

MBeanServer createMBeanServer(String domain)

Renvoyer une nouvelle instance d'un objet qui implémente l'interface MBeanServer associé au nom de domaine fourni en paramètre.

ArrayList<MBeanServer> findMBeanServer(String agentId)

Retourner une collection des instances de MBeanServer créées avec la méthode createMBeanServer() et toujours présentes dans la fabrique. Toutes les instances sont retournées avec null en paramètres.

MBeanServer newMBeanServer()

Renvoyer une nouvelle instance d'un objet qui implémente l'interface MBeanServer associé au nom de domaine par défaut (DefaultDomain) sans conserver de référence sur l'instance

MBeanServer newMBeanServer(String domain)

Renvoyer une nouvelle instance d'un objet qui implémente l'interface MBeanServer associé au nom de domaine fourni en paramètre sans conserver de référence sur l'instance

void releaseMBeanServer(MBeanServer mbeanServer)

Supprimer les références au MBeanServer dans la fabrique pour permettre au ramasse-miettes de libérer l'espace mémoire de l'instance


Le paramètre domain attendu par certaines de ces méthodes permet de préciser le domaine utilisé par le serveur de MBeans. Le domaine par défaut est DefaultDomain.

Le plus simple pour obtenir une instance d'un serveur de MBeans est d'invoquer la méthode createMBeanServer() de la classe MBeanServerFactory.

Exemple :
...
    MBeanServer mbs = MBeanServerFactory.createMBeanServer();
...

La fabrique fait appel à un objet de type MBeanServerBuilder dont une implémentation par défaut est fournie.

Depuis la version 1.2 de JMX, il est possible de remplacer l'implémentation par défaut de la classe MBeanServer en définissant une classe qui héritent de la classe MBeanServerBuilder et qui possède un constructeur par défaut. Il suffit alors de passer le nom pleinement qualifié de cette classe comme valeur de la propriété javax.management.builder.initial de la JVM. Cette fonctionnalité est cependant à réserver pour des besoins très particuliers.

Il est aussi possible d'obtenir une instance de type MBeanServer en utilisant la méthode getPlatformMBeanServer() de la fabrique java.lang.management.ManagementFactory. Cette fabrique permet d'obtenir des instances des MBeans de la JVM et une instance du MBeanServer par défaut de la JVM.

Exemple :
...
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
...

La classe MBeanServerFactory possède la méthode findMBeanServer(String) qui permet de rechercher une ou plusieurs instances de serveurs de MBeans. Elle retourne une collection qui contient les instances de serveurs de MBeans qui correspondent à l'identifiant fourni en paramètre ou à tous les serveurs de MBeans si le paramètre fourni est null.

 

32.6.5.2. L'instanciation et l'enregistrement d'un MBean dans le serveur

L'interface MBeanServer propose deux méthodes pour enregistrer un MBean :

Pour utiliser la méthode registerMbean(), il faut créer une instance de type ObjectName qui va encapsuler le nom unique du MBean dans le serveur.

Il faut ensuite créer une instance du MBean et enregistrer le MBean dans le serveur en utilisant la méthode registrerMBean() de l'interface MBeanServer qui attend en paramètre l'instance du MBean et son ObjectName.

Exemple :
...
    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");
      Premier mbean = new Premier();
      mbs.registerMBean(mbean, name);

    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } 

...

La méthode createMBean() possède plusieurs surcharges : elles permettent toutes avec différents paramètres d'instancier dynamiquement un MBean en utilisant l'introspection. Ces paramètres contiennent toujours le nom de la classe de type String et l'ObjectName du MBean. Les autres paramètres permettent de préciser le classloader à utiliser et les paramètres à fournir lors de l'invocation du constructeur du MBean.

Il est possible d'avoir plusieurs instances d'un même MBean dans un serveur de Mbeans en leur affectant à chacune un ObjectName distinct.

 

32.6.5.3. L'ajout d'un connecteur ou d'un adaptateur de protocoles

Pour pouvoir être utilisé par un client JMX, l'agent doit avoir au moins un connecteur ou un adaptateur de protocoles.

Généralement l'un ou l'autre sont fournis sous la forme d'un MBean qu'il convient d'instancier et d'enregistrer auprès du serveur de MBeans.

Il faut enfin les démarrer pour qu'ils commencent à écouter les appels des clients, le plus souvent en invoquant leur méthode start().

Le détail de la mise en oeuvre d'un connecteur et d'un adaptateur de protocole est proposé dans une prochaine section.

 

32.6.5.4. L'utilisation d'un service de l'agent

Le détail de la mise en oeuvre des services offerts par un agent est proposé dans la prochaine section.

 

32.7. Les services d'un agent JMX

Un agent JMX propose plusieurs services définis dans la version 1.1 des spécifications JMX visant à rendre la solution de gestion plus riche en fonctionnalités avancées :

Ces services peuvent être implémentés sous la forme de MBeans ce qui leur permet d'être utilisés par les autres MBeans et d'être administrables.

 

32.7.1. Le service de type M-Let

M-Let est l'abréviation de management applet. Le service de type M-Let permet de charger un MBean local ou distant, de l'instancier et de l'enregistrer dans le serveur de MBeans. La description des MBeans à traiter est contenue dans un fichier texte possédant une syntaxe dédiée. Le fichier est fourni au service grâce à une url pointant sur un fichier local ou distant qui contient la définition des MBeans.

Le fichier doit être un fichier texte dans lequel chaque MBean doit être défini avec un tag <MLET>. Ce tag possède plusieurs attributs qui permettent de fournir les informations concernant le MBean.

Le service M-Let est enregistré en tant que MBean dans le serveur de MBeans. Le service M-Let lit le fichier de description précisé par une url. Chaque MBean décrit dans le fichier est traité par le service :

Ce service permet de créer dynamiquement des agents extensibles.

 

32.7.1.1. Le format du fichier de définitions

Chaque MBean devant être traité par le service M-Let doit avoir une définition dans le fichier sous la forme d'un tag <MLET>

Le format du tag MLET est le suivant :

<MLET
CODE = class | OBJECT = serfile ARCHIVE = "archiveList" [CODEBASE = codebaseURL] [NAME = mbeanname] [VERSION = version] > [arglist] </MLET>

Les attributs du tags MLET sont :

Attribut

Rôle

CODE

Préciser le nom pleinement qualifié de la classe du MBean. La classe doit être présente dans un des fichiers jar indiqués par l'attribut ARCHIVE

OBJECT

Préciser un fichier .ser qui contient le résultat de la sérialisation de l'instance du MBean. Ce fichier doit être présent dans un des fichiers jar indiqués par l'attribut ARCHIVE

ARCHIVE

Préciser un ou plusieurs fichiers jar contenant le ou les MBeans et leurs dépendances. Si plusieurs sont précisés, la valeur de l'attribut doit être entourée de double quotes et chaque jar doit être séparés avec un caractère virgule. Tous les jars utilisés doivent être stockés dans le répertoire précisé par l'attribut CODEBASE . (obligatoire)

CODEBASE

Préciser le répertoire dans lequel les jars sont stockées. L'utilisation de cet attribut n'est obligatoire que si les jars ne sont pas stockés dans le même répertoire que le fichier de description

NAME

Préciser l'ObjectName du MBean lors de son enregistrement dans le serveur de MBeans. Si la valeur de l'attribut commence par un caractère « : » alors l'ObjectName sera préfixé par le nom de domaine par défaut du serveur de MBeans

VERSION

Préciser le numéro de version du MBean et du jar qui le contient


Deux attributs sont obligatoires :

Le tag MLET possède le tag fils <ARG> qui permet de préciser des arguments qui seront passés au constructeur du MBean lors de son instanciation.

Le tag <ARG> possède deux attributs :

Attribut

Rôle

TYPE

Préciser le type de l'argument. Seuls quelques types possédant une représentation sous la forme d'une chaîne de caractères peuvent être utilisés : java.lang.Boolean, java.lang.Byte, java.lang.Short, java.lang.Long, java.lang.Integer, java.lang.Float, java.lang.Double, java.lang.String

VALUE

Préciser la valeur de l'argument sous la forme d'une chaîne de caractères


Lors de l'instanciation du MBean, le service M-Let va rechercher un constructeur du MBean dont la signature corresponde aux arguments précisés par le tag <ARG>.

Le fichier peut contenir plusieurs tags <MLET>, un pour chaque MBean qui devra être instancié et enregistré dans le serveur de MBeans.

 

32.7.1.2. L'instanciation et l'utilisation d'un service M-Let dans un agent

La classe javax.management.loading.MLet est une implémentation du service M-Let fournie en standard. L'implémentation d'un service M-Let doit implémenter l'interface javax.management.loading.MLetMBean.

La classe MLet hérite de la classe URLClassLoader ce qui lui permet de télécharger des classes à travers le réseau.

C'est un MBean qui doit être instancié et enregistré dans le serveur de MBean : il est ainsi possible d'utiliser ce MBean à distance en passant par l'agent JMX.

La classe MLet propose la méthode getMbeansFromURL() qui attend en paramètre l'url du fichier de description et qui permet de lire le fichier et de traiter les MBeans qu'il contient. Deux surcharges permettent de préciser l'url sous la forme d'une chaîne de caractères ou d'un objet de type java.net.URL.

 

32.7.1.3. Un exemple de mise en oeuvre du service M-Let

Il faut développer un agent qui va instancier et enregistrer un objet de type MLet.

L'instance de cet objet va lire un fichier de description qui va permettre d'instancier et d'enregistrer un MBean dans le serveur de MBeans.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Set;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.ServiceNotFoundException;
import javax.management.loading.MLet;

public class LancerAgentAvecMLet {

  public static void main(String[] args) {
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      // Instanciation et enregistrement du Service MLet
      System.out.println("Instanciation et enregistrement du Service MLet");
      MLet mlet = new MLet();
      mlet.setLibraryDirectory("c:/temp");
      mbs.registerMBean(mlet, new ObjectName("Services:type=MLet"));

      // Lecture du fichier de configuration pour instanciation et
      // enregistrement du MBean
      System.out.println("\nLecture du fichier de configuration");
      Set<Object> mbeans = mlet.getMBeansFromURL(new URL(
          "http://localhost:8080/jmx/mlet.txt"));
      for (Object obj : mbeans) {
        System.out.println("Object = " + obj);
      }

      System.out.println("\nClasspath du service MLet : "
          + Arrays.asList(mlet.getURLs()));

      System.out.println("\nRecherche du mbean enregistré");
      Set<ObjectName> names = mbs.queryNames(name, null);
      for (ObjectName objName : names) {
        System.out.println("ObjectName=" + objName);
      }

      System.out.println("\nExecution de l'agent ...");
      while (true) {

        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        int valeur = Integer.valueOf(mbs.getAttribute(name, "Valeur")
            .toString());
        Attribute attr = new Attribute("Valeur", valeur + 1);
        mbs.setAttribute(name, attr);
      }
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (NumberFormatException e) {
      e.printStackTrace();
    } catch (AttributeNotFoundException e) {
      e.printStackTrace();
    } catch (InstanceNotFoundException e) {
      e.printStackTrace();
    } catch (MBeanException e) {
      e.printStackTrace();
    } catch (ReflectionException e) {
      e.printStackTrace();
    } catch (InvalidAttributeValueException e) {
      e.printStackTrace();
    } catch (ServiceNotFoundException e) {
      e.printStackTrace();
    }
  }
}

Il faut rédiger le fichier de description.

Exemple :
<MLET CODE=fr.jmdoudoux.dej.jmx.Premier 
 ARCHIVE="TestJMX.jar" 
 CODEBASE=http://localhost:8080/jmx/ 
 NAME=fr.jmdoudoux.dej.jmx:type=PremierMBean></MLET>

Il faut un serveur web sur lequel on place :

Dans l'exemple de cette section, c'est une simple webapp déployée dans un serveur Tomcat qui contient à sa racine les deux fichiers. Il faut exécuter l'agent.

Résultat :
Instanciation et enregistrement du Service MLet

Lecture du fichier de configuration
Object = fr.jmdoudoux.dej.jmx.Premier[fr.jmdoudoux.dej.jmx:type=PremierMBean]

Classpath du service MLet : [http://localhost:8080/jmx/TestJMX.jar]

Recherche du mbean enregistré
ObjectName=fr.jmdoudoux.dej.jmx:type=PremierMBean

Execution de l'agent ...

Il est possible de consulter le MBean enregistré dans l'agent par exemple avec JConsole.

 

32.7.2. Le service de type Timer

Le service Timer permet d'envoyer des notifications prédéfinies à un moment donné ou périodiquement. Ces notifications sont envoyées à tous les objets qui se sont abonnés pour recevoir les notifications émises par le service.

Les caractéristiques de l'émission d'une notification sont assez souples : elle démarre à une certaine date/heure et est répétée à intervalles de temps réguliers durant une période ou pour un certain nombre d'occurrences.

Le service Timer est implémenté sous la forme d'un MBean ce qui permet de l'administrer au travers de JMX lui-même.

Les notifications émises par le service Timer sont de type TimerNotification.

L'implémentation du service Timer est encapsulée dans la classe javax.management.timer.Timer

 

32.7.2.1. Les fonctionnalités du service Timer

Le service Timer est implémenté sous la forme d'un MBean. Pour l'utiliser, il faut l'instancier et l'enregistrer dans le serveur de MBeans.

Pour activer le service, il faut utiliser sa méthode start(). Pour le désactiver, il faut utiliser la méthode stop(). Avant l'appel à la méthode start() et après la méthode stop() aucune notification n'est émise même si les conditions d'une émission sont remplies. Si le service est redémarré et que la méthode setSendPastNotifications() a été invoquée avec le paramètre true alors les notifications ratées seront émises.

La méthode isActive() permet de savoir si le service est actif ou non.

Pour s'abonner aux notifications, un client ou une classe doivent s'enregistrer en tant que listener sur le MBean du service Timer. Chaque fois qu'une condition est remplie, une notification est envoyée à tous les abonnés.

Dès que les conditions d'une notification ne peuvent plus être remplies (par exemple si le nombre d'occurrences est atteint), la définition de la notification est supprimée automatiquement de la liste maintenue dans le service.

Il est possible de supprimer la définition d'une notification en utilisant la méthode removeNotification() qui attend l'identifiant de cette définition. Il est aussi possible de supprimer plusieurs définitions en utilisant la méthode removeNotifications() qui attend en paramètre leur type : dans ce cas, elle va supprimer toutes les définitions de notifications qui ont le type fourni en paramètre.

La méthode removeAllNotifications() permet de supprimer toutes les notifications contenues dans le service.

 

32.7.2.2. L'ajout d'une définition de notifications

Le service Timer maintient une liste des définitions de notifications qu'il aura à traiter. La méthode addNotification() permet d'ajouter une définition de notifications. Cette méthode possède plusieurs surcharges qui ont toutes quatre paramètres en commun :

Plusieurs surcharges attendent en paramètres un ou plusieurs des paramètres ci-dessous :

La méthode addNotification() peut lever une exception de type IllegalArgumentException si une ou plusieurs valeurs de ses paramètres est invalide, par exemple :

La méthode addNotification() renvoie un identifiant de la définition des notifications qui peut être utile notamment pour supprimer la définition.

Remarque : il n'est pas possible de modifier les paramètres d'une définition de notifications.

 

32.7.2.3. Un exemple de mise en oeuvre du service Timer

Dans l'exemple de cette section, une notification sera émise toutes les 5 secondes pour une durée infinie.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.util.Date;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

public class LancerAgentAvecTimer {

  public static void main(String[] args) {
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    try {

      // instanciation et enregistrement du service Timer
      javax.management.timer.Timer timer = new javax.management.timer.Timer();
      mbs.registerMBean(timer, new ObjectName("Services:type=Timer"));

      // ajout de la définition des notifications
      timer.addNotification("Register", "Test du service timer", new String(),
          new Date(), 5000, 0);
      timer.setSendPastNotifications(false);
      timer.start();

      // Creation et démarrage du connecteur RMI
      JMXServiceURL url = new JMXServiceURL(
          "service:jmx:rmi:///jndi/rmi://localhost:9000/server");
      JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
          url, null, mbs);
      cs.start();
      System.out.println("Lancement connecteur RMI " + url);

      while (true) {
        Thread.sleep(1000);
      }

    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}


Pour voir les notifications dans JConsole, il faut sélectionner « Notitifications » pour le MBean du service Timer et cliquer sur le bouton « Subscribe ».

 

32.7.3. Le service de type Monitor

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

 

32.7.4. Le service de type Relation

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

 

32.8. La couche services distribués

Il existe de nombreuses applications de gestion et de monitoring qui utilisent des protocoles standard comme SNMP (Simple Network Management Protocol) ou propriétaires.

Une application de gestion permet à des utilisateurs d'interagir avec les MBeans en communiquant avec le serveur de MBeans ou un service de l'agent JMX. Une application web utilisant un adaptateur de protocole pour HTML ou une application utilisant un adaptateur de protocole pour SNMP sont des exemples d'applications de gestion. Ces applications peuvent ne pas implémenter l'API JMX et dans ce cas elles communiquent avec l'agent JMX grâce à un protocole dédié.

La couche services distribuées fournit des interfaces et des composants pour permettre à des outils distants de communiquer avec l'agent JMX.

La couche services distribuées contient une application de gestion qui va interagir avec l'agent JMX. Les clients JMX distants s'exécutent dans une JVM différente de celle du serveur de MBeans et utilisent un protocole pour communiquer avec ce dernier.

Un client JMX distant ne peut pas utiliser le serveur de MBeans directement. Les composants de cette couche permettent d'accéder à un serveur de MBeans au travers de différents protocoles.

La manière dont un agent JMX est accédé au travers du réseaux est spécifié par la JSR 160 (JMX Remoting) qui est une fonctionnalité de la version 1.2 de JMX.

 

32.8.1. L'interface MBeanServerConnection

Un client JMX distant manipulent des MBeans en utilisant un objet qui implémente l'interface MBeanServerConnection. Une instance de l'interface MBeanServerConnection permet de se connecter à un serveur de MBeans local ou distant et d'interagir avec lui.

Pour utiliser un MBean local, il est possible d'utiliser directement le serveur de MBeans. Pour accéder à un MBean distant, il faut obligatoirement utiliser une instance de l'interface MBeanServerConnection.

Le client utilise les méthodes de l'interface MBeanServerConnection pour accéder aux fonctionnalités exposées par un MBean :

 

32.8.2. Les connecteurs et les adaptateurs de protocoles

Pour permettre la communication entre un agent et un client JMX, JMX propose des adaptateurs de protocoles ou des connecteurs qui se chargent de la communication entre l'application de gestion et l'agent JMX avec un protocole particulier.

La communication entre un agent JMX et une application de gestion peut donc être assurée par deux mécanismes :

Les connecteurs et les adaptateurs de protocoles permettent l'accès, par une application distante, aux MBeans enregistrés dans un agent JMX. Ils permettent un accès aux services de l'agent et aux MBeans enregistrés dans celui-ci.

Ainsi pour être utilisable, un agent JMX doit fournir ou moins un connecteur ou un adaptateur de protocole. La plate-forme Java SE fournit en standard un connecteur reposant sur RMI.

Un agent peut être accédé par plusieurs connecteurs ou adaptateurs de protocoles simultanément.

 

32.8.2.1. Les connecteurs

Un connecteur permet le dialogue entre l'agent et l'application de gestion distante grâce à un protocole dédié. Un connecteur est composé d'une partie cliente liée à l'application de gestion et d'une partie serveur liée à l'agent JMX. La partie serveur du connecteur attend les connexions de la partie cliente : c'est donc la partie cliente qui est responsable de l'initialisation de la connexion.

Un connecteur permet donc à un client JMX distant de communiquer avec un agent JMX. L'agent JMX est chargé d'initialiser et de configurer le connecteur côté serveur. Le client JMX est chargé d'initialiser et de configurer le connecteur côté client.

Un connecteur permet d'obtenir une instance de l'interface MBeanServerConnection.

Les spécifications de l'API JMX Remote définissent trois catégories de connecteurs :

Le connecteur RMI peut utiliser les deux standards de transport de RMI :

 

32.8.2.2. Les adaptateurs de protocoles

Les adaptateurs de protocoles permettent un accès à un agent JMX au travers d'un protocole particulier. Celui-ci peut par exemple être un protocole propriétaire utilisé par une application de gestion commerciale.

Un adaptateur de protocole permet à une application qui n'utilise pas JMX de communiquer avec un agent JMX sans que la partie cliente n'utilise aucune API de JMX.

Par exemple, l'adaptateur HTML fourni avec l'implémentation de référence de JMX permet d'interagir avec les MBeans d'un agent JMX utilisant cet adaptateur avec un simple navigateur web.

 

32.8.3. L'utilisation du connecteur RMI

Pour utiliser le connecteur RMI, l'agent JMX doit créer un connecteur RMI et le lancer. Le connecteur est accessible par une url encapsulée dans la classe JMXServiceURL.

La partie serveur d'un connecteur est encapsulée dans la classe JMXConnectorServer. Une instance de la classe JMXConnectorServer est obtenue en utilisant la méthode newJMXConnectorServer() de la fabrique JMXConnectorServerFactory.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

public class LancerAgentAvecRMI {

  public static void main(String[] args) {

    System.out.println("Lancement de l'agent JMX");
    
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = null;
    try {
      System.out.println("Instanciation et enregistrement du MBean");

      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      Premier mbean = new Premier();

      mbs.registerMBean(mbean, name);

      // Creation et demarrage du connecteur RMI
      JMXServiceURL url = new JMXServiceURL(
          "service:jmx:rmi:///jndi/rmi://localhost:9000/server");
      JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
          url, null, mbs);
      cs.start();
      System.out.println("Lancement connecteur RMI "+url);

      int i = 0;
      System.out.println("Incrementation de la valeur du MBean ...");
      while (i < 60) {

        mbean.setValeur(mbean.getValeur() + 1);
        Thread.sleep(1000);
        i++;        
      }

      System.out.println("Arret connecteur RMI ");
      cs.stop();
      
      System.out.println("Arret de l'agent JMX");
      
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

L'appel à la méthode stop() est important car il permet notamment de supprimer le connecteur enregistré dans le registre RMI.

Un exception de type javax.naming.NameAlreadyBoundException est levée si l'agent est lancé et que le connecteur est déjà enregistré dans le registre RMI. Dans ce cas, il faut redémarrer le registre.

Exemple :
C:\Users\Jean Michel\workspace\TestJMX\bin>java fr.jmdoudoux.dej.jmx.LancerAg
entAvecRMI
Lancement de l'agent JMX
Instanciation et enregistrement du MBean
Lancement connecteur RMI service:jmx:rmi:///jndi/rmi://localhost:9000/server
Incrementation de la valeur du MBean ...

C:\Users\Jean Michel\workspace\TestJMX\bin>java fr.jmdoudoux.dej.jmx.LancerAg
entAvecRMI
Lancement de l'agent JMX
Instanciation et enregistrement du MBean
java.io.IOException: Cannot bind to URL [rmi://localhost:9000/server]: javax.nam
ing.NameAlreadyBoundException: server [Root exception is java.rmi.AlreadyBoundEx
ception: server]
        at javax.management.remote.rmi.RMIConnectorServer.newIOException(RMIConn
ectorServer.java:804)
        at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServ
er.java:417)
        at fr.jmdoudoux.dej.jmx.LancerAgentAvecRMI.main(LancerAgentAvecRMI.ja
va:40)
Caused by: javax.naming.NameAlreadyBoundException: server [Root exception is jav
a.rmi.AlreadyBoundException: server]
        at com.sun.jndi.rmi.registry.RegistryContext.bind(RegistryContext.java:1
22)
        at com.sun.jndi.toolkit.url.GenericURLContext.bind(GenericURLContext.jav
a:208)
        at javax.naming.InitialContext.bind(InitialContext.java:400)
        at javax.management.remote.rmi.RMIConnectorServer.bind(RMIConnectorServe

Pour utiliser un connecteur RMI, il faut obligatoirement lancer un registre RMI

Exemple :
C:\>rmiregistry 9000

Le paramètre précisé à la commande rmiregistry est le port utilisé pour les communications.

Le client JMX doit obtenir une instance du type MBeanServerConnection qui va lui permettre de se connecter sur le serveur de MBeans de l'agent JMX. Une telle instance est obtenue en utilisant un objet de type JMXConnector fourni par l'invocation de la méthode connect() de la fabrique JMXConnectorFactory. La méthode connect() attend en premier paramètre l'url de connexion sur le serveur de MBeans de l'agent JMX.

La méthode getMBeanServerConnection() de l'instance de type JMXConnector renvoie un objet de type MBeanServerConnection qui permet d'interagir avec le serveur de MBeans.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;
import java.net.MalformedURLException;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class ClientJMXAvecRMI {
  public static void main(String[] args) {
    MBeanServerConnection mbsc = null;
    JMXConnector connecteur = null;

    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      JMXServiceURL url = new JMXServiceURL(
          "service:jmx:rmi:///jndi/rmi://localhost:9000/server");

      connecteur = JMXConnectorFactory.connect(url, null);

      mbsc = connecteur.getMBeanServerConnection();

      PremierMBean mbean = (PremierMBean) MBeanServerInvocationHandler
          .newProxyInstance(mbsc, name, PremierMBean.class, false);
      int valeur = mbean.getValeur();
      System.out.println("valeur = " + valeur);
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (mbsc != null) {
        try {
          connecteur.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }
}

Il est aussi possible d'utiliser un autre client JMX, par exemple l'outil JConsole

L'onglet MBean permet de retrouver le MBean enregistré dans le serveur de MBeans et d'interagir avec lui.

 

32.8.4. L'utilisation du connecteur utilisant le protocole JMXMP

Un connecteur générique utilise le protocole JMXMP qui repose sur des sockets avec le protocole TCP pour les communications, la sérialisation pour l'échange des objets et les API standard de Java pour la sécurité notamment JSSE et JASS.

L'utilisation du connecteur JMXMP est simple. Pour utiliser ce protocole, il faut une implémentation de ce protocole par exemple celle fournie avec l'implémentation de référence de la JSR 160. Il suffit alors d'ajouter la bibliothèque jmxremote_optional.jar dans le classpath.

Le protocole JMXMP permet d'échanger des objets Java sérialisés par une connexion TCP. La communication entre le client et le serveur de MBeans n'a alors besoin que de définir un port de communication.

Le code de l'agent est similaire à celui de l'agent utilisant le connecteur RMI avec cependant une url de connexion dédiée au protocole JMXMP

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

public class LancerAgentAvecJMXMP {

  public static void main(String[] args) {

    System.out.println("Lancement de l'agent JMX");

    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = null;
    try {
      System.out.println("Instanciation et enregistrement du Mbean");

      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      Premier mbean = new Premier();

      mbs.registerMBean(mbean, name);
      
      // Creation et demarrage du connecteur pour le protocole JMXMP
      JMXServiceURL url = new JMXServiceURL(
          "service:jmx:jmxmp://localhost:9998");

      JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
      cs.start();
      
      System.out.println("Lancement connecteur pour le protocle JMXMP " + url);

      int i = 0;
      System.out.println("Incrementation de la valeur du MBean ...");
      while (i < 6000) {

        mbean.setValeur(mbean.getValeur() + 1);
        Thread.sleep(1000);
        i++;
      }

      System.out.println("Arret connecteur pour le protocle JMXMP ");
      cs.stop();

      System.out.println("Arret de l'agent JMX");

    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Le code du client est aussi similaire à celui du client utilisant le connecteur RMI avec l'utilisation de l'url dédiée.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;
import java.net.MalformedURLException;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class ClientJMXAvecJMXMP {
  public static void main(String[] args) {
    MBeanServerConnection mbsc = null;
    JMXConnector connecteur = null;

    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      JMXServiceURL url = new JMXServiceURL(
          "service:jmx:jmxmp://localhost:9998");

      connecteur = JMXConnectorFactory.connect(url, null);

      mbsc = connecteur.getMBeanServerConnection();

      PremierMBean mbean = (PremierMBean) MBeanServerInvocationHandler
          .newProxyInstance(mbsc, name, PremierMBean.class, false);
      int valeur = mbean.getValeur();
      System.out.println("valeur = " + valeur);
      mbean.rafraichir();

    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (mbsc != null) {
        try {
          connecteur.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }
}

L'exécution du client sans mettre la bibliothèque jmxremote_optional.jar lève une exception de type MalformedURLException

Résultat :
C:\Users\Jean Michel\workspace\TestJMX\bin>java -cp . fr.jmdoudoux.dej.jmx.La
ncerAgentAvecJMXMP
Lancement de l'agent JMX
Instanciation et enregistrement du Mbean
java.net.MalformedURLException: Unsupported protocol: jmxmp
        at javax.management.remote.JMXConnectorServerFactory.newJMXConnectorServ
er(JMXConnectorServerFactory.java:323)
        at fr.jmdoudoux.dej.jmx.LancerAgentAvecJMXMP.main(LancerAgentAvecJMXM
P.java:39)

Avec la bibliothèque ajoutée au classpath, le client peut se connecter à l'agent et interagir avec le MBean.

Exemple :
C:\Users\Jean Michel\workspace\TestJMX\bin>java -cp
.;C:\java\api\jmxremote-1_0_1-bin\lib\jmxremote_optional.jar 
fr.jmdoudoux.dej.jmx.ClientJMXAvecJMXMP
valeur = 867

Il est aussi possible d'utiliser l'outil JConsole pour se connecter à l'agent en utilisant comme paramètre de connexion l'url de l'agent.

Dans ce cas pour que la connexion réussisse, il faut ajouter la bibliothèque au classpath pour permettre à JConsole d'avoir l'implémentation du protocole JMXMP. Le plus simple est d'ajouter le fichier jar dans le sous-répertoire lib/ext du répertoire d'installation du JRE.

 

32.8.5. L'utilisation de l'adaptateur de protocole HTML

L'adaptateur de protocoles HTML permet d'accéder à un agent en utilisant le protocole HTML : ainsi un simple navigateur web peut faire office de client JMX.

L'adaptateur de protocoles HTML est implémenté sous la forme d'un MBean et peut donc à ce titre être géré comme tout MBean.

Bien que JMX soit intégré à Java 5, l'adaptateur de protocole HTML n'est pas fourni en standard avec le JDK. Il faut télécharger l'implémentation de référence de JMX à l'url :

https://www.oracle.com/java/technologies/java-archive-downloads-java-plat-downloads.html

L'archive jmx-1_2_1-ri.zip contient dans le sous-répertoire lib une bibliothèque nommée jmxtools.jar qui doit être ajoutée au classpath.

L'adaptateur est encapsulé dans la classe com.sun.jdmk.comm.HtmlAdaptorServer : c'est un MBean qui doit être instancié et enregistré dans le serveur de MBeans de l'agent.

Le port utilisé par l'adaptateur doit être précisé en utilisant la méthode setPort(). Par défaut, c'est le port 8082 qui est utilisé.

La méthode start() démarre l'adaptateur et permet de traiter les requêtes HTML.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.lang.management.ManagementFactory;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

import com.sun.jdmk.comm.HtmlAdaptorServer;

public class LancerAgentAvecHTMLAdaptateur {
  static final int PORT_ADAPTATEUR = 8000;

  public static void main(String[] args) {

    System.out.println("Lancement de l'agent JMX");

    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = null;
    ObjectName adapterName = null;

    try {
      System.out.println("Instanciation et enregistrement du MBean");

      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      Premier mbean = new Premier();

      mbs.registerMBean(mbean, name);

      // Creation et demarrage de l'adaptateur de protocole HTML
      HtmlAdaptorServer adapter = new HtmlAdaptorServer();
      adapterName = new ObjectName(
          "fr.jmdoudoux.dej.jmx:name=htmladaptor,port=" + PORT_ADAPTATEUR);
      adapter.setPort(PORT_ADAPTATEUR);
      mbs.registerMBean(adapter, adapterName);
      adapter.start();
      System.out
          .println("Lancement de l'adaptateur de protocole HTML sur le port "
              + PORT_ADAPTATEUR);

      int i = 0;
      System.out.println("Incrementation de la valeur du MBean ...");
      while (i < 600) {

        mbean.setValeur(mbean.getValeur() + 1);
        Thread.sleep(1000);
        i++;
      }

      System.out.println("Arret de l'adaptateur de protocole HTML ");
      adapter.stop();

      System.out.println("Arret de l'agent JMX");

    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

Après compilation des classes, il faut exécuter l'agent JMX

Exemple :
C:\Users\Jean Michel\workspace\TestJMX\bin>java -cp .;jmxtools.jar fr.jmdoudoux
.dej.jmx/LancerAgentAvecHTMLAdaptateur
Lancement de l'agent JMX
Instanciation et enregistrement du MBean
Lancement de l'adaptateur de protocole HTML sur le port 8000
Incrementation de la valeur du MBean ...
Arret de l'adaptateur de protocole HTML 
Arret de l'agent JMX

Il faut ouvrir un navigateur avec l'url http://localhost:8000.

La première page affichée par l'adaptateur affiche les MBeans enregistrés pour le domaine par défaut dans le serveur de MBeans de l'agent JMX.

Le bouton Admin affiche une page qui permet d'enregistrer un nouvel MBean dans le serveur.

L'adaptateur lui-même est un MBean ce qui explique qu'il apparaît avec le MBean PremierMBean. En cliquant sur le lien de ce dernier, l'adaptateur affiche une page avec les propriétés et les opérations du MBean

Cette page affiche les propriétés, permet de modifier celles qui possèdent un setter et permet l'invocation des méthodes définies dans l'interface du MBean.

 

32.8.6. L'invocation d'un MBean par un proxy

Le classe MBeanServerConnection propose plusieurs méthodes pour interagir avec un MBean notamment :

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;
import java.net.MalformedURLException;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class ClientJMXAvecRMI {
  public static void main(String[] args) {
    MBeanServerConnection mbsc = null;
    JMXConnector connecteur = null;

    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      JMXServiceURL url = new JMXServiceURL(
          "service:jmx:rmi:///jndi/rmi://localhost:9000/server");

      connecteur = JMXConnectorFactory.connect(url, null);

      mbsc = connecteur.getMBeanServerConnection();

      System.out.println("valeur = " + mbsc.getAttribute(name, "Valeur"));
      mbsc.invoke(name, "rafraichir", null, null);

    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (AttributeNotFoundException e) {
      e.printStackTrace();
    } catch (InstanceNotFoundException e) {
      e.printStackTrace();
    } catch (MBeanException e) {
      e.printStackTrace();
    } catch (ReflectionException e) {
      e.printStackTrace();
    } finally {
      if (mbsc != null) {
        try {
          connecteur.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

L'utilisation de ces méthodes peut être source de tracas car certains problèmes ne peuvent être détectés qu'à l'exécution, par exemple, si le nom fourni de la méthode à invoquer est erroné.

La classe MBeanServerInvocationHandler permet de créer un proxy qui va interagir avec un MBean du serveur de MBeans. Cette classe va utiliser l'interface du MBean pour générer dynamiquement une classe de type proxy permettant d'interagir avec le MBean.

La méthode statique newProxyInstance() permet de créer un proxy pour invoquer un MBean. Elle attend en paramètres l'instance qui encapsule la connexion vers le serveur de MBeans, le nom identifiant le MBean, le type de l'interface du MBean et un booléen qui permet de préciser si le proxy doit implémenter l'interface NotificationEmitter permettant d'abonner un listener aux notifications du MBean.

L'exemple ci-dessous est identique à l'exemple précédent.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.io.IOException;
import java.net.MalformedURLException;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class ClientJMXAvecRMI {
  public static void main(String[] args) {
    MBeanServerConnection mbsc = null;
    JMXConnector connecteur = null;

    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");

      JMXServiceURL url = new JMXServiceURL(
          "service:jmx:rmi:///jndi/rmi://localhost:9000/server");

      connecteur = JMXConnectorFactory.connect(url, null);

      mbsc = connecteur.getMBeanServerConnection();

      PremierMBean mbean = (PremierMBean) MBeanServerInvocationHandler
          .newProxyInstance(mbsc, name, PremierMBean.class, false);
      int valeur = mbean.getValeur();
      System.out.println("valeur = " + valeur);
      mbean.rafraichir();

    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (mbsc != null) {
        try {
          connecteur.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }
}

Le code de la création du proxy est un peu particulier mais son utilisation facilite l'écriture du code qui invoque les fonctionnalités du MBean puisque le proxy donne aux appels distants l'apparence d'appels locaux. Le code est ainsi plus facile à écrire et à lire.

 

32.8.7. La recherche et la découverte des agents JMX

L'API JMX Remote précise comment il est possible de rechercher et découvrir des agents JMX en utilisant des infrastructures et des API existantes (aucune nouvelle API n'est définie par les spécifications JMX).

Un client JMX se connecte à un agent JMX par un connecteur ou un adaptateur de protocole. Pour rechercher et découvrir un agent, un client peut utiliser trois possibilités optionnelles d'infrastructures :

 

32.8.7.1. Par le Service Location Protocol (SLP)

Le Service Location Protocol (SLP) est un framework qui permet de rechercher, découvrir et connaître la configuration de services au travers du réseau.

L'agent JMX doit enregistrer chacun de ses connecteurs auprès du registre de SLP en lui fournissant son adresse et des attributs obligatoires et éventuellement optionnels pour qualifier le connecteur.

Le client JMX interroge le registre de SLP pour obtenir les adresses éventuellement en utilisant des filtres sur les attributs pour limiter la recherche. Le client peut alors se connecter en utilisant les adresses obtenues.

 

32.8.7.2. Par la technologie Jini

La technologie Jini offre une architecture logicielle pour créer et déployer des services au travers du réseau. Jini offre bien sûr un registre qui contient les services.

L'agent JMX doit enregistrer chacun de ses connecteurs auprès du registre de Jini et lui fournissant un objet de type stub et des attributs obligatoires et éventuellement optionnels pour qualifier le connecteur.

Le client JMX interroge le registre de SLP pour obtenir les stubs éventuellement en utilisant des filtres sur les attributs pour limiter la recherche. Le client peut alors se connecter en utilisant les stubs obtenus.

 

32.8.7.3. Par un annuaire et la technologie JNDI

JNDI est une API standard qui permet d'interagir avec différents services de nommage ou annuaires.

L'agent JMX doit enregistrer chacun de ses connecteurs auprès du registre de l'annuaire en lui fournissant son adresse et des attributs obligatoires et éventuellement optionnels pour qualifier le connecteur.

Le client JMX interroge le registre de l'annuaire pour obtenir les adresses éventuellement en utilisant des filtres sur les attributs pour limiter la recherche. Le client peut alors se connecter en utilisant les adresses obtenues.

 

32.9. Les notifications

Tous les types de MBeans peuvent émettre des notifications pour informer de certains événements survenus sur la ressource gérée par le MBean ou sur le MBean lui-même.

Les notifications peuvent avoir plusieurs utilités : avertir du changement d'une valeur ou de l'état d'un attribut, signaler un événement ou un problème, ...

 

32.9.1. L'interface NotificationBroadcaster

Pour émettre des notifications, un MBean peut implémenter l'interface NotificationBroadcaster mais son utilisation n'est plus recommandée. Il est préférable d'utiliser son interface fille NotificationEmitter.

L'interface NotificationBroadcaster définit trois méthodes qui permettent l'abonnement d'un listener, d'obtenir des informations sur les notifications et le désabonnement d'un listener.

Méthode

Rôle

void addNotificationListener(NotificationListener, NotificationFilter, Object)

Abonner le listener fourni en paramètre

MBeanNotificationInfo[] getNotificationInfo()

Renvoyer un tableau contenant des informations sur les notifications pouvant être émises

void removeNotificationListener(NotificationListener)

Désabonner le listener fourni en paramètre

 

32.9.2. L'interface NotificationEmitter

Pour émettre des notifications, un MBean doit de préférence implémenter l'interface NotificationEmitter. Celle-ci hérite de l'interface NotificationBroadcaster.

L'interface NotificationEmitter ne définit qu'une seule méthode supplémentaire, qui est une surcharge de la méthode removeNotificationListener() :

Méthode

Rôle

void removeNotificationListener(NotificationListener, NotificationFilter, Object)

Désabonner le listener fourni en paramètre

 

32.9.3. La classe NotificationBroadcasterSupport

Le plus simple pour implémenter les notifications dans un MBean est de le faire hériter de la classe NotificationBroadcasterSupport en plus d'implémenter l'interface du MBean. La classe NotificationBroadcasterSupport implémente l'interface NotificationEmitter et propose la méthode sendNotification() qui permet l'émission de la notification qui lui est fournie en paramètre. Celle-ci est envoyée à chaque listener abonné.

 

32.9.4. La classe javax.management.Notification

La classe Notification encapsule une notification émise par un MBean suite à un événement.

Une notification est donc une instance de la classe javax.management.Notification ou d'une de ses sous-classes : AttributeChangeNotification, JMXConnectionNotification, MBeanServerNotification, MonitorNotification, RelationNotification ou TimerNotification

Chaque notification possède :

Les sous-classes de la classe Notification peuvent avoir des attributs supplémentaires.

Lorsque le serveur de MBeans envoie la notification au client JMX, il transforme la source en son ObjectName si l'objet source est l'instance du MBean. En effet, le client JMX connait l'ObjectName mais ne possède pas la référence sur l'instance du MBean.

Chaque notification doit obligatoirement avoir un numéro de séquence.

Exemple :
    Notification notif = new AttributeChangeNotification(this,
        numeroSequence, System.currentTimeMillis(),
        "Modification de la valeur", "Valeur", "int", this.valeur, valeur);

    this.valeur = valeur;

    sendNotification(notif);

Pour émettre une notification, il faut instancier un objet de type Notification et appeler la méthode sendNotification() en lui passant en paramètre l'instance créée.

Il faut redéfinir la méthode getNotificationInfo() qui renvoie un tableau de type MBeanNotificationInfo contenant des données sur les notifications pouvant être émises.

Un objet de type MBeanNotificationInfo possède :

Le MBean devrait permettre de fournir directement à un client les données incluses dans une notification.

 

32.9.5. Un exemple de notifications

Cette section contient un exemple complet de mise en oeuvre de notifications par un MBean.

Exemple :
package fr.jmdoudoux.dej.jmx;

import javax.management.AttributeChangeNotification;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;

public class Premier extends NotificationBroadcasterSupport implements
    PremierMBean {

  private static String nom            = "PremierMBean";

  private int           valeur         = 100;

  private static long   numeroSequence = 0l;

  public String getNom() {
    return nom;
  }

  public int getValeur() {
    return valeur;
  }

  public synchronized void setValeur(int valeur) {
    numeroSequence++;
    Notification notif = new AttributeChangeNotification(this,
        numeroSequence, System.currentTimeMillis(),
        "Modification de la valeur", "Valeur", "int", this.valeur, valeur);

    this.valeur = valeur;

    sendNotification(notif);
  }

  public void rafraichir() {
    System.out.println("Rafraichir les donnees");
  }

  @Override 
  public MBeanNotificationInfo[] getNotificationInfo() { 
      String[] types = new String[] { 
          AttributeChangeNotification.ATTRIBUTE_CHANGE 
      }; 
      String name = AttributeChangeNotification.class.getName(); 
      String description = "Un attribut du MBean a ete modifie"; 
      MBeanNotificationInfo info = 
          new MBeanNotificationInfo(types, name, description); 
      return new MBeanNotificationInfo[] {info}; 
  } 
}

Il faut lancer l'agent et utiliser l'outil JConsole pour visualiser les notifications.

Il suffit de sélectionner Notifications et de cliquer sur le bouton «Subscribe»

Les notifications sont affichées au fur et à mesure de leur arrivée :

 

32.9.6. L'abonnement aux notifications par un client JMX

Un client JMX distant peut enregistrer un listener de type NotificationListener pour lui permettre d'être informé des changements du statut de la connexion utilisée pour les notifications. L'enregistrement se fait en utilisant la méthode addConnectionNotificationListener() sur une instance de l'interface JMXConnector.

Une classe d'un client JMX s'abonne aux notifications en enregistrant un listener de type NotificationListener grâce à la méthode addNotificationListerner() de l'instance de type MBeanServerConnection. Plusieurs listeners peuvent s'abonner à une même notification mais un même listener ne peut s'abonner qu'une seule fois à une notification.

L'interface NotificationListener ne définit qu'une seule méthode :
public void handleNotification(Notification notif, Object handback)

Cette méthode de type callback sera invoquée pour chaque listener abonné lors de l'émission d'une notification.

Le MBean est défini grâce à une interface.

Exemple ( code Java 5.0 ) :
package test.jmx;

import javax.management.MBeanNotificationInfo;

public interface PremierMBean {
  public abstract String getNom();
  public abstract int getValeur();
  public abstract void setValeur(final int valeur);
  public abstract void rafraichir();
  public abstract MBeanNotificationInfo[] getNotificationInfo();
}

L'implémentation du MBean hérite de la classe NotificationBroadcasterSupport qui propose des fonctionnalités pour permettre l'émission de notifications en invoquant la méthode sendNotification().

Exemple ( code Java 5.0 ) :
package test.jmx;

import javax.management.AttributeChangeNotification;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;

public class Premier extends NotificationBroadcasterSupport implements PremierMBean {
  private static String nom = "PremierMBean";
  private int valeur = 100;
  private static long numeroSequence = 0l;

  public String getNom() {
    return nom;
  }

  public int getValeur() {
    return valeur;
  }

  public synchronized void setValeur(final int valeur) {
    numeroSequence++;
    final Notification notif = new AttributeChangeNotification(this, numeroSequence, 
      System.currentTimeMillis(), "Modification de la valeur", 
      "Valeur", "int", this.valeur, valeur);
    this.valeur = valeur;
    sendNotification(notif);
    }

  public void rafraichir() {
    System.out.println("Rafraichir les donnees");
  }

  @Override
  public MBeanNotificationInfo[] getNotificationInfo() {
    final String[] types = new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE };
    final String name = AttributeChangeNotification.class.getName();
    final String description = "Un attribut du MBean a ete modifie";
    final MBeanNotificationInfo info = new MBeanNotificationInfo(types, name, description);
    return new MBeanNotificationInfo[] { info };
  }
}

Dans l'exemple ci-dessus, la notification est de type AttributChangeNotification fournie en standard par l'API JMX.

La partie serveur effectue plusieurs traitements :

Exemple ( code Java 5.0 ) :
package test.jmx;
      
  import java.lang.management.ManagementFactory;  
  import javax.management.MBeanServer;  
  import javax.management.ObjectName;  
  
  public class LanceServeur {  
    public static void main(final String[] args) {  
      System.out.println("Lancement de l'agent JMX");  
      final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();  
      ObjectName name = null;  

      try {  
        System.out.println("Instanciation et enregistrement du Mbean");  
        name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");  
        final Premier mbean = new Premier();  
        mbs.registerMBean(mbean, name);  
        int i = 0;  
        System.out.println("Incrementation de la valeur du MBean ...");  
  
        while (i < 6000) {  
          System.out.println("valeur = " + (mbean.getValeur() + 1));  
          mbean.setValeur(mbean.getValeur() + 1);  
          Thread.sleep(1000);  
          i++;  
        }  
        System.out.println("Arret de l'agent JMX");  
      } catch (final Exception e) {  
        e.printStackTrace();  
      }  
    }  
  }

La partie cliente instancie et démarre le listener qui va traiter les notifications et attend.

Exemple ( code Java 5.0 ) :
package test.jmx;
      
public class LanceClient {
  public static void main(final String[] args) {
    ClientListener listener = null;
    try {
      listener = new ClientListener();
      listener.connecter();
      while (true) {
        ;
      }
    } catch (final Exception e) {
      e.printStackTrace();
    } finally {
      if (listener != null) {
        listener.deconnecter();
      }
    }
  }
}

L'implémentation du listener se charge de gérer la connexion au serveur de MBeans et de traiter les notifications qui sont reçues.

Il implémente l'interface NotificationListener et implémente donc la méthode handleNotification().

Cette implémentation affiche simplement les notifications reçues et le détail de celles-ci si la notification est de type AttributeChangeNotification.

Exemple ( code Java 5.0 ) :
package test.jmx;
      
import java.io.IOException;
import javax.management.AttributeChangeNotification;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class ClientListener implements NotificationListener {
  private String port = "9998";
  private String host = "localhost";
  private JMXConnector connector;
  private MBeanServerConnection mbsc;
  private ObjectName name = null;

  public void handleNotification(final Notification notification, final Object handback) {
    System.out.println("\nNotification recue:");
    System.out.println("\tClassName: " + notification.getClass().getName());
    System.out.println("\tSource: " + notification.getSource());
    System.out.println("\tType: " + notification.getType());
    System.out.println("\tMessage: " + notification.getMessage());

    if (notification instanceof AttributeChangeNotification) {
      final AttributeChangeNotification acn = (AttributeChangeNotification) notification;
      System.out.println("\tAttributeName: " + acn.getAttributeName());
      System.out.println("\tAttributeType: " + acn.getAttributeType());
      System.out.println("\tNewValue: " + acn.getNewValue());
      System.out.println("\tOldValue: " + acn.getOldValue());
    }
  }

  public void deconnecter() {
    try {
      System.out.println("Desabonner le NotificationListener");
      mbsc.removeNotificationListener(name, this);
    }
    catch (final Exception e) {
      e.printStackTrace();
    }
    try {
      System.out.println("Deconnexion du server JMX");
      connector.close();
    }
    catch (final IOException e) {
      e.printStackTrace();
    }
  }
  public void connecter() throws Exception {
    final JMXServiceURL address = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" 
      + host + ":" + port + "/jmxrmi");
    name = new ObjectName("fr.jmdoudoux.dej.jmx:type=PremierMBean");
    
    System.out.println("Connexion au server JMX");
    connector = JMXConnectorFactory.connect(address, null);
    
    System.out.println("Connection JMX etablie a la JVM " + host + " sur le port " + port);
    final PremierMBean mbean = (PremierMBean) MBeanServerInvocationHandler
      .newProxyInstance(mbsc, name, PremierMBean.class, false);
    final int valeur = mbean.getValeur();
    
    System.out.println("Valeur courante du mbean = " + valeur);
    mbean.rafraichir();
    System.out.println("Abonnement du NotificationListener pour " + name.toString());
    mbsc.addNotificationListener(name, this, null, null);
  }

  public String getHost() {
    return host;
  }

  public void setHost(final String host) {
    this.host = host;
  }

  public String getPort() {
    return port;
  }

  public void setPort(final String port) {
    this.port = port;
  }
}

Il est possible de fournir un objet de type NotificationFilter dont l'implémentation encapsule un filtre sur les notifications que le client souhaite recevoir. L'interface NotificationFilter ne définit qu'une seule méthode isNotificationEnabled(Notification) qui renvoie un booléen.

JMX propose trois filtres en standard :

 

32.10. Les Dynamic MBeans

Un MBean standard expose ses fonctionnalités au travers d'une interface statique. Ainsi une propriété est définie à l'aide d'un getter et/ou d'un setter. Les MBeans standards ne permettent pas de répondre à tous les besoins notamment lorsque l'interface du MBean ne peut pas être définie de façon statique.

Parfois l'interface ne peut être définie que de façon dynamique : c'est par exemple le cas si les attributs sont issus d'une collection de type Map ou de la lecture d'une ressource externe comme un fichier.

Les Dynamic MBeans sont donc utiles si le nombre de fonctionnalités susceptibles d'être invoquées varie au cours des exécutions.

Le développement d'un MBean dynamic est complexe et nécessite que le MBean implémente l'interface DynamicMBean dont les méthodes retournent des informations statiques. Il est nécessaire d'utiliser et d'assembler des structures d'informations qui sont parfois redondantes. Ces informations sont encapsulées dans plusieurs classes du package javax.management : MBeanInfo, MBeanAttributeInfo, MBeanOperationInfo, ...

Les Dynamic MBeans ne possèdent pas un getter et un setter pour chaque attribut : ils proposent à la place des méthodes génériques pour obtenir et mettre à jour la valeur d'un attribut et invoquer les méthodes du MBean dynamiquement à partir de son nom.

Les fonctionnalités exposées par le MBean sont contenues dans un objet de type MBeanInfo retourné par la méthode getMBeanInfo() de l'interface DynamicMBean. Ces fonctionnalités concernent les attributs, les opérations et les notifications : les informations qu'il est cependant possible d'obtenir sur le MBean et ses méthodes sont plus riches que celles d'un MBean standard.

Ainsi l'interface du MBean est statique mais son implémentation expose dynamiquement les fonctionnalités du MBean.

L'exploitation des MBeans standard et dynamic ne fait aucune différence pour les clients JMX qui utilisent l'un et l'autre de la même façon. La différence se fait dans la façon dont ils exposent chacun leurs fonctionnalités :

Un agent JMX n'a pas besoin de faire de l'introspection sur un Dynamic MBean pour découvrir ses fonctionnalités, il lui suffit de lire ses métadonnées obtenues grâce à la méthode getMBeanInfo().

 

32.10.1. L'interface DynamicMBean

L'interface javax.management.DynamicMBean propose des méthodes pour permettre à un MBean Dynamic d'exposer dynamiquement ses fonctionnalités. Celles-ci sont exposées au moyen de descripteurs qui sont fournis grâce à ses méthodes.

Méthode

Rôle

MBeanInfo getMBeanInfo()

Renvoyer un objet de type MbeanInfo qui encapsule les fonctionnalités exposées par le MBean

Object getAttribute(String attribute)

Permettre d'obtenir la valeur d'un attribut à partir de son nom

void setAttribute(Attribute attribute)

Permettre de mettre à jour la valeur d'un attribut

AttributeList getAttributes(String[] attributes)

Permettre d'obtenir la valeur d'un ensemble d'attributs à partir de leurs noms

AttributeList setAttributes(AttributeList attributes)

Permettre de mettre à jour la valeur d'un ensemble d'attributs

Object invoke(String actionName, Object params[], String signature[])

Permettre d'invoquer une opération


La classe MBeanInfo encapsule les fonctionnalités exposées par le MBean : une collection des attributs avec leur type et leur nom, une collection des constructeurs, une collection des opérations invocables avec leurs paramètres, une collection des notifications et quelques informations.

 

32.10.2. Les métadonnées d'un Dynamic MBean

La méthode getMBeanInfo() renvoie un objet de type MBeanInfo qui encapsule les métadonnées des fonctionnalités du MBean.

 

32.10.2.1. La classe MBeanInfo

La classe javax.management.MBeanInfo est une classe qui encapsule les métadonnées des fonctionnalités du MBean. Chaque instance est immuable.

Cette classe propose plusieurs méthodes pour obtenir les métadonnées selon le type de leurs fonctionnalités :

Méthode

Rôle

MBeanAttributeInfo[] getAttributes()

Renvoyer un tableau de type MBeanAttributeInfo qui contient les métadonnées des attributs

MBeanConstructorInfo[] getConstructors()

Renvoyer un tableau de type MBeanConstructorInfo qui contient les métadonnées des constructeurs

String getDescription()

Renvoyer une description du MBean

MBeanNotificationInfo[] getNotifications()

Renvoyer un tableau de type MBeanNotificationInfo qui contient les métadonnées des notifications

MBeanOperationInfo[] getOperations()

Renvoyer un tableau de type MBeanOperationInfo qui contient les métadonnées des opérations


Elle possède un seul constructeur :

MBeanInfo(String className, String description, MBeanAttributeInfo[] attributes, MBeanConstructorInfo[] constructors, MBeanOperationInfo[] operations, MBeanNotificationInfo[] notifications)

 

32.10.2.2. La classe MBeanFeatureInfo

La classe javax.management.MBeanFeatureInfo est la classe mère des classes qui encapsulent les métadonnées d'une fonctionnalité du MBean.

Cette classe possède deux propriétés :

Propriété

Rôle

Description

Description de la fonctionnalité

Name

Nom de la fonctionnalité


Elle ne propose que des getters sur ses propriétés.

 

32.10.2.3. La classe MBeanAttributeInfo

La classe javax.management.MBeanAttributeInfo encapsule les métadonnées d'un attribut du MBean. Elle hérite de la classe MBeanFeatureInfo.

Elle possède plusieurs propriétés :

Propriété

Rôle

String type

Type de l'attribut

boolean isReadable

Indique si l'attribut est lisible

boolean isWritable

Indique si l'attribut est modifiable

boolean isIs

Indique si le getter est de type isXXX pour les booléens


Chaque instance de cette classe est immuable : elle ne propose donc que des getters sur ses propriétés.

Elle possède deux constructeurs :

 

32.10.2.4. La classe MBeanParameterInfo

La classe javax.management.MBeanParameterInfo encapsule les métadonnées d'un paramètre d'un constructeur ou d'une méthode du MBean. Elle hérite de la classe MBeanFeatureInfo.

Elle possède une propriété :

Propriété

Rôle

String type

Le type du paramètre


Chaque instance de cette classe est immuable : elle ne propose donc qu'un getter sur sa propriété.

Elle possède un seul constructeur :

MBeanParameterInfo(java.lang.String name, java.lang.String type, java.lang.String description)

 

32.10.2.5. La classe MBeanConstructorInfo

La classe javax.management.MBeanConstructorInfo encapsule les métadonnées d'un constructeur du MBean. Elle hérite de la classe MBeanFeatureInfo.

Elle possède une propriété :

Propriété

Rôle

MbeanParameterInfo[] signature

Renvoie un tableau des paramètres du constructeur


Chaque instance de cette classe est immuable : elle ne propose donc qu'un getter sur sa propriété.

Elle possède deux constructeurs :

 

32.10.2.6. La classe MBeanOperationInfo

La classe javax.management.MBeanOperationInfo encapsule les métadonnées d'une méthode du MBean. Elle hérite de la classe MBeanFeatureInfo.

Elle possède plusieurs propriétés :

Propriété

Rôle

MbeanParameterInfo[] signature

Renvoyer un tableau des paramètres de la méthode

int impact

Préciser la nature de la méthode : les valeurs possibles sont INFO, ACTION, ACTION_INFO, UNKNOWN

String returnType

Renvoyer le type de la valeur de retour de la méthode


Chaque instance de cette classe est immuable : elle ne propose donc que des getters sur ses propriétés.

Elle possède deux constructeurs :

La classe MbeanOperationInfo définit plusieurs constantes utilisables pour sa propriété impact :

Constante

Rôle

INFO

Précise que la méthode ne fait que lire l'état du MBean

ACTION

Précise que la méthode va modifier l'état du MBean

ACTION_INFO

Précise que la méthode lit et modifie l'état du MBean

UNKNOWN

Précise que la nature de la méthode est inconnue

 

32.10.2.7. La classe MBeanNotificationInfo

La classe javax.management.MBeanNotificationInfo encapsule les métadonnées d'une notification du MBean. Elle hérite de la classe MBeanFeatureInfo.

Elle possède une propriété :

Propriété

Rôle

String[] notifTypes

Renvoyer un tableau du libellé des types de la notification (ce n'est pas le nom des classes)


Chaque instance de cette classe est immuable : elle ne propose donc qu'un getter sur sa propriété.

Elle possède un seul constructeur :

MBeanNotificationInfo(java.lang.String[] notifTypes, java.lang.String name, java.lang.String description)

 

32.10.3. La définition d'un MBean Dynamic

Les Dynamic MBeans proposent des fonctionnalités plus avancées que les MBeans standard mais ils sont aussi plus complexes à mettre en oeuvre.

Un Dynamic MBean n'a pas besoin de respecter des conventions de nommage particulières ni de définir sa propre interface mais simplement d'implémenter l'interface DynamicMBean et d'avoir au moins un constructeur public.

Ce premier exemple est une implémentation en MBean Dynamic du MBean standard PremierMBean défini précédemment.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.util.Iterator;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ReflectionException;

public class PremierDynamic implements DynamicMBean {

  private static String nom = "PremierDynamic";
  private int valeur = 100;

  public PremierDynamic() {

  }

  @Override
  public Object getAttribute(String attribute)
      throws AttributeNotFoundException, MBeanException, ReflectionException {
    Object resultat = null;

    if (attribute.equals("nom")) {
      resultat = getNom();
    } else {

      if (attribute.equals("valeur")) {
        resultat = getValeur();
      } else {
        throw new AttributeNotFoundException(attribute);
      }
    }
    return resultat;

  }

  @Override
  public AttributeList getAttributes(String[] attributes) {
    AttributeList attributs = new AttributeList();
    attributs.add(new Attribute("nom", getNom()));
    attributs.add(new Attribute("valeur", getValeur()));
    return attributs;
  }

  @Override
  public MBeanInfo getMBeanInfo() {
    MBeanParameterInfo[] sansParamInfo = new MBeanParameterInfo[0];

    MBeanAttributeInfo attributs[] = new MBeanAttributeInfo[2];
    attributs[0] = new MBeanAttributeInfo("valeur", "int",
        "Valeur de l'instance", true, true, false);
    attributs[1] = new MBeanAttributeInfo("nom", "java.lang.String",
        "Nom de l'instance", true, false, false);

    MBeanConstructorInfo[] constructeurs = new MBeanConstructorInfo[1];
    constructeurs[0] = new MBeanConstructorInfo("PremierDynamic",
        "Constructeur par défaut de la classe", sansParamInfo);

    MBeanOperationInfo[] operations = new MBeanOperationInfo[1];
    operations[0] = new MBeanOperationInfo("rafraichir",
        "Rafraichir les données", sansParamInfo, void.class.getName(),
        MBeanOperationInfo.ACTION);

    return new MBeanInfo(getClass().getName(), "Mon premier MBean Dynamic",
        attributs, constructeurs, operations, null);

  }

  @Override
  public Object invoke(String actionName, Object[] params, String[] signature)
      throws MBeanException, ReflectionException {
    try {
      if (actionName.equals("rafraichir")) {
        rafraichir();
      }
      return null;
    } catch (Exception x) {
      throw new MBeanException(x);
    }

  }

  @Override
  public void setAttribute(Attribute attribute)
      throws AttributeNotFoundException, InvalidAttributeValueException,
      MBeanException, ReflectionException {

    String name = attribute.getName();
    try {
      if (name.equals("valeur")) {
        setValeur(((Integer) attribute.getValue()).intValue());
      } else {
        throw new AttributeNotFoundException(name);
      }

    } catch (ClassCastException cce) {
      throw new InvalidAttributeValueException(name);
    }
  }

  @Override
  @SuppressWarnings("unchecked")
  public AttributeList setAttributes(AttributeList attributes) {
    for (Iterator  i = attributes.iterator(); i.hasNext();) {
      Attribute attr = (Attribute) i.next();
      try {
        setAttribute(attr);
      } catch (AttributeNotFoundException e) {
        e.printStackTrace();
      } catch (InvalidAttributeValueException e) {
        e.printStackTrace();
      } catch (MBeanException e) {
        e.printStackTrace();
      } catch (ReflectionException e) {
        e.printStackTrace();
      }
    }
    return attributes;

  }

  private String getNom() {
    return nom;
  }

  private int getValeur() {
    return valeur;
  }

  private synchronized void setValeur(int valeur) {
    this.valeur = valeur;
  }

  private void rafraichir() {
    System.out.println("Rafraichir les donnees");

  }
}

L'intérêt de cet exemple est purement pédagogique car dans ce cas aucune fonctionnalité dynamique n'est utilisée mais il illustre bien la complexité d'écriture d'un MBean Dynamic par rapport à son équivalent sous la forme d'un MBean standard.

Les Dynamic MBeans peuvent tous fournir une description détaillée de leurs fonctionnalités ce qui peut les rendre plus facile à exploiter.

Le second exemple utilise une collection pour stocker ses attributs : cette collection pourrait par exemple être remplie en lisant un fichier de configuration. L'implémentation sous la forme d'un MBean standard est impossible car il n'est pas possible de connaître le contenu de la collection en dehors du contexte d'exécution.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.util.Hashtable;
import java.util.Iterator;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ReflectionException;

public class SecondDynamic implements DynamicMBean {

  private Hashtable<String, Object> attributs = new Hashtable<String, Object>();

  public SecondDynamic() {
    attributs.put("attrString1", "string");
    attributs.put("attrInt1", 0);
    attributs.put("attrString2", "string");
    attributs.put("valeur", 0);
  }

  @Override
  public synchronized Object getAttribute(String attribute)
      throws AttributeNotFoundException, MBeanException, ReflectionException {
    Object resultat = null;

    if (attributs.containsKey(attribute)) {
      resultat = attributs.get(attribute);
    } else {
      throw new AttributeNotFoundException(attribute);
    }
    return resultat;
  }

  @Override
  public AttributeList getAttributes(String[] attributes) {
    AttributeList resultat = new AttributeList();

    for (String cle : attributes) {
      if (attributs.containsKey(cle)) {
        resultat.add(new Attribute(cle, attributs.get(cle)));
      }
    }
    return resultat;
  }

  @Override
  public MBeanInfo getMBeanInfo() {
    MBeanParameterInfo[] sansParamInfo = new MBeanParameterInfo[0];

    int i = 0;
    MBeanAttributeInfo attribs[] = new MBeanAttributeInfo[attributs.size()];
    for (String cle : attributs.keySet()) {
      attribs[i] = new MBeanAttributeInfo(cle, attributs.get(cle).getClass()
          .getName(), "Description de l'attribut " + cle, true, true, false);
      i++;
    }

    MBeanConstructorInfo[] constructeurs = new MBeanConstructorInfo[1];
    constructeurs[0] = new MBeanConstructorInfo("SecondDynamic",
        "Constructeur par défaut de la classe", sansParamInfo);

    MBeanOperationInfo[] operations = new MBeanOperationInfo[1];
    operations[0] = new MBeanOperationInfo("rafraichir",
        "Rafraichir les données", sansParamInfo, void.class.getName(),
        MBeanOperationInfo.ACTION);

    return new MBeanInfo(getClass().getName(), "Mon second MBean Dynamic",
        attribs, constructeurs, operations, null);

  }

  @Override
  public Object invoke(String actionName, Object[] params, String[] signature)
      throws MBeanException, ReflectionException {

    try {
      if (actionName.equals("rafraichir")) {
        rafraichir();
      }
      return null;
    } catch (Exception x) {
      throw new MBeanException(x);
    }

  }

  @Override
  public synchronized void setAttribute(Attribute attribute)
      throws AttributeNotFoundException, InvalidAttributeValueException,
      MBeanException, ReflectionException {

    String name = attribute.getName();

    if (attributs.containsKey(name)) {
      attributs.remove(name);
      attributs.put(name, attribute.getValue());
    } else {
      throw new AttributeNotFoundException(name);
    }
  }

  @Override
  @SuppressWarnings("unchecked")
  public synchronized AttributeList setAttributes(AttributeList attributes) {
    for (Iterator i = attributes.iterator(); i.hasNext();) {
      Attribute attr = (Attribute) i.next();
      try {
        setAttribute(attr);
      } catch (AttributeNotFoundException e) {
        e.printStackTrace();
      } catch (InvalidAttributeValueException e) {
        e.printStackTrace();
      } catch (MBeanException e) {
        e.printStackTrace();
      } catch (ReflectionException e) {
        e.printStackTrace();
      }
    }
    return attributes;

  }

  private void rafraichir() {
    System.out.println("Rafraichir les donnees");
  }
}

Il est possible d'avoir un contrôle précis sur les fonctionnalités exposées en fonction d'un contexte. Par exemple, il est possible en fonction de la valeur d'une propriété d'exposer toutes les fonctionnalités ou seulement un sous-ensemble.

Remarque : il est tout à fait possible pour un MBean standard d'implémenter aussi l'interface DynamicMBean.

 

32.10.4. La classe StandardMBean

Il peut être intéressant pour un MBean standard de profiter de certaines fonctionnalités des MBeans Dynamic bien que l'interface du MBean soit statique. Ces fonctionnalités concernent par exemple la possibilité de fournir une description des attributs ou des méthodes.

Dans ce cas, plutôt que d'implémenter l'interface DynamicMBean, il est plus simple d'hériter de la classe javax.management.StandardMBean. Ainsi, les fonctionnalités du MBean sont toujours exposées sous la forme de son interface statique et il est possible de bénéficier des fonctionnalités des MBeans Dynamic.

La classe StandardMBean possède deux constructeurs :

Constructeur

Rôle

StandardMBean(Class interface)

Créer un MBean Dynamic à partir de l'interface du MBean

StandardMBean(Object implementation, Class interface)

Créer un MBean Dynamic à partir de l'instance du MBean et de son interface


Elle propose de nombreuses méthodes pour permettre d'interagir avec les informations dynamiques demandées sur une fonctionnalité.

La classe StandardMBean a été ajoutée par la version 1.2 de JMX.

 

32.11. Les Model MBeans

Les Model MBeans sont des Dynamic MBeans génériques et configurables.

Chaque implémentation de JMX à l'obligation de fournir une implémentation d'une classe nommée javax.management.modelmbean.RequiredModelMBean qui implémente l'interface ModelMBean

La classe RequiredModelMbean agit comme un modèle générique pour créer dynamiquement des MBeans à partir d'objets qui ne respectent pas les spécifications des MBeans. Un Model MBean est donc obligatoirement un Dynamic MBean puisqu'il n'est pas possible de connaître à l'avance la classe qu'il va encapsuler.

Les informations de configuration des fonctionnalités exposées sont encapsulées dans une instance de l'interface ModelMBeanInfo. Un descripteur encapsulé dans l'interface Descriptor permet de fournir le mapping entre une fonctionnalité exposée et la méthode correspondante à invoquer dans l'objet encapsulé dans le Model MBean.

Pour exposer un objet sous la forme d'un Model MBean, il faut suivre plusieurs étapes :

Les Model MBeans sont une des fonctionnalités avancées proposées par la spécification JMX. Leur mise en oeuvre est relativement compliquée ce qui limite leur usage. Ils ont cependant plusieurs intérêts :

 

32.11.1. L'interface ModelMBean et la classe RequiredModelMBean

Chaque implémentation de JMX doit fournir une implémentation de l'interface ModelMBean sous la forme d'une classe nommée RequiredModelMBean

L'interface ModelMBean doit être implémentée par un Model MBean. Cette interface définit deux méthodes :

Méthode

Rôle

void setModelMBeanInfo(ModelMBeanInfo inModelMBeanInfo)

Fournir les informations de configuration du Model MBean

void setManagedResource(java.lang.Object mr, java.lang.String mr_type)

Fournir l'instance de l'objet sur lequel le Model Bean va invoquer les méthodes


L'interface ModelMBeanInfo encapsule les informations et les descriptions des fonctionnalités de l'objet qui seront exposées au travers du Model MBean.

Chaque implémentation de JMX doit fournir une implémentation de la classe RequiredModelMBean. Cette implémentation doit fournir les fonctionnalités de base pour exposer une instance d'une classe sous la forme d'un MBean quand cette classe ne respecte pas les spécifications de JMX.

La classe RequiredModelMBean possède deux constructeurs :

Constructeur

Rôle

RequiredModelMBean()

Créer une instance avec un objet de type ModelMBeanInfo vide

RequiredModelMBean(ModelMBeanInfo mbi)

Créer une instance avec l'objet de type ModelMBeanInfo fourni en paramètre


Les méthodes setModelMBeanInfo() et setManagedResource() peuvent être utilisées pour fournir respectivement la description des fonctionnalités exposées par le MBean et l'instance de la classe qui sera encapsulée par le MBean.

Pour des besoins spécifiques, il est possible de créer une classe fille de la classe RequiredModelMBean.

 

32.11.2. La description des fonctionnalités exposées

La classe javax.management.modelmbean.ModelMBeanInfoSupport propose une implémentation de l'interfaceMBeanInfo qui facilite la création d'une instance du type de cette interface.

Cette classe propose trois constructeurs :

Constructeur

Rôle

ModelMBeanInfoSupport(ModelMBeanInfo mbi)

Créer une instance qui est une duplication de celle fournie en paramètre

ModelMBeanInfoSupport(java.lang.String className, java.lang.String description, ModelMBeanAttributeInfo[] attributes, ModelMBeanConstructorInfo[] constructors, ModelMBeanOperationInfo[] operations, ModelMBeanNotificationInfo[] notifications)

Créer une instance avec les informations fournies en paramètre et un descripteur par défaut. Le premier paramètre est le nom pleinement qualifié de la classe qui sera encapsulée par le MBean

ModelMBeanInfoSupport(java.lang.String className, java.lang.String description, ModelMBeanAttributeInfo[] attributes, ModelMBeanConstructorInfo[] constructors, ModelMBeanOperationInfo[] operations, ModelMBeanNotificationInfo[] notifications, Descriptor mbeandescriptor)

Créer une instance avec les informations et le descripteur fournis en paramètre. Le premier paramètre est le nom pleinement qualifié de la classe qui sera encapsulée par le MBean


Les informations sur les fonctionnalités exposées (attributs, constructeurs, opérations et notifications) sont décrites grâce à différents objets :

L'interface Descriptor permet de fournir des informations supplémentaires sur un attribut, un constructeur, une opération ou une notification. Une instance de type Descriptor peut être associée à chaque instance des classes ModelMBean*Info.

Une instance de Descriptor encapsule une collection de champs ayant chacun la forme nom=valeur.

La classe DescriptorSupport implémente l'interface Descriptor et permet de facilement créer une instance de type Descriptor.

Pour créer une instance de l'interface ModelMBeanInfo, il faut écrire beaucoup de code car il faut créer au moins un objet pour chaque élément exposé par le MBean.

Remarque importante : il faut définir chaque attribut avec un objet de type ModelMBeanAttributeInfo mais il faut aussi impérativement définir chaque getter et chaque setter dans un objet de type ModelMBeanOperationInfo. Ceci est nécessaire car les conventions de nommage des JavaBeans n'ont pas d'obligation a être respectées dans la classe qui sera encapsulée par le MBean.

Un objet de type RequiredModelMBean ne fait pas d'introspection sur la classe qu'il encapsule pour vérifier les informations fournies dans le ModelMBeanInfo : il fait aveuglement confiance aux informations qu'il contient.

Certaines implémentations peuvent fournir des utilitaires pour faciliter l'instanciation de l'interface ModelMBeanInfo par exemple à partir de fichiers XML, ce qui réduit la quantité de code à produire pour développer un Model MBean.

 

32.11.3. Un exemple de mise en oeuvre

L'exemple de cette section va exposer sous la forme d'un Model MBean une instance d'une simple classe nommée MaClasse.

Exemple :
package fr.jmdoudoux.dej.jmx;

public class MaClasse {

  private static String nom = "MaClasse";
  private int valeur = 100;
  
  public String getNom() {
    return nom;
  }

  public int getValeur() {
    return valeur;
  }

  public synchronized void setValeur(int valeur) {
     this.valeur = valeur;
  }

  public void rafraichir() {
    System.out.println("Rafraichir les donnees");
  }

  public MaClasse() {
  }
}

La classe MaClasse est un simple POJO qui n'implémente aucune interface particulière : elle ne respecte aucune spécification de JMX. L'exemple suivant va encapsuler une instance de cette classe dans un Model MBean et fournir une description de ses fonctionnalités exposées par le Model MBean.

Dans l'agent JMX, une instance de la classe RequiredModelMBean est instanciée en passant en paramètre de son constructeur l'instance de l'interface ModelMBeanInfo.

Les informations sur les fonctionnalités exposées par le Model MBean et le mapping avec les méthodes correspondantes à invoquer sont encapsulés dans cette instance de l'interface ModelMBeanInfo.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.lang.management.ManagementFactory;

import javax.management.Descriptor;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.RuntimeOperationsException;
import javax.management.modelmbean.DescriptorSupport;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanConstructorInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanOperationInfo;
import javax.management.modelmbean.RequiredModelMBean;

public class LancerAgentModelMBean {

  public static void main(String[] args) {
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = null;
    try {
      name = new ObjectName(
          "fr.jmdoudoux.dej.jmx:type=OpenMXBean,name=MaClasse");

      MaClasse maClasse = new MaClasse();
      RequiredModelMBean modelMBean = new RequiredModelMBean(creerMBeanInfo());
      modelMBean.setManagedResource(maClasse, "objectReference");
      mbs.registerMBean(modelMBean, name);

      System.out.println("Lancement ...");
      while (true) {
        Thread.sleep(1000);
      }
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
    } catch (RuntimeOperationsException e) {
      e.printStackTrace();
    } catch (InstanceNotFoundException e) {
      e.printStackTrace();
    } catch (MBeanException e) {
      e.printStackTrace();
    } catch (InvalidTargetObjectTypeException e) {
      e.printStackTrace();
    }
  }

  private static ModelMBeanInfo creerMBeanInfo() {
    Descriptor descriptorValeur = new DescriptorSupport(new String[] {
        "name=Valeur", "descriptorType=attribute", "default=0",
        "displayName=Valeur stockée dans la classe", "getMethod=getValeur",
        "setMethod=setValeur" });

    Descriptor descriptorNom = new DescriptorSupport(new String[] { "name=Nom",
        "descriptorType=attribute", "displayName=Nom de la classe",
        "getMethod=getNom" });

    ModelMBeanAttributeInfo[] mmbai = new ModelMBeanAttributeInfo[2];
    mmbai[0] = new ModelMBeanAttributeInfo("Valeur", "java.lang.Integer",
        "Valeur stockée dans la classe", true, true, false, descriptorValeur);
    mmbai[1] = new ModelMBeanAttributeInfo("Nom", "java.lang.String",
        "Nom de la classe", true, false, false, descriptorNom);

    ModelMBeanOperationInfo[] mmboi = new ModelMBeanOperationInfo[4];

    mmboi[0] = new ModelMBeanOperationInfo("getValeur",
        "getter pour l'attribut Valeur", null, "Integer",
        ModelMBeanOperationInfo.INFO);

    MBeanParameterInfo[] mbpiSetValeur = new MBeanParameterInfo[1];
    mbpiSetValeur[0] = new MBeanParameterInfo("valeur", "java.lang.Integer",
        "valeur de l'attribut");
    mmboi[1] = new ModelMBeanOperationInfo("setValeur",
        "setter pour l'attribut Valeur", mbpiSetValeur, "void",
        ModelMBeanOperationInfo.ACTION);

    mmboi[2] = new ModelMBeanOperationInfo("getNom",
        "getter pour l'attribut Nom", null, "String",
        ModelMBeanOperationInfo.INFO);

    mmboi[3] = new ModelMBeanOperationInfo("rafraichir",
        "Rafraichir les données", null, "void", ModelMBeanOperationInfo.ACTION);

    ModelMBeanConstructorInfo[] mmbci = new ModelMBeanConstructorInfo[1];
    mmbci[0] = new ModelMBeanConstructorInfo("MaClasse",
        "Constructeur par défaut", null);

    return new ModelMBeanInfoSupport("fr.jmdoudoux.dej.jmx.MaClasse",
        "Exemple de ModelBean", mmbai, mmbci, mmboi, null);
  }
}

La partie la plus délicate lors de la mise en oeuvre d'un Model MBean est de créer cette instance de l'interface ModelMBeanInfo qui va contenir la description des fonctionnalités à exposer ainsi que le mapping vers les méthodes de l'instance à invoquer. Pour rendre le code plus lisible, la création de cette instance est faite dans une méthode dédiée.

L'instance de la classe à invoquer est fournie en paramètre du Model MBean en utilisant la méthode setManagedRessource(). Elle attend deux paramètres :

C'est bien le Model MBean qui est enregistré dans le serveur de MBeans. Pour les clients JMX, le Model MBean est un Dynamic MBean. Le client n'a pas besoin d'avoir accès à la classe de l'instance encapsulée dans le Model MBean.

L'objet de type RequiredModelMBean va dynamiquement générer les entités nécessaires pour exposer les fonctionnalités sous la forme d'un Dynamic MBean.

Il suffit de lancer l'agent et d'ouvrir un client JMX pour pouvoir interagir avec le ModelMBean.

Hormis le fait que les getters et les setters apparaissent dans les opérations, rien ne distingue le ModelMBean d'un autre MBean pour le client JMX.

 

32.11.4. Les fonctionnalités optionnelles des Model MBeans

La classe RequiredModelMBean peut proposer un support optionnel de certaines fonctionnalités comme le logging, la persistance ou un cache de données (caching).

La fonctionnalité de cache de données permet de stocker dans une variable du MBean la valeur d'un attribut et de la renvoyer durant sa durée de vie dans le cache plutôt que de toujours solliciter dynamiquement l'instance encapsulée.

Certaines de ces fonctionnalités peuvent être configurées au travers des données fournies par un Descriptor. Celles-ci peuvent être modifiées dynamiquement par le MBean ou par un client JMX pour adapter le comportement des fonctionnalités par défaut.

Pour connaître les fonctionnalités optionnelles implémentées et la façon de les mettre en oeuvre il faut consulter la documentation de l'implémentation utilisée. Leur utilisation limite donc la portabilité vers une autre implémentation de JMX.

 

32.11.5. Les différences entre un Dynamic MBean et un Model MBean

Un Model MBean est un Dynamic MBean mais il est plus souple à mettre en oeuvre : le code d'un Dynamic MBean doit être intégralement écrit alors que pour un Model MBean une implémentation par défaut est fournie obligatoirement par l'implémentation de JMX et il suffit de lui fournir les fonctionnalités exposées et une référence sur l'objet à invoquer.

Les traitements d'un Dynamic MBean doivent être codés dans le MBean alors qu'avec un Model MBean le code est contenu dans l'instance de la classe dont les fonctionnalités sont exposées par le MBean.

Les fonctionnalités exposées par un Dynamic MBean doivent être connues au moment de l'écriture du code du MBean. Avec un Model MBean, les fonctionnalités exposées peuvent être définies et modifiées dynamiquement en utilisant la méthode setModelMBeanInfo().

Un Model MBean peut fournir des descriptions complémentaires des fonctionnalités qu'il expose sous la forme d'objets de type Descriptor qui encapsulent des champs. Ces champs peuvent être personnalisés.

L'implémentation d'un Model MBean peut proposer des services optionnels (persistance, logging, caching) qui peuvent être dynamiquement configurés avec des champs d'un Descriptor.

 

32.12. Les Open MBeans

La spécification JMX permet l'emploi de types complexes et portables en utilisant les Open MBeans.

Un Open MBean est un Dynamic MBean où tous les types utilisés doivent appartenir à une liste précisément définie dans les spécifications JMX. Ces types de base peuvent être utilisés dans un type composé dédié appelé Open Type.

Seul un sous-ensemble restreint de classes peut être décrit sous la forme d'un OpenType :

Il faut noter que les types primitifs ne sont pas autorisés : il faut utiliser leur wrapper.

Ceci permet à un Open MBean d'être portable avec des types complexes mais sans utiliser un type spécifique qui devrait être fourni à chaque client JMX. Les Open MBeans sont ainsi les MBeans les plus ouverts et les plus portables avec les clients JMX puisqu'ils n'ont pas besoin d'ajouter dans leur classpath des classes spécifiques.

La restriction des types de données utilisables est aussi intéressante pour les connecteurs et les adaptateurs de protocoles qui n'ont qu'à savoir gérer ces types.

Un objet de type OpenMBeanInfo encapsule la description des fonctionnalités exposées par un Open MBean en incluant en plus du type Java un objet de type OpenType pour chaque attribut et opération.

Les spécifications des Open MBeans dans la version 1.0 de JMX sont incomplètes donc inutilisables. Dans la version 1.1, les spécifications sont complètes mais leur implémentation est facultative. A partir de la version 1.2, elles sont obligatoires dans toutes les implémentations.

 

32.12.1. La mise en oeuvre d'un Open MBean

Un Open MBean doit implémenter l'interface DynamicMBean et n'a pas besoin d'implémenter une interface spécifique aux Open MBeans. La différence avec un Dynamic MBean se fait à deux niveaux :

La description des fonctionnalités d'un Open MBean est assurée grâce aux interfaces du package javax.management.openmbean.

Pour distinguer les Open MBeans des autres MBeans, les données de description des fonctionnalités exposées sont encapsulées dans des interfaces dédiées du package javax.management.openmbean :

Chacune de ces interfaces possède une classe correspondante dont le nom se termine par Support.

Un objet de type OpenMBeanInfo peut décrire la valeur par défaut et les valeurs autorisées pour un attribut, un paramètre et une valeur de retour.

Pour la description des notifications, les Open MBeans utilisent la classe MBeanNotificationInfo.

L'interface OpenMBeanOperationInfo possède une propriété impact de type int qui précise l'impact de la méthode lorsqu'elle est invoquée. Les valeurs possibles sont :

 

32.12.2. Les types de données utilisables dans les Open MBeans

Les types utilisés pour les attributs, les paramètres et les valeurs de retour des opérations d'un Open MBean doivent appartenir à la liste des types précisés dans les spécifications JMX :

L'ensemble de ces types est désigné sous le nom Open Types.

 

32.12.2.1. Les Open Types

Les Open Types encapsulent la description d'un type de données utilisé par les Open MBeans. Ils permettent de fournir un moyen standard de décrire les types utilisables par les Open MBeans. Un Open Type peut représenter un type primitif sous la forme de son wrapper, un type complexe composé d'autres Open Types ou un tableau d'Open Types.

La classe Abstraite OpenType est la classe mère des différentes classes qui encapsulent des Open Types :

Les classes Composite Type, TabularType et ArrayType peuvent contenir d'autres Open Types.

Les instances des Open Types sont, selon leur classe, une instance de différents types :

Les interfaces CompositeData et TabularData encapsulent les données de leurs types complexes respectifs.

 

32.12.2.2. La classe CompositeType et l'interface CompositeData

Les données utilisées par un Open MBean peuvent être de type CompositeData. Cette classe permet d'encapsuler un objet complexe qui n'utilisera que des OpenTypes et respectera ainsi les spécifications des Open MBeans.

L'OpenType correspondant dans l'OpenMBeanInfo est CompositeType. Un CompositeType décrit un ensemble d'éléments ou de champs. Chaque élément possède un nom et un Open Type.

La classe CompositeData associe une clé à une valeur pour chacun des attributs définis avec un CompositeType. Un ou plusieurs éléments d'un CompositeData forment la clé de l'élément.

Les classes CompositeDataSupport et TabularDataSupport proposent une implémentation respectueuse des interfaces CompositeData et TabularData pour faciliter leur création.

Un objet de type CompositeData est immuable : toutes les données qu'il encapsule doivent donc être fournies lors de son instanciation. La classe CompositeDataSupport propose pour cela deux constructeurs :

Constructeur

Rôle

CompositeDataSupport(CompositeType compositeType, Map<String,?> items)

Créer une instance qui encapsule les données du CompositeType avec les valeurs fournies dans la collection de type Map

CompositeDataSupport(CompositeType compositeType, String[] itemNames, Object[] itemValues)

Créer une instance qui encapsule les données du CompositeType avec les valeurs fournies sous la forme de deux tableaux (un qui contient les noms des attributs et l'autre qui encapsule leurs valeurs : l'ordre des données des deux tableaux doit correspondre).


La méthode getCompositeType() renvoie une instance de la classe CompositeType qui contient la description de l'Open Type encapsulé.

 

32.12.2.3. La classe TabularType et l'interface TabularData

Les données utilisées par un Open MBean peuvent être de type TabularData. Cette classe permet d'encapsuler un tableau d'objets de type CompositeData qui n'utilisera que des OpenTypes et respectera ainsi les spécifications des Open MBeans.

L'OpenType correspondant dans l'OpenMBeanInfo est TabularType. Tous les éléments d'un TabularData possèdent le même CompositeType.

Une instance de TabularData encapsule une collection d'objets de type CompositeData. Chaque objet de ce type encapsule les données d'une occurrence. Chaque occurrence possède une clé unique obtenue à partir des données encapsulées dans le CompositeData.

Avec une instance de TabularData, il est possible d'ajouter ou de supprimer une ou plusieurs occurrences.

La classe TabularDataSupport encapsule un tableau à une dimension d'objets de type CompositeData. Tous les CompositeData contenus dans le TabularData doivent avoir le même CompositeType.

Chaque occurrence est associée à une clé unique qui identifie cette occurrence. Cette clé est composée d'un ou plusieurs attributs encapsulés dans le CompositeData correspondant. Généralement cette clé est composée d'une seule donnée de type Integer ou String.

La classe TabularDataSupport propose toutes les méthodes nécessaires pour manipuler les occurrences qu'elle encapsule : put(), putAll(), get(), remove(), clear(), size(), isEmpty(), containsKey(), keySet(), values(), ...

La méthode get() attend en paramètre un tableau des valeurs de clés et renvoie l'objet de type CompositeData associé si celui-ci existe.

 

32.12.3. Un exemple d'utilisation d'un Open MBean

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

 

32.12.4. Les avantages et les inconvénients des Open MBeans

Le grand avantage des Open MBeans est de garantir l'interopérabilité. Leur principal défaut est d'être des Dynamic MBeans, donc relativement difficiles à coder. Une alternative est de coder un MBean Standard qui n'utilise que des Open Types.

La version 1.1 des spécifications de JMX ne fournit pas tous les détails concernant les Open MBeans, leur support est donc optionnel. D'ailleurs l'implémentation de référence de la version 1.1 ne propose pas d'implémentation pour les Open MBeans.

 

32.13. Les MXBeans

La version 6.0 de Java introduit un nouveau type de MBean : les MXBeans. Ce type de MBeans permet d'utiliser des types définis par l'utilisateur du moment que ces types respectent les contraintes définies dans les spécifications.

Ces types sont convertis vers des Open Types tels que SimpleType, CompositeType, ArrayType, TabularType, ... définis pour les Open MBeans. Les Open Types sont définis dans le package javax.management.openmbean.

Cela permet une utilisation du MBean sans que le client JMX n'aie besoin du jar qui contient la définition du MBean même si ce client est distant.

Les MXBeans sont définis grâce à une interface statique mais contrairement aux MBeans standard le nom de la classe qui implémente l'interface du MXBean est libre.

La plate-forme Java fournit plusieurs MXBeans regroupés dans le package java.lang.management. Le développeur peut aussi écrire les siens.

 

32.13.1. La définition d'un MXBean

L'interface d'un MXBean doit soit avoir un nom qui termine par convention par MXBean soit être annotée avec l'annotation @javax.management.MXBean. Dans ce dernier cas, le nom de l'interface est libre.

Exemple :
package fr.jmdoudoux.dej.jmx;

public interface InfoMXBean {

  public InfoParametre getInfo();
  
  public void rafraichir();
}

Le MXBean doit implémenter son interface.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.util.Date;

public class Info implements InfoMXBean {

  @Override
  public InfoParametre getInfo() {
    return new InfoParametre("nom1", new Date(), 100l, "description1");
  }

  @Override
  public void rafraichir() {
    System.out.println("Appel de la méthode rafraichir()");
  }
}

 

32.13.2. L'écriture d'un type personnalisé utilisé par le MXBean

L'exemple utilise un type personnalisé qui est un simple bean. Le constructeur est annoté avec l'annotation @ConstructorProperties. Cette annotation permet d'associer chaque paramètre d'un constructeur à une propriété.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.beans.ConstructorProperties;
import java.util.Date;

public class InfoParametre {

  private String nom;
  private String description;
  private Date dateCreation;
  private long taille;

  @ConstructorProperties( { "nom", "dateCreation", "taille", "description" })
  public InfoParametre(String pnom, Date pdateCreation, long ptaille,
      String pdescription) {
    super();
    this.nom = pnom;
    this.description = pdescription;
    this.dateCreation = pdateCreation;
    this.taille = ptaille;
  }

  public String getNom() {
    return nom;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public Date getDateCreation() {
    return dateCreation;
  }

  public void setDateCreation(Date dateCreation) {
    this.dateCreation = dateCreation;
  }

  public long getTaille() {
    return taille;
  }

  public void setTaille(long taille) {
    this.taille = taille;
  }
}

JMX va utiliser les getters pour créer une instance de CompositeData qui encapsule les données. Pour recréer une instance de la classe InfoParametre à partir d'une instance de CompositeDate, JMX utilise les informations fournies par l'annotation @ConstructorProperties.

 

32.13.3. La mise en oeuvre d'un MXBean

Le MXBean doit être instancié et enregistré dans le serveur de MBeans de la même manière que pour les autres MBeans.

Exemple :
package fr.jmdoudoux.dej.jmx;

import java.lang.management.ManagementFactory;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

public class LancerAgentMXBean {

  public static void main(String[] args) {
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = null;
    try {
      name = new ObjectName("fr.jmdoudoux.dej.jmx:type=InfoMXBean");

      InfoMXBean mbean = new Info();

      mbs.registerMBean(mbean, name);

      System.out.println("Lancement ...");
      while (true) {

        Thread.sleep(1000);
      }
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceAlreadyExistsException e) {
      e.printStackTrace();
    } catch (MBeanRegistrationException e) {
      e.printStackTrace();
    } catch (NotCompliantMBeanException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
    }
  }
}

La principale différence est au niveau du client JMX : l'attribut Info n'est pas du type InfoParametre mais de l'Open Type CompositeData.

En double cliquant sur la valeur de la ligne de l'attribut Info, il est possible d'afficher le détail des données encapsulées dans le CompositeData.

 

32.14. L'interface PersistentMBean

Il peut être nécessaire à un MBean de rendre ses données persistantes. Dans ce cas, le MBean doit implémenter l'interface javax.management.PersistentMBean.

Cette interface ne définit que deux méthodes :

Méthode

Rôle

void load()

Lire des données du MBean à partir de l'unité de persistance

void store()

Ecriture des données du MBean vers l'unité de persistance


L'invocation de la méthode load() dans son constructeur est à la charge de l'implémentation du MBean.

L'invocation de la méthode save() peut être définie dans une Persistance Policy.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

L'implémentation du MBean est libre de choisir l'unité de persistance utilisée (fichier, base de données, ...)

 

32.15. Le monitoring d'une JVM

JMX est aussi utilisé pour surveiller et gérer la JVM. A partir de la version 5 de Java : un agent JMX peut être utilisé pour accéder à l'instrumentation de la JVM et ainsi la surveiller et la gérer à distance.

Java 5.0 propose plusieurs fonctionnalités relatives au monitoring notamment :

La JVM incorpore un serveur de MBeans et utilise des MXBeans dédiés fournis avec la plate-forme Java SE pour permettre de surveiller et gérer la JVM.

Ces MXBeans encapsulent chacun une grande fonctionnalité de la JVM : chargement des classes, ramasse-miettes, compilateur JIT, threads, mémoire, ... Ceci permet d'obtenir de façon standard, grâce à JMX, des informations sur la consommation en ressources et l'activité de la JVM.

Il est ainsi possible de consulter et d'interagir avec ces fonctionnalités en utilisant un client JMX comme JConsole fourni avec le JDK.

Pour pouvoir activer la connexion RMI de l'agent JMX de la JVM, il faut utiliser la propriété com.sun.management.jmxremote de la JVM.

Exemple :
java -Dcom.sun.management.jmxremote MonApplication

Pour permettre un accès à un client distant sans authentification, il faut fournir trois autres propriétés à la JVM.

Exemple :
com.sun.management.jmxremote.port=9999
com.sun.management.jmxremote.authenticate=false
com.sun.management.jmxremote.ssl=false

Le port précisé ne doit pas être déjà utilisé.

La JSR 174 (Monitoring and Management Specification for the Java Virtual Machine JVM) définit plusieurs MXBeans dans le package java.lang.management pour la gestion et le monitoring de la JVM.

 

32.15.1. L'interface ClassLoadingMXBean

Cet MXBean permet de surveiller et de gérer le système de chargement des classes de la JVM.

Une instance de l'interface ClassLoadingMXBean est obtenue en invoquant la méthode getClassLoadingMXBean() de la fabrique ManagementFactory.

L'ObjectName pour l'unique instance de cet MXBean est : java.lang:type=ClassLoading

Cet MXBean permet d'obtenir plusieurs informations :

Il permet aussi d'activer ou non l'affichage dans la sortie standard d'informations sur les activités de classloading de la JVM.

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;

import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;

public class TestClassLoadingMXB {

  public static void main(String[] args) {

    ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean();
    System.out.printf("Loaded class count : %d\n", clBean
        .getLoadedClassCount());
    System.out.printf("Total loaded class count : %d\n", clBean.getTotalLoadedClassCount());
    System.out.printf("Unloaded class count : %d\n", clBean
        .getUnloadedClassCount());
    System.out.printf("isVerbose : %b \n", clBean.isVerbose());
    System.out.println();
    clBean.setVerbose(true);
  }
}

Résultat :
Loaded class count : 334
Total loaded class count : 422
Unloaded class count : 0
isVerbose : false 

[Loaded java.util.IdentityHashMap$KeySet from shared objects file]
[Loaded java.util.IdentityHashMap$IdentityHashMapIterator from shared objects file]
[Loaded java.util.IdentityHashMap$KeyIterator from shared objects file]
[Loaded java.io.DeleteOnExitHook from shared objects file]
[Loaded java.util.HashMap$KeySet from shared objects file]
[Loaded java.util.LinkedHashMap$KeyIterator from shared objects file]

 

32.15.2. L'interface CompilationMXBean

Cet MXBean permet de surveiller le système de compilation JIT de la JVM.

Une instance de l'interface CompilationMXBean est obtenue en invoquant la méthode getCompilationMXBean() de la fabrique ManagementFactory.

L'ObjectName pour l'unique instance de cet MXBean est : java.lang:type=Compilation

Cet MXBean permet d'obtenir plusieurs informations :

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;

import java.lang.management.CompilationMXBean;
import java.lang.management.ManagementFactory;

public class TestCompilationMXB {

  public static void main(String[] args) {

    CompilationMXBean cBean = ManagementFactory.getCompilationMXBean();
    System.out.printf("Name : %s\n", cBean.getName());
    System.out.printf("Total compilation time : %d ms\n", cBean
        .getTotalCompilationTime());
    System.out.printf("iscompilationTimeMonitoringSupported : %b \n", cBean
        .isCompilationTimeMonitoringSupported());
  }
}

Résultat :
Name : HotSpot Client Compiler
Total compilation time : 7 ms
iscompilationTimeMonitoringSupported : true 

 

32.15.3. L'interface GarbageCollectorMXBean

Cet MXBean permet de surveiller le ramasse-miettes de la JVM. Elle hérite de l'interface MemoryManagerMXBean. Une JVM peut avoir une ou plusieurs instances de cet MXBean, une pour chaque algorithme utilisé pour gérer la mémoire.

Les instances de l'interface GarbageCollectorMXBean sont obtenues en invoquant la méthode getGarbageCollectorMXBeans() de la fabrique ManagementFactory.

L'ObjectName d'une instance de cet MXBean est de la forme : java.lang:type=GarbageCollector, name=xxx

Cet MXBean permet d'obtenir plusieurs informations :

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;
      
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;

public class TestGarbageCollectorMXB {
  public static void main(String[] args) {
    for (int i = 0; i < 100000; i++) {
      Byte[] tableau = new Byte[50 * 1024];
      if ((i % 10000) == 0) {
        System.out.print(".");
      }
    }
    System.out.println("");
    for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
      System.out.printf("Memory manager name : %s\n", gcBean.getName());
      System.out.printf("  isValid : %b\n", gcBean.isValid());
      for (String pool : gcBean.getMemoryPoolNames()) {
        System.out.printf("  Memory pool name : %s\n", pool);
      }
      System.out.printf("\n  collectionCount : %d\n", gcBean
          .getCollectionCount());
      System.out.printf("  collectionTime : %d ms\n", gcBean
          .getCollectionTime());
      System.out.println();
    }
  }
}

Résultat :
..........
Memory manager name : Copy
  isValid : true
  Memory pool name : Eden Space
  Memory pool name : Survivor Space
  collectionCount : 25000
  collectionTime : 1228 ms
Memory manager name : MarkSweepCompact
  isValid : true
  Memory pool name : Eden Space
  Memory pool name : Survivor Space
  Memory pool name : Tenured Gen
  Memory pool name : Perm Gen
  Memory pool name : Perm Gen [shared-ro]
  Memory pool name : Perm Gen [shared-rw]
  collectionCount : 0
  collectionTime : 0 ms

 

32.15.4. L'interface MemoryManagerMXBean

Cet MXBean permet de lister les gestionnaires de mémoire de la JVM. Une JVM peut avoir une ou plusieurs instances de cet MXBean, une pour chaque gestionnaire utilisé pour gérer la mémoire.

Les instances de l'interface MemoryManagerMXBean sont obtenues en invoquant la méthode getMemoryManagerMXBeans() de la fabrique ManagementFactory.

L'ObjectName d'une instance de cet MXBean est de la forme : java.lang:type=MemoryManager, name=xxx

Cet MXBean permet d'obtenir plusieurs informations :

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;
      
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryManagerMXBean;

public class TestMemoryManagerMXB {
  public static void main(String[] args) {
    for (MemoryManagerMXBean mmBean : ManagementFactory
        .getMemoryManagerMXBeans()) {
      System.out.printf("Memory manager name: %s\n", mmBean.getName());
      System.out.printf("  isValid : %b\n", mmBean.isValid());
      for (String pool : mmBean.getMemoryPoolNames()) {
        System.out.printf("  Memory pool name : %s\n", pool);
      }
      System.out.println();
    }
  }
}

Résultat :
Memory manager name: CodeCacheManager
  isValid : true
  Memory pool name : Code Cache

Memory manager name: Copy
  isValid : true
  Memory pool name : Eden Space
  Memory pool name : Survivor Space

Memory manager name: MarkSweepCompact
  isValid : true
  Memory pool name : Eden Space
  Memory pool name : Survivor Space
  Memory pool name : Tenured Gen
  Memory pool name : Perm Gen
  Memory pool name : Perm Gen [shared-ro]
  Memory pool name : Perm Gen [shared-rw]

 

32.15.5. L'interface MemoryMXBean

Cet MXBean permet de surveiller et de gérer la mémoire de la JVM.

Une instance de l'interface MemoryMXBean est obtenue en invoquant la méthode getMemoryMXBean() de la fabrique ManagementFactory.

L'ObjectName pour l'unique instance de cet MXBean est : java.lang:type=Memory

Cet MXBean permet d'obtenir plusieurs informations :

Il permet aussi de demander une exécution du ramasse-miettes en invoquant sa méthode gc() et d'activer ou non les traces relatives à la gestion de la mémoire sur la sortie standard grâce à la méthode setVerbose().

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

public class TestMemoryMXB {

  public static void main(String[] args) {
    MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
    
    System.out.printf("Heap Memory Usage :\n%s \n", 
      afficherMemoire(mbean.getHeapMemoryUsage()));
    System.out.printf("Non Heap Memory Usage :\n%s \n",     
      afficherMemoire(mbean.getNonHeapMemoryUsage()));
    System.out.printf("Object pending finalization : %d\n", 
      mbean.getObjectPendingFinalizationCount() );
    System.out.printf("isVerbose : %b\n", mbean.isVerbose() );
    System.out.println("Demande d'execution du ramasse miette");
    mbean.gc();
  }
  
  public static String afficherMemoire(MemoryUsage mu) {
    StringBuilder sb= new StringBuilder();
    sb.append("  init = "+mu.getInit()+"\n");
    sb.append("  used = "+mu.getUsed()+"\n");
    sb.append("  commited = "+mu.getCommitted()+"\n");
    sb.append("  max = "+mu.getMax()+"\n");
    return sb.toString();
  } 
}

Résultat :
Heap Memory Usage :
  init = 0
  used = 223848
  commited = 5177344
  max = 66650112
 
Non Heap Memory Usage :
  init = 33718272
  used = 13066720
  commited = 34078720
  max = 121634816
 
Object pending finalization : 0
isVerbose : false
Demande d'execution du ramasse miette

La classe MemoryUsage encapsule des données sur l'occupation de la mémoire.

Le MemoryMXBean peut émettre des notifications de deux types :

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;
      
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryNotificationInfo;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.openmbean.CompositeData;

public class TestMemoryMXBNotif implements javax.management.NotificationListener {
  private static final int UN_MEGA_OCTET = 1024 * 1024;
  private static boolean stop = false;
  
  /**
   * Gestionnaire de traitement de notifications
   */
  public void handleNotification(Notification notif, Object handback) {
    System.out.println("\nReception d'une notification");
    System.out.println("Type : " + notif.getType());
    System.out.println("Message :  " + notif.getMessage());
    System.out.println("Source objectname : " + notif.getSource());
    CompositeData cd = (CompositeData) notif.getUserData();
    MemoryNotificationInfo memInfo = MemoryNotificationInfo.from( cd );
    System.out.println( "PoolName : " + memInfo.getPoolName() );
    MemoryUsage memoryUsage = memInfo.getUsage();
    System.out.println("\nEtat de la mémoire");
    System.out.println("  init : " + memoryUsage.getInit());
    System.out.println("  used : " + memoryUsage.getUsed());
    System.out.println("  committed : " + memoryUsage.getCommitted());
    System.out.println("  max : " + memoryUsage.getMax());
    // arret des traitements
    stop = true;
  }
  
  public static void main(String[] args) throws InterruptedException {
    // définition du seuil d'utilisation de la old generation
    for (MemoryPoolMXBean mpbean : ManagementFactory.getMemoryPoolMXBeans()) {
      if (mpbean.getName().equals("Tenured Gen")) {
        if (mpbean.isUsageThresholdSupported()) {
          mpbean.setUsageThreshold(4 * UN_MEGA_OCTET);
        }
        break;
      }
    }
    // abonnement du listerner auprès du MXBean
    MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
    ((NotificationEmitter) mbean).addNotificationListener(new TestMemoryMXBNotif(),
       null, null);
    // remplissage de la mémoire
    ArrayList<byte[]> list = new ArrayList<byte[]>();
    int i = 0;
    while (!stop) {
      System.out.printf("iteration %d \n", ++i);
      list.add(new byte[UN_MEGA_OCTET]);
    }
  }
}

Résultat :
iteration 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 
Reception d'une notification
Type : java.management.memory.threshold.exceeded
Message :  Memory usage
exceeds usage threshold
Source objectname : java.lang:type=Memory
PoolName : Tenured Gen
Etat de la mémoire
  init : 4194304
  used : 4348720
  committed : 5500928
  max : 61997056

Remarque : vu le fonctionnement des différents algorithmes utilisés par le ramasse-miettes, ces notifications ne doivent pas être utilisées pour détecter un manque de mémoire.

 

32.15.6. L'interface MemoryPoolMXBean

Cet MXBean permet de surveiller les espaces de mémoire de la JVM. Une JVM a plusieurs instances de cet MXBean, une pour chaque espace de mémoire utilisé.

Les instances de l'interface MemoryPoolMXBean sont obtenues en invoquant la méthode getMemoryPoolMXBeans() de la fabrique ManagementFactory.

L'ObjectName d'une instance de cet MXBean est de la forme : java.lang:type=MemoryPool, name=xxx

Cet MXBean permet d'obtenir plusieurs informations :

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;
      
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.util.List;

public class TestMemoryPoolMXB {
  public static void main(String[] args) {
    List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
    for (MemoryPoolMXBean mpBean : memoryPoolMXBeans) {
      System.out.printf("Memory Pool Name: %s\n", mpBean.getName());
      System.out.printf("  Type: %s\n", mpBean.getType().toString());
      System.out.printf("  isValid: %b\n", mpBean.isValid());
      for (String managerName : mpBean.getMemoryManagerNames()) {
        System.out.printf("  Memory manager name : %s\n", managerName);
      }
      System.out.println();
      System.out.printf("  Usage : %s\n", mpBean.getUsage().toString());
      System.out.printf("  PeakUsage : %s\n", mpBean.getPeakUsage().toString());
      boolean bUsageThSupported = mpBean.isUsageThresholdSupported();
      System.out.printf("  UsageThresholdSupport : %b\n", bUsageThSupported);
      if (bUsageThSupported) {
        System.out.printf("  UsageThreshold : %d\n", mpBean.getUsageThreshold());
        System.out.printf("  UsageThresholdCount : %d\n",
            mpBean.getUsageThresholdCount());
        System.out.printf("  isUsageThresholdExceeded : %b\n", 
            mpBean.isUsageThresholdExceeded());
      }
      System.out.println();
      System.out.printf("  CollectionUsage: %s\n", mpBean.getCollectionUsage());
      boolean bCollectionUsageThSupported = mpBean.isCollectionUsageThresholdSupported();
      System.out.printf("  CollectionUsageThresholdSupport: %b\n",
                        bCollectionUsageThSupported);
      
      if (bCollectionUsageThSupported) {
        System.out.printf("  CollectionUsageThreshold: %d\n", 
            mpBean.getCollectionUsageThreshold());
        System.out.printf("  CollectionUsageThresholdcount: %d\n",
            mpBean.getCollectionUsageThresholdCount());
        System.out.printf("  isCollectionUsageThresholdExceeded: %b\n", 
            mpBean.isCollectionUsageThresholdExceeded());
      }
      System.out.println();
    }
  }
}

Le MemoryMXBean peut émettre des notifications de deux types :

 

32.15.7. L'interface OperatingSystemMXBean

Cet MXBean permet d'obtenir quelques informations sur le système d'exploitation.

Une instance de l'interface OperatingSystemMXBean est obtenue en invoquant la méthode getOperatingSystemMXBean() de la fabrique ManagementFactory.

L'ObjectName pour l'unique instance de cet MXBean est : java.lang:type=OperatingSystem

Cet MXBean permet d'obtenir plusieurs informations :

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;

import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;

public class TestOperatingSystemMXB {

  public static void main(String[] args) {

    OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
    System.out.printf("Arch : %s\n", osBean.getArch());
    System.out.printf("Available processeurs : %d\n", osBean.getAvailableProcessors());
    System.out.printf("Name : %s\n", osBean.getName());
    System.out.printf("System Load Average : %f\n", osBean.getSystemLoadAverage());
    System.out.printf("Version : %s\n", osBean.getVersion());
  }
}

Résultat :
Arch : x86
Available processeurs : 2
Name : Windows Vista
System Load Average : -1,000000
Version : 6.0

Remarque : il est possible d'obtenir la plupart de ces informations soit par des propriétés de la JVM soit par une API.

Si la charge système est négative c'est que cette valeur n'est pas disponible.

 

32.15.8. L'interface RuntimeMXBean

Cet MXBean permet d'obtenir des informations sur la machine virtuelle.

Une instance de l'interface RuntimeMXBean est obtenue en invoquant la méthode getRuntimeMXBean() de la fabrique ManagementFactory.

L'ObjectName pour l'unique instance de cet MXBean est : java.lang:type=Runtime

Cet MXBean permet d'obtenir plusieurs informations sur la JVM.

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.List;
import java.util.Map;

public class TestRuntimeMXB {

  public static void main(String[] args) {

    RuntimeMXBean rBean = ManagementFactory.getRuntimeMXBean();

    System.out.printf("Boot classpath : %s\n", rBean.getBootClassPath());
    System.out.printf("classpath : %s\n", rBean.getClassPath());
    System.out.println("Input argument :");
    List<String> arguments = rBean.getInputArguments();
    for (String arg : arguments) {
      System.out.println("  " + arg);
    }
    System.out.printf("Library path : %s\n", rBean.getLibraryPath());
    System.out.printf("Management spec version : %s\n", rBean
        .getManagementSpecVersion());
    System.out.printf("Name : %s\n", rBean.getName());
    System.out.printf("Spec name : %s\n", rBean.getSpecName());
    System.out.printf("Vendor : %s\n", rBean.getSpecVendor());
    System.out.printf("Spec version : %s\n", rBean.getSpecVersion());
    System.out.printf("StartTime : %d ms\n", rBean.getStartTime());
    System.out.println("System properties : %s\n");
    Map<String, String> props = rBean.getSystemProperties();
    for (String cle : props.keySet()) {
      System.out.println("  " + cle + " = " + props.get(cle));
    }
    System.out.printf("UpTime : %d ms\n", rBean.getUptime());
    System.out.printf("VmName : %s\n", rBean.getVmName());
    System.out.printf("VmVendor : %s\n", rBean.getVmVendor());
    System.out.printf("VmVersion : %s\n", rBean.getVmVersion());
    System.out.printf("isBootClassPathSupported : %b\n", rBean
        .isBootClassPathSupported());
  }
}

Remarque : il est possible d'obtenir la plupart de ces informations soit par des propriétés de la JVM soit par une API

 

32.15.9. L'interface ThreadMXBean

Cet MXBean permet d'obtenir des informations sur les threads de la JVM.

Une instance de l'interface ThreadMXBean est obtenue en invoquant la méthode getThreadMXBean() de la fabrique ManagementFactory.

L'ObjectName pour l'unique instance de cet MXBean est : java.lang:type=Threading

Cet MXBean permet d'obtenir de nombreuses informations sur les threads de la JVM.

Exemple :
package fr.jmdoudoux.dej.jmx.mxbeans;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class TestThreadMXB {

  public static void main(String[] args) {

    Thread monThread = new Thread(new Runnable() {

      @Override
      public void run() {
        try {
          Thread.sleep(50);
        } catch (InterruptedException e) {
        }
      }
    });

    monThread.setName("Mon Thread");
    monThread.start();

    ThreadMXBean tBean = ManagementFactory.getThreadMXBean();

    System.out.printf("Current thead cpu time : %d\n", tBean
        .getCurrentThreadCpuTime());
    System.out.printf("Current thread user time : %d\n", tBean
        .getCurrentThreadUserTime());
    System.out.printf("Daemon thread count : %d\n", tBean
        .getDaemonThreadCount());
    System.out.printf("Peak thread count : %d\n", tBean.getPeakThreadCount());
    System.out.printf("Thread count : %d\n", tBean.getThreadCount());
    System.out.printf("Total Started Thread count : %d\n", tBean
        .getTotalStartedThreadCount());
    System.out.println("Liste des threads");
    ThreadInfo[] threads = tBean.dumpAllThreads(false, false);
    for (ThreadInfo ti : threads) {
      System.out.printf("Thead id  : %d\n", ti.getThreadId());
      System.out.printf("  Name : %s\n", ti.getThreadName());
      System.out.printf("  State : %s\n", ti.getThreadState());
      System.out.println("  Stack : ");
      StackTraceElement[] ste = ti.getStackTrace();
      for (StackTraceElement elt : ste) {
        System.out.printf("    %s.%s() - %d\n", elt.getClassName(), elt
            .getMethodName(), elt.getLineNumber());
      }
    }
    monThread.interrupt();
  }
}

Résultat :
Current thead cpu time : 62400400
Current thread user time : 62400400
Daemon thread count : 4
Peak thread count : 6
Thread count : 6
Total Started Thread count : 6
Liste des threads
Thead id  : 8
  Name : Mon Thread
  State : TIMED_WAITING
  Stack : 
    java.lang.Thread.sleep() - -2
    fr.jmdoudoux.dej.jmx.mxbeans.TestThreadMXB$1.run() - 17
    java.lang.Thread.run() - 619
Thead id  : 5
  Name : Attach Listener
  State : RUNNABLE
  Stack : 
Thead id  : 4
  Name : Signal Dispatcher
  State : RUNNABLE
  Stack : 
Thead id  : 3
  Name : Finalizer
  State : WAITING
  Stack : 
    java.lang.Object.wait() - -2
    java.lang.ref.ReferenceQueue.remove() - 116
    java.lang.ref.ReferenceQueue.remove() - 132
    java.lang.ref.Finalizer$FinalizerThread.run() - 159
Thead id  : 2
  Name : Reference Handler
  State : WAITING
  Stack : 
    java.lang.Object.wait() - -2
    java.lang.Object.wait() - 485
    java.lang.ref.Reference$ReferenceHandler.run() - 116
Thead id  : 1
  Name : main
  State : RUNNABLE
  Stack : 
    sun.management.ThreadImpl.dumpThreads0() - -2
    sun.management.ThreadImpl.dumpAllThreads() - 374
    fr.jmdoudoux.dej.jmx.mxbeans.TestThreadMXB.main() - 36

 

32.15.10. La sécurisation des accès à l'agent

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

 

32.16. Des recommandations pour l'utilisation de JMX

Il faut être vigilant sur les ObjectNames associés aux MBeans en définissant des conventions de nommage notamment pour permettre leur organisation d'une façon hiérarchique dans les clients JMX. Il peut par exemple être intéressant d'utiliser un ou plusieurs attributs pour structurer cette hiérarchie et toujours avoir un attribut qui précise le type du MBean.

Pour améliorer la portabilité, il est préférable d'utiliser les Open MBeans ou les Open Types pour les structures de données des MBeans plutôt que des types personnalisés.

Il est préférable d'utiliser les MBeans standard lorsque cela est possible car ils sont faciles à écrire et à maintenir.

Le monitoring implique généralement la récupération et l'exploitation de différentes données : états, métriques, statistiques, ... Pour l'exploitation de ces données, leur affichage peut avoir des conséquences sur les fonctionnalités des MBeans.

Pour avoir une vision d'ensemble au niveau d'un domaine regroupant plusieurs applications et au niveau de tout ou partie du système d'informations, il faut agréger les données collectées de toutes les JVM.

Typiquement un client de monitoring interroge périodiquement le système pour afficher des données fraîches. Ce procédé peut être consommateur en terme de ressources (CPU, bande passante, ...). Les notifications peuvent être une solution dans certains cas mais mal utilisées cela peut être encore pire. Elles sont toujours plus intéressantes lorsqu'une donnée évolue peu : dans ce cas, il est préférable d'émettre une notification à chaque changement de valeur plutôt que de périodiquement récupérer une valeur qui sera fréquemment identique.

 

32.17. Des ressources

La page principale de la technologie JMX

https://www.oracle.com/java/technologies/javase/javamanagement.html

La documentation de JMX

https://www.oracle.com/java/technologies/javase/docs-jmx-jsp.html

La documentation de l'API JMX

https://docs.oracle.com/javase/1.5.0/docs/guide/jmx/

JMX best practice

https://www.oracle.com/java/technologies/javase/management-extensions-best-practices.html

mx4j est une implémentation open source de JMX

http://mx4j.sourceforge.net/

JbossMX est un implémentation open source de JMX

https://community.jboss.org/wiki/JBossMX

MC4J est un client JMX open source

https://sourceforge.net/projects/mc4j/

 


31. Le scripting 33. L'API Service Provider (SPI) Imprimer Index Index avec sommaire Télécharger le PDF    
Développons en Java
v 2.40   Copyright (C) 1999-2023 .