IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

 

Développons en Java   2.30  
Copyright (C) 1999-2022 Jean-Michel DOUDOUX    (date de publication : 15/06/2022)

[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

 

93. Spring et JMX

 

chapitre    9 3

 

Niveau : niveau 4 Supérieur 

 

Comme pour d'autres API, Spring propose des fonctionnalités qui facilitent la mise en oeuvre de JMX sans avoir forcement à utiliser directement cette API de bas niveau.

Spring permet d'enregistrer n'importe quel bean gérer dans le contexte sous la forme d'un Model MBean simplement en utilisant une instance d'un composant de type MBeanExporter.

Le composant MBeanExporter permet d'enregistrer des model MBeans dont l'instance est un bean géré par le conteneur Spring. Cela permet d'exposer de simple POJO sans avoir à créer une interface ni respecter une convention de nommage particulière.

Ce chapitre contient plusieurs sections :

 

93.1. L'enregistrement d'un bean en tant que MBean

Un objet de type org.springframework.jmx.export.MBeanExporter est responsable de l'enregistrement d'un bean géré par le conteneur Spring sous la forme d'un MBean dans un MBeanServer. Il sera alors possible d'utiliser ce bean grâce à JMX.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
public class MonBean {
  private String maPropriete;
  private long longueur;
  public int maValeur;
  
  public String getMaPropriete() {
    return maPropriete;
  }
  
  public void monOperation() {
    System.out.println(maPropriete);
  }
  
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
  }
}

L'exemple ci-dessus va servir de base pour un MBean à exporter grâce à JMX.

 

93.1.1. La classe MBeanExporter

Pour exporter des beans, il suffit de déclarer dans le contexte un bean de type org.springframework.jmx.export.MBeanExporter. La propriété lazy-init doit toujours être à false pour ce type de beans.

La propriété beans de cette classe est une collection de type map qui permet de préciser l'objectName du MBean comme clé et le bean correspondant comme valeur.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
 
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
 
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter"
    lazy-init="false">
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
</beans>

L'application n'a pas besoin d'utiliser l'API JMX pour obtenir le serveur de MBeans et enregistrer le MBean : il suffit simplement de créer le contexte Spring. Il est nécessaire de demander l'activation de JMX à la JVM en lui passant au minimum le paramètre -Dcom.sun.management.jmxremote

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
public class MonBean {
  private String maPropriete;
  private String maSecondePropriete;
  private long longueur;
  public int maValeur;
  
  public String getMaPropriete() {
    return maPropriete;
  }
  
  public String getMaSecondePropriete() {
    return maSecondePropriete;
  }
  
  public void maSecondeOperation() {
    System.out.println(maPropriete);
  }
  
  public void monOperation() {
    System.out.println(maPropriete);
  }
  
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
  }
  
  public void setMaSecondePropriete(final String maSecondePropriete) {
    this.maSecondePropriete = maSecondePropriete;
  }
}

Il est alors possible de voir le MBean dans un client JMX comme l'outil jconsole.

Par défaut, toutes les propriétés publiques de la classe (possédant un getter et/ou un setter) sont exposées comme propriétés du MBean et toutes les méthodes publiques sauf celles héritées de la classe Object sont exposées comme opérations du MBean.

Il est possible, pour des besoins spécifiques, de définir plusieurs MBeanExporter dans le contexte.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
 
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
 
  <bean id="monAutreBean" class="fr.jmdoudoux.dej.spring.jmx.MonAutreBean" />
  <bean id="mbeanExporter1" class="org.springframework.jmx.export.MBeanExporter"
    lazy-init="false">
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
 
  <bean id="mbeanExporter2" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monAutreBean" value-ref="monAutreBean" />
      </map>
    </property>
 
  </bean>
 
  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
    <property name="attributeSource">
  
  <bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
    </property>
  </bean>
</beans>

La propriété booléenne autodetect de la classe MBeanExporter permet de demander l'enregistrement automatique des beans du contexte qui sont des MBeans comme définis par la spécification JMX.

La propriété registrationBehaviorName permet de préciser le comportement du MBeanExporter selon trois valeurs :

  • REGISTRATION_FAIL_ON_EXISTING : si un MBean est déjà enregistré avec l'objectName, alors une exception de type InstanceAlreadyExistsException est levée. C'est le comportement par défaut
  • REGISTRATION_IGNORE_EXISTING : si un MBean est déjà enregistré avec l'objectName, alors l'enregistrement du MBean est simplement ignoré sans lever d'exception
  • REGISTRATION_REPLACE_EXISTING : un MBean déjà enregistré avec l'objectName est désenregistré et le nouveau MBean est enregistré dans le serveur en remplacement

 

93.1.2. La création d'un MBeanServer

Le composant MBeanExporter tente de trouver un MBeanServer pour y enregistrer le MBean. Si aucun MBeanServer n'existe, il est possible d'utiliser le composant MBeanServerFactoryBean pour en créer un.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
</beans>

La propriété localeExistingServerIfPossible avec la valeur true permet de demander la création d'un nouveau serveur de MBeans uniquement si aucun n'est trouvé.

Si plusieurs serveurs de MBeans sont lancés, la propriété agentId permet de fournir l'identifiant du serveur qui sera retourné par la fabrique.

La propriété mbeanServer du composant MBeanExporter permet de préciser le serveur de Mbeans qui doit être utilisé pour l'enregistrement des MBeans

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
    <property name="server" ref="mbeanServer" />
  </bean>
</beans>

 

93.1.3. L'accès distant au serveur de MBeans

Spring facilite la mise en oeuvre de la JSR 160 (JMX Remote API) pour permettre l'accès distant aux MBeans. Pour permettre un accès distant à un serveur de MBeans, il faut utiliser un connecteur encapsulé dans une instance obtenue en utilisant la classe ConnectorServerFactoryBean.

La classe ConnectorServerFactoryBean peut utiliser plusieurs protocoles notamment JMXMP et RMI. Par défaut, elle utilise le protocole JMXMP.

Exemple :
  <bean class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>

L'url par défaut est service:jmx:jmxmp://localhost:9875

Le protocole JMXNP n'est pas supporté en standard.

Résultat :
Caused by: java.net.MalformedURLException: Unsupported protocol: jmxmp
      at javax.management.remote.JMXConnectorServerFactory.newJMXConnectorServer(Unknown
Source)
      at org.springframework.jmx.support.ConnectorServerFactoryBean.afterPropertiesSet
(ConnectorServerFactoryBean.java:144)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.
invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.
initializeBean(AbstractAutowireCapableBeanFactory.java:1417)

Il faut ajouter le fichier jmxremote_optional.jar dans le classpath. Pour obtenir ce fichier, il faut télécharger le fichier jmx_remote-1_0_1_03-ri.zip sur le site d'Oracle.

La classe ConnectorServerFactoryBean peut aussi mettre en oeuvre d'autres protocoles : par exemple, un connecteur utilisant RMI.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />

  <bean class="org.springframework.jmx.support.ConnectorServerFactoryBean"
    depends-on="rmiRegistry">
    <property name="objectName" value="connector:name=rmi" />
    <property name="serviceUrl"     
      value="service:jmx:rmi://localhost/jndi/rmi://localhost:8099/monconnector"/>
    <property name="server" ref="mbeanServer"></property>
  </bean>
  
  <bean id="rmiRegistry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
    <property name="port" value="8099" />
  </bean>
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
  
  <bean id="assembler"
    class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
    <property name="attributeSource">
      <bean
        class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"
      />
    </property>
  </bean>
</beans>

Un bean de type RmiRegistryFactoryBean est défini dans le contexte. C'est une fabrique qui tente de trouver un registre RMI et d'en créer un si la recherche échoue. Le port utilisé par le registre est précisé grâce à la propriété port.

Un bean de type ConnectorServerFactoryBean est défini avec une dépendance sur le bean qui encapsule le registre RMI comme le recommande la documentation Spring pour garantir que le registre sera démarré avant son utilisation par le connecteur. La classe ConnectorServerFactoryBean est une fabrique d'objets de type JmxConnectorServer défini dans la JSR 160.

La classe ConnectorServerFactoryBean possède plusieurs attributs :

Attributs

Rôle

boolean daemon

Préciser si les threads démarrés pour le connecteur sont des démons

Properties environment

Fournir des propriétés utilisées pour construire l'instance du connecteur

Map<String,?> environment

Fournir des propriétés utilisées pour construire l'instance du connecteur

Object objectName

Préciser l'objectName utilisé pour enregistrer le connecteur dans le serveur de MBeans

String serviceUrl

Définir l'url du service permettant d'invoquer le connecteur

boolean threaded

Préciser si le connecteur doit être démarré dans un thread dédié


Il est important que le port précisé dans l'url et le registre RMI soient les mêmes et ne soient pas déjà utilisés sur la machine.

Pour sécuriser l'accès au serveur de MBeans, il est possible de fournir des informations à la propriété environment pour permettre l'authentification et les autorisations. L'exemple ci-dessous utilise une JVM de Sun/Oracle.

Exemple :
  <bean class="org.springframework.jmx.support.ConnectorServerFactoryBean"
    depends-on="rmiRegistry">
    <property name="objectName" value="connector:name=rmi" />
    <property name="serviceUrl"
      value="service:jmx:rmi://localhost/jndi/rmi://localhost:8099/monconnector"/>
    <property name="server" ref="mbeanServer"></property>
    <property name="environment">
      <map>
        <entry key="jmx.remote.x.password.file" value="C:/monapp/jmxremote.password" />
        <entry key="jmx.remote.x.access.file" value="C:/monapp/jmxremote.access"/>
      </map>
    </property>
  </bean>

Il est alors nécessaire de préciser l'utilisateur et son mot de passe définis dans les deux fichiers.

 

93.1.4. Les listeners d'un Exporter

L'interface MBeanExporterListener définit les méthodes de callback d'un listener lorsqu'un MBean est enregistré ou supprimé d'un serveur de MBeans.

Méthode

Rôle

void mbeanRegistered(ObjectName objectName)

Méthode invoquée lorsqu'un MBean est correctement enregistré dans un serveur de MBeans

void mbeanUnregistered(ObjectName objectName)

Méthode invoquée lorsqu'un MBean est correctement supprimé d'un serveur de MBeans


Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import javax.management.ObjectName;
import org.springframework.jmx.export.MBeanExporterListener;

public class MonMBeanExporterListener implements MBeanExporterListener {
  @Override
  public void mbeanRegistered(final ObjectName arg0) {
    System.out.println("Enregistrement du MBean " + arg0);
  }
  
  @Override
  public void mbeanUnregistered(final ObjectName arg0) {
    System.out.println("Suppression du MBean " + arg0);
  }
}

La propriété listeners de la classe MBeanExporter permet de définir des listeners de type MBeanExporterListener qui seront invoqués par le MBeanExporter.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="monMBeanExporterListener"
    class="fr.jmdoudoux.dej.spring.jmx.MonMBeanExporterListener" />
    
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
    <property name="server" ref="mbeanServer" />
    <property name="listeners">
      <array>
        <ref bean="monMBeanExporterListener" />
      </array>
    </property>
  </bean>
</beans>

 

93.2. Le nommage des MBeans

Tout MBean doit avoir un nom unique dans le serveur de MBeans dans lequel il est enregistré. Le nom d'un MBean est encapsulé dans un objet de type ObjectName.

 

93.2.1. Les stratégies de nommage des MBeans

Un MBeanExporter utilise une stratégie de nommage pour déterminer l'ObjectName d'un MBean : il délègue la définition d'un objectName à une instance de type ObjectNamingStrategy lorsqu'il doit enregistrer une instance d'un bean dans un serveur de MBeans.

Spring propose en standard plusieurs stratégies de nommage des MBeans.

 

93.2.1.1. L'interface ObjectNamingStrategy

Une stratégie de nommage doit implémenter l'interface ObjectNamingStrategy.

Elle ne définit qu'une seule méthode :

Méthode

Rôle

ObjectName getObjectName(Object managedBean, String beanKey)

Obtenir l'ObjectName pour l'instance courante du bean


Cette méthode sera invoquée par le MBeanExporter pour obtenir l'ObjectName avec lequel il va enregistrer le MBean.

 

93.2.1.2. La classe IdentityNamingStrategy

La classe IdentityNamingStrategy implémente l'interface ObjectNamingStrategy.

Son implémentation définit un ObjectName en utilisant la valeur de hachage de l'instance du bean.

La valeur de l'ObjectName retournée est de la forme
package:class=nom_de_la_classe,hashCode=valeur_de_hachage_de_l_instance

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map> 
        <entry key="monBean" value-ref="monBean" />
      </map>
    </property>
    <property name="namingStrategy">
      <bean class="org.springframework.jmx.export.naming.IdentityNamingStrategy"/>
    </property>
  </bean>
</beans>

Avec l'exemple ci-dessus, le MBean est enregistré dans le serveur de MBeans avec l'objectName
fr.jmdoudoux.dej.spring.jmx:hashCode=1a68ef9,type=MonBean

 

93.2.1.3. La classe KeyNamingStrategy

La classe KeyNamingStrategy implémente l'interface ObjectNamingStrategy.

Par défaut, cette implémentation donne à l'objectName la valeur fournie dans le Map comme clé de la propriété beans du MBeanExporter.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
    <property name="namingStrategy">
      <bean class="org.springframework.jmx.export.naming.KeyNamingStrategy">
      </bean>
    </property>
  </bean>
</beans>

Il est aussi possible de définir explicitement la valeur de la clé sous la forme de Properties en utilisant les méthodes setMappings(), setMappingLocation() ou setMappingLocations().

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="monBean" value-ref="monBean" />
      </map>
    </property>
    <property name="namingStrategy">
      <bean class="org.springframework.jmx.export.naming.KeyNamingStrategy">
        <property name="mappings">
          <props>
            <prop key="monBean">monAppli:name=monBean,type=MonBean</prop>
          </props>
        </property>
      </bean>
    </property>
  </bean>
</beans>

Il est possible d'utiliser la propriété mappingLocations dont la valeur précise un ou plusieurs fichiers .properties séparés par des virgules. La valeur de l'objectName sera alors recherchée dans ces fichiers qui doivent être dans le classpath en utilisant la valeur de la propriété key du bean. Si aucune clé correspondante n'est trouvée dans les fichiers properties alors c'est la valeur correspondante au bean dans la propriété mappings qui est utilisée.

 

93.2.1.4. La classe MetadataNamingStrategy

La classe MetadataNamingStrategy implémente l'interface ObjectNamingStrategy.

Son implémentation recherche la valeur de l'ObjectName dans les métadonnées contenues dans la classe du bean.

Cette stratégie s'utilise avec une instance de type JmxAttributeSource qui va se charger de lire les métadonnées. Cette instance peut être fournie en utilisant la surcharge du constructeur qui attend un paramètre de ce type ou utiliser la méthode setAttributeSource().

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter"
    lazy-init="false">
    <property name="autodetect" value="true"></property>
    <property name="namingStrategy" ref="namingStrategy"></property>
    <property name="assembler" ref="assembler"></property>
  </bean>
  
  <bean id="attributeSource" 
    class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource" />

  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
    <property name="attributeSource" ref="attributeSource" />
  </bean>
  
  <bean id="namingStrategy" 
    class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
    <property name="attributeSource" ref="attributeSource" />
  </bean>
</beans>

Dans l'exemple, ci-dessus la classe du bean à exposer comme MBean est annotée avec @ManagedResource détaillée dans les sections suivantes.

Si l'attribut objectName n'est pas précisé dans l'annotation @ManagedResource alors l'objectName utilisé sera de la forme nom_complet_du_package:type=nom_de_la_classe,name=nom_du_bean

 

93.2.2. L'interface SelfNaming

L'interface SelfNaming permet à un composant de déterminer dynamiquement l'objectName qui sera utilisé par l'Exporter pour exposer l'instance du bean sous la forme d'un MBean.

L'utilisation de cette interface est notamment nécessaire si plusieurs instances d'une même classe doivent être exposées comme MBean. La convention utilisée par Spring impliquerait dans ce cas des doublons dans les objectNames, ce qui est impossible.

Il faut alors que la classe implémente l'interface SelfNaming et redéfinir sa méthode getObjectName() qui renvoie une instance de type ObjectName. La valeur de l'objet retourné doit être unique pour un même serveur de MBeans.

Au moment de l'enregistrement du bean dans le serveur de MBeans, Spring invoquera cette méthode pour obtenir l'objectName sous lequel l'instance sera enregistrée.

 

93.3. Les Assembler

Un MBeanExporter utilise une instance de l'interface org.springframework.jmx.export.assembler.MBeanInfoAssembler qui va permettre de déterminer les fonctionnalités exposées par le MBean. Spring propose plusieurs implémentations de l'interface MBeanInfoAssembler en standard :

  • SimpleReflectiveMBeanInfoAssembler : expose par introspection toutes les propriétés et les méthodes publiques
  • InterfaceBaseMBeanInfoAssembler : expose les propriétés et les méthodes du MBean définies dans une interface
  • MetadataMBeanInfoExporter : utilise des annotations pour déterminer les propriétés et les opérations du MBean à exposer (c'est l'implémentation par défaut)
  • MethodExclusionMBeanInfoAssembler : expose toutes les propriétés et les méthodes du MBean sauf celles précisées
  • MethodNameBasedMBeanInfoAssembler : expose toutes les méthodes précisées du MBean. Les méthodes de type getters/setters sont exposées comme des propriétés JMX

Pour associer un Assembler à un Exporter, il faut utiliser la propriété assembler de l'Exporter.

 

93.3.1. La classe MethodNameBasedMBeanInfoAssembler

L'Assembler le plus simple est implémenté par la classe MethodNameBasedMBeanInfoAssembler qui permet de définir le nom des méthodes à exposer. Les méthodes de type getter et setter exposent automatiquement la propriété correspondante.

Il faut définir dans le contexte un bean de type MethodNameBasedMBeanInfoAssembler et passer à sa propriété managedMethods une collection des noms des méthodes à exposer.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>

  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>    
  </bean>
  
  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
    <property name="managedMethods">
      <list>
        <value>monOperation</value>
        <value>getMaPropriete</value>
      </list>
    </property>
  </bean>
</beans>

Le résultat est le suivant en connectant la JVM à JConsole.

La propriété methodMapping permet pour chaque MBean de préciser les méthodes qui doivent être exposées par JMX. C'est un objet de type Properties dont la clé est l'objectName du MBean et la valeur est l'ensemble des méthodes à exposer séparées chacunes par un caractère virgule.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
  
  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
    <property name="methodMappings">
      <props>
        <prop key="monAppli:name=monBean">
          getMaPropriete,monOperation
          </prop>
      </props>
    </property>
  </bean>
</beans>

Les noms de méthodes qui n'existent pas dans la classe sont simplement ignorés.

La propriété notificationInfoMappings permet de déclarer les notifications du MBean auxquelles il est possible de s'abonner.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>

  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
    <property name="notificationInfoMappings">
      <map>
        <entry key="monAppli:name=monBean">
          <bean class="org.springframework.jmx.export.metadata.ManagedNotification">
            <property name="name" value="Ma notification" />
            <property name="description"
              value="Modification de la valeur d'une propriété" />
            <property name="notificationTypes" value="MiseAJour" />
          </bean>
        </entry>
      </map>
    </property>
  </bean>
</beans>

Dans l'exemple ci-dessus, seule la notification «MiseAJour» du MBean est exposée grâce à JMX.

 

93.3.2. La classe MethodExclusionMBeanInfoAssembler

La classe MethodExclusionMBeanInfoAssembler permet de définir le nom des méthodes qui ne seront pas exposées. Toutes les autres méthodes publiques de la classe seront exposées automatiquement.

Il faut définir dans le contexte un bean de type MethodExclusionMBeanInfoAssembler et passer à sa propriété ignoredMethods un tableau contenant les noms des méthodes à ne pas exposer.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
  
  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.MethodExclusionMBeanInfoAssembler">
    <property name="ignoredMethods">
      <list>
        <value>monOperation</value>
      </list>
    </property>
  </bean>
</beans>

Cette définition concerne tous les MBeans qui seront exposés.

Il est aussi possible d'utiliser sa propriété ignoredMethodMappings qui est de type Properties. Elle permet de définir pour chaque MBean les méthodes à exclure. La clé est l'objecName du MBean et la valeur est une collection des noms des méthodes à ne pas exposer chacun séparé par une virgule.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
  
  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.MethodExclusionMBeanInfoAssembler">
    <property name="ignoredMethodMappings">
      <props>
        <prop key="monAppli:name=monBean">
          setMaPropriete,monOperation </prop>
      </props>
    </property>
  </bean>
</beans>

 

93.3.3. La classe InterfaceBasedMBeanInfoAssembler

La classe InterfaceBasedMBeanInfoAssembler permet de préciser une liste d'interfaces dont les méthodes seront exposées par les MBeans. Le mécanisme standard de JMX pour identifier un MBean et les fonctionnalités qu'il doit exposer reposent sur une interface qui doit respecter une convention de nommage précise. La classe InterfaceBasedMBeanInfoAssembler utilise une ou plusieurs interfaces, dont le nom est libre, uniquement pour déterminer les fonctionnalités à exposer.

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

public interface IMonBean {
  public abstract String getMaPropriete();
  public abstract void monOperation();
  public abstract void setMaPropriete(final String maPropriete);
}

Par défaut, toutes les méthodes définies par des interfaces sont exposées.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.Notification;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;

public class MonBean implements NotificationPublisherAware, IMonBean {
  private String maPropriete;
  private NotificationPublisher notificationPublisher;
  private AtomicInteger sequence = new AtomicInteger();
  
  @Override
  public String getMaPropriete() {
    return maPropriete;
  }
  
  public void maSecondeOperation() {
    System.out.println("Seconde operation");
  }
  
  @Override
  public void monOperation() {
    System.out.println(maPropriete);
  }
  
  @Override
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
    notificationPublisher.sendNotification(new Notification("MiseAJour", this,
        sequence.getAndIncrement(), "Modification de la propriété avec la valeur "
           + maPropriete));
  }
  
  @Override
  public void setNotificationPublisher(
      final NotificationPublisher notificationPublisher) {
    this.notificationPublisher = notificationPublisher;
  }
}
Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean"
    class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer"
    class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
  
  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
  </bean>
</beans>

Il n'est pas toujours souhaité d'exposer toutes les méthodes de toutes les interfaces. Dans l'exemple ci-dessus, c'est notamment le cas pour l'interface NotificationPublisherAware. Il est possible de préciser la ou les interfaces dont les méthodes seront exposées en utilisant la propriété managedInterfaces.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
  
  <bean id="assembler" 
    class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
  <property name="managedInterfaces">
    <list>
      <value>fr.jmdoudoux.dej.spring.jmx.IMonBean</value>
    </list>
  </property>
  </bean>
</beans>

La propriété interfaceMappings permet de définir pour chaque MBean, la ou les interfaces contenant les méthodes à exposer. Cette propriété est de type Properties : la clé est l'objectName du MBean et la valeur est une liste des interfaces séparées par des virgules.

 

93.3.4. La classe MetadataBasedMBeanInfoAssembler

L'assembler MetadataMBeanInfoAssembler utilise des métadonnées sur les beans pour déterminer les fonctionnalités à exposer par le MBean.

Il faut définir un bean de type MetadataMBeanInfoAssembler dans le contexte et associer sa référence à la propriété assembler de l'Exporter.

La propriété attributSource permet de préciser les métadonnées qui sont utilisées en lui passant une instance de type org.springframework.jmx.export.metadata.JmxAttributes. Spring propose en standard deux implémentations de cette interface selon la version utilisée :

  • jusqu'à la version 2.5 : la classe AttributesJmxAttributeSource qui recherche des attributs
  • à partir de la version 3.0 : la classe AnnotationJmxAttributeSource qui recherche des annotations

L'avantage d'utiliser les annotations est que le compilateur effectue une vérification lors de ses traitements.

 

93.3.4.1. L'utilisation d'attributs comme métadonnées

Jusqu'à la version 2.5 de Spring, la classe org.springframework.jmx.export.metadata.AttributesJmxAttributeSource recherchait des attributs particuliers permettant de fournir les informations nécessaires à l'exposition d'un MBean.

Spring propose plusieurs attributs :

  • org.springframework.jmx.export.metadata.ManagedRessource : s'utilise sur une classe pour déclarer que ses instances seront exposées comme MBean
  • org.springframework.jmx.export.metadata.ManagedAttribute : s'utilise uniquement sur des méthodes de type getter ou setter pour exposer un attribut
  • org.springframework.jmx.export.metadata.ManagedOperation : s'utilise sur des méthodes qui ne sont pas de type getter ou setter pour exposer une opération
  • org.springframework.jmx.export.metadata.ManagedOperationParameter : s'utilise sur des méthodes pour décrire des paramètres
  • org.springframework.jmx.export.metadata.ManagedMetric
  • org.springframework.jmx.export.metadata.ManagedNotification

Ces attributs possèdent des propriétés qui sont identiques à celles proposées par les annotations correspondantes décrites dans la section suivante.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
  /**
   * @@org.springframework.jmx.export.metadata.ManagedResource
   *   (description="Mon MBean de test avec attributs", 
   *   objectName="monAppli:type=mesBeans,name=MonBeanAvecAttributs",
   *   log=true, logFile="jmx.log", currencyTimeLimit=15)
   */
  public class MonBeanAvecAttributs {
  
  private String maPropriete;
  
  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   * (description="Ma propriete",
   * currencyTimeLimit=15)
   */
  public String getMaPropriete() {
    return maPropriete;
  }
  
  /**
   * @@org.springframework.jmx.export.metadata.ManagedOperation
   * (description="Mon operation")
   */
  public void monOperation() {
    System.out.println(maPropriete);
  }
  
  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   * (description="Ma propriete")
   */
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
  }
}

Java ne propose pas en standard le support de ce type d'attributs : il est nécessaire d'utiliser l'outil Apache Commons Attributes qui propose un compilateur générant du code source Java à partir des attributs qu'il va rencontrer. Ce compilateur est utilisable avec Ant et Maven.

Exemple :
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  http://maven.apache.org/xsd/maven-4.0.0.xsd">
  
  <modelVersion>4.0.0</modelVersion>
  <groupId>TestSpringJMX25</groupId>
  <artifactId>TestSpringJMX25</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>commons-attributes-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>2.5.6</version>
    </dependency>
  
    <dependency>
      <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>2.5.6</version>
    </dependency>
    
    <dependency>
      <groupId>commons-attributes</groupId>
      <artifactId>commons-attributes-api</artifactId>
      <version>2.2</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>2.5.6</version>
    </dependency>
  </dependencies>
</project>
Résultat :
C:\Documents
and Settings\jm\Documents\workspace\TestSpringJMX25>mvn clean compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Unnamed - TestSpringJMX25:TestSpringJMX25:jar:0.0.1-SNAPSHOT
[INFO]    task-segment: [clean, compile]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
[INFO] Deleting directory C:\Documents and Settings\jm\Documents\workspace 
\TestSpringJMX25\target
[INFO] [commons-attributes:compile {execution: default}]
[INFO] [resources:resources {execution: default-resources}]
[INFO] Copying 1 resource
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Compiling 4 source files to C:\Documents and Settings\jm\Documents\worksp
ace\TestSpringJMX25\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Wed Feb 06 20:46:46 CET 2013
[INFO] Final Memory: 11M/28M
[INFO]
------------------------------------------------------------------------

Le compilateur d'attributs génère un fichier MonBeanAvecAttributs$__attributeRepository.java dans le sous-répertoire target/generated-sources. Ce fichier sera alors compilé pour être utilisé lors de l'exécution de l'application.

Le fichier de description du contexte Spring et la classe principale qui se charge de lire et de créer ce contexte n'ont rien de particulier.

Dans l'exemple ci-dessus, un fichier de log est créé par le MBean pour journaliser ses activités.

Résultat :
LogMsg: Wed Feb 06 20:50:28 CET 2013 jmx.attribute.change
AttributeChangeDetected Name = MaPropriete Old value = null New value =
maValeur

Attention : l'utilisation des attributs comme métadonnées n'est plus utilisable à partir de Spring 3.0 : il est nécessaire d'utiliser les annotations.

 

93.3.4.2. L'utilisation d'annotations comme métadonnées

La classe org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource est utilisée pour rechercher des annotations particulières qui permettent de fournir les informations nécessaires à l'exposition d'un MBean.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
 
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
 
  <bean id="mbeanServer" 
    class="org.springframework.jmx.support.MBeanServerFactoryBean"> 
    <property name="locateExistingServerIfPossible"
      value="true" />
  </bean>
 
  <bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">  
    <property name="assembler" ref="assembler" />
    <property name="server" ref="mbeanServer" />
    <property name="beans">
      <map>
        <entry key="monAppli:name=monBean" value-ref="monBean" />
      </map>
    </property>
  </bean>
 
  <bean id="assembler"
    class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
    <property name="attributeSource">
      <bean
        class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"
      />
    </property>
  </bean>
</beans>

Une exception est levée au chargement du contexte si un bean doit être exposé et qu'aucune métadonnée n'est présente.

Résultat :
Exception in thread "main"
org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'mbeanExporter' defined in class path resource [appContext.xml]:
Invocation of init method failed; nested exception is org.springframework.jmx.export.
UnableToRegisterMBeanException:
Unable to register MBean [fr.jmdoudoux.dej.spring.jmx.MonBean@1d10a5c] with
key 'monAppli:name=monBean'; nested exception is
org.springframework.jmx.export.MBeanExportException: Could not create
ModelMBean for managed resource [fr.jmdoudoux.dej.spring.jmx.MonBean@1d10a5c]
with key 'monAppli:name=monBean'; nested exception is
org.springframework.jmx.export.metadata.InvalidMetadataException: No
ManagedResource attribute found for class: class
fr.jmdoudoux.dej.spring.jmx.MonBean

 

93.4. L'utilisation des annotations

Spring propose plusieurs annotations pour déclarer et définir des MBeans notamment :

  • @ManagedResource : s'utilise sur la classe du bean pour déclarer qu'une classe est exposable sous la forme d'un MBean
  • @ManagedAttribute : s'utilise sur une propriété du bean pour la définir comme exposable grâce à JMX
  • @ManagedOperation : s'utilise sur une méthode du bean pour la définir comme exposable grâce à JMX

Pour exploiter ces annotations, il y a plusieurs solutions :

  • définir un MBeanExporter en lui associant un MetadataMBeanInfoAssembler qui utilise un AnnotationJmxAttributeSource
  • définir un AnnotationMBeanExporter
  • utiliser le tag <context:mbean-export>

Un composant de type org.springframework.jmx.export.annotation.AnnotationMBeanExporter va détecter automatiquement les beans définis dans le contexte qui sont annotés avec les annotations @ManagedXXX.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer"
    class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter"
    class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
    <property name="server" ref="mbeanServer" />
  </bean>
</beans>

Par défaut, le domaine utilisé dans l'objectName des MBeans est le nom du package de la classe, le nom de l'objectname est le nom du bean utilisé dans le contexte et le type est le nom de la classe.

Résultat :
13 janv. 2013 17:24:41 org.springframework.context.support.AbstractApplicationContext
prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2808b3:
startup date [Sun Jan 13 17:24:41 CET 2013]; root of context hierarchy
13 janv. 2013 17:24:41 org.springframework.beans.factory.xml.XmlBeanDefinitionReader
loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [appContext.xml]
13 janv. 2013 17:24:41 org.springframework.beans.factory.support.DefaultListableBeanFactory
preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.
DefaultListableBeanFactory@141b571:
defining beans [monBean,mbeanServer,mbeanExporter]; root of factory hierarchy
13 janv. 2013 17:24:41 org.springframework.jmx.export.MBeanExporter afterPropertiesSet
INFO: Registering beans for JMX exposure on startup
13 janv. 2013 17:24:41 org.springframework.jmx.export.MBeanExporter autodetect
INFO: Bean with name 'monBean' has been autodetected for JMX exposure
13 janv. 2013 17:24:42 org.springframework.jmx.export.MBeanExporter registerBeanInstance
INFO: Located managed bean 'monBean': registering with JMX server as MBean
[fr.jmdoudoux.dej.spring.jmx:name=monBean,type=MonBean]
13 janv. 2013 17:24:42 org.springframework.jmx.export.MBeanExporter getMBeanInfo
ATTENTION: Bean with key 'monBean' has been registered as an MBean but has no exposed
attributes or operations

La propriété defaultDomain permet de fournir le nom du domaine utilisé dans l'object name.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter"
    class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
    <property name="server" ref="mbeanServer" />
    <property name="defaultDomain" value="monAppli" />
  </bean>
</beans>

Le résultat est le suivant en connectant la JVM à l'outil jconsole.

L'attribut objectName de l'annotation @ManagedResource permet de préciser l'object name qui sera utilisé pour enregistrer le MBean. Cette solution n'est utilisable que pour des beans ayant la portée singleton.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(objectName = "monAppli:type=mesBeans,name=MonBean")
public class MonBean {
}

Les classes qui doivent être exposées comme des MBeans en utilisant les annotations @ManagedXXX ne devraient pas implémenter d'interfaces ayant le suffixe MBean ou MXBean.

 

93.4.1. L'annotation @ManagedResource

L'annotation @ManagedResource, qui s'utilise sur une classe, permet d'indiquer que le bean doit être exposé comme un MBean.

Elle possède plusieurs attributs qui seront utilisés pour configurer le Model MBean :

Attribut

Rôle

String description

Fournir une description du MBean (optionnel)

boolean log

Préciser si certaines actions sur le beans doivent être mises dans un fichier de log (optionnel, false par défaut)

String logFile

Définir le nom du fichier de log avec son chemin (optionnel)

String objectName

Préciser l'ObjectName du MBean (optionnel)

String persistLocation

Définir le chemin d'un répertoire dans lequel les valeurs du MBean seront rendues persistantes (optionnel)

String persistName

Définir le nom du fichier dans lequel les valeurs du MBean seront rendues persistantes (optionnel)

int persistPeriod

Définir la durée en secondes applicable pour les persistPolicy NoMoreOftenThan et OnTimer (optionnel)

String persistPolicy

Préciser à quel moment les valeurs sont rendues persistantes (optionnel). Les valeurs possibles sont : OnUpdate, OnTimer, NoMoreOftenThan, Always, Never (valeur par défaut)


Son attribut par défaut est équivalent à l'attribut objectName.

L'attribut optionnel objectName permet de préciser l'object name avec lequel le MBean sera enregistré. Par défaut, l'objectName est composé de plusieurs éléments :

  • le nom de domaine est le nom du package
  • le type est le nom du bean défini dans le contexte
  • le nom est le nom de la classe

L'attribut objectName permet de fournir explicitement l'objectName qui sera utilisé pour enregistrer le MBean.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(objectName = "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
public class MonBean {
}

L'utilisation de l'attribut objectName n'est possible que pour des singletons. Si plusieurs instances du bean peuvent exister dans le contexte, il faut que chaque instance ait un objectName unique. Pour cela, il faut utiliser l'interface selfNaming().

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.SelfNaming;

@ManagedResource(objectName= "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
public class MonBean implements SelfNaming, BeanNameAware {
  private String beanName;
  
  public String getBeanName() {
    return beanName;
  }
  
  @Override
  public ObjectName getObjectName() throws MalformedObjectNameException {
    final ManagedResource managedResource = this.getClass().getAnnotation(
        ManagedResource.class);
    final String name = managedResource.objectName() + ",beanName="
        + getBeanName();
    return ObjectName.getInstance(name);
  }
  
  @Override
  public void setBeanName(final String name) {
    beanName = name;
  }
}

Dans l'exemple ci-dessus, la classe du bean implémente deux interfaces :

  • BeanNameAware qui va permettre d'obtenir le nom du bean tel que son instance le définit
  • SelfNaming qui va permettre de définir dynamiquement l'objectName d'une instance.

L'implémentation de la méthode getObjectName() recherche par introspection l'objectName défini grâce à l'annotation @ManageResource et lui ajoute un attribut beanName dont la valeur est le nom de l'instance du bean défini dans le contexte.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="monBean1" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="monBean2" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  
  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  
  <bean id="mbeanExporter"
    class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
    <property name="server" ref="mbeanServer" />
  </bean> 
</beans>

 

93.4.2. L'annotation @ManagedAttribute

L'annotation @ManagedAttribute s'utilise sur une méthode de type getter et/ou setter pour exposer une propriété grâce au MBean. Pour exposer la propriété de manière non modifiable, il faut utiliser l'annotation @ManagedAttribute uniquement sur la méthode de type getter. Pour l'exposer de manière modifiable, il faut utiliser l'annotation sur le getter et le setter.

Elle possède plusieurs attributs qui seront utilisés par Spring pour configurer le model MBean :

Attribut

Rôle

int currencyTimeLimit

Préciser combien de temps la valeur est valide avant de devoir être rafraîchie : < 0 jamais, 0 toujours et >0 la durée en seconde

String defaultValue

Fournir la valeur par défaut (optionnel)

String description

Fournir une description de la propriété (optionnel)

int persistPeriod

Définir la durée en secondes applicable pour les persistPolicy NoMoreOftenThan et OnTimer (optionnel)

String persistPolicy

Préciser à quel moment les valeurs sont rendues persistantes (optionnel). Les valeurs possibles sont : OnUpdate, OnTimer, NoMoreOftenThan, Always, Never (valeur par défaut)


Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(objectName= "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
public class MonBean {
  private String maPropriete;
  private String maSecondePropriete;
  public int maValeur;
  
  @ManagedAttribute(description="Description de ma premiere propriete.")
  public String getMaPropriete() {
    return maPropriete;
  }
  
  @ManagedAttribute(description="Description de ma seconde propriete.", 
    currencyTimeLimit = 20, persistPolicy = "OnUpdate")
  public String getMaSecondePropriete() {
    return maSecondePropriete;
  }
  
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
  }
  
  public void setMaSecondePropriete(final String maSecondePropriete) {
    this.maSecondePropriete = maSecondePropriete;
  }
}

 

93.4.3. L'annotation @ManagedOperation

L'annotation @ManagedOperation s'utilise sur une méthode pour exposer une opération grâce au MBean. Cette méthode ne doit pas être utilisée sur un getter ou un setter.

Elle possède plusieurs attributs :

Attribut

Rôle

int currencyTimeLimit

Préciser combien de temps la valeur de retour est valide avant de devoir être rafraichie : < 0 jamais, 0 toujours et >0 la durée en seconde (optionnel)

String description

Fournir une description de l'opération (optionnel)


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

import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(objectName = "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
public class MonBean {
  private String maPropriete;
  private String maSecondePropriete;
  public int maValeur;
  
  @ManagedOperation(description = "Description de la seconde operation.")
  public void maSecondeOperation() {
    System.out.println(maPropriete);
  }
  
  @ManagedOperation(description = "Description de la premiere operation.")
  public void monOperation() {
    System.out.println(maSecondePropriete);
  }
}

 

93.4.4. Les annotations @ManagedOperationParameters et @ManagedOperationParameter

Les annotations @ManagedOperationParameters et @ManagedOperationParameter permettent de préciser des métadonnées sur les paramètres d'une opération.

L'annotation @ManagedOperationParameters est un conteneur pour une ou plusieurs annotations @ManagedOperationParameter. Elle s'utilise sur une méthode.

L'annotation @ManagedOperationParameter s'utilise sur une méthode

Elle possède plusieurs attributs qui seront utilisés pour configurer le Model MBean :

Attribut

Rôle

String name

Préciser le nom du paramètre (obligatoire)

String description

Fournir une description du paramètre (obligatoire)


Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(objectName= "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
public class MonBean {
  private String maPropriete;
  private String maSecondePropriete;
  public int maValeur;
  
  @ManagedOperation(description = "Description de la premiere operation.")
  @ManagedOperationParameters({
  @ManagedOperationParameter(name = "valeur", description = "La valeur a traiter") })
  public void monOperation(final String valeur) {
    System.out.println(maSecondePropriete);
  }
  
  @ManagedOperation(description = "Description de la seconde operation.")
  @ManagedOperationParameters({
    @ManagedOperationParameter(name = "min", description = "La valeur minimale"),
    @ManagedOperationParameter(name = "max", description = "La valeur maximale") })
  public void maSecondeOperation(final int min, final int max) {
    System.out.println(maPropriete);
  }
}

L'utilisation des annotations @ManagedOperationParameters et @ManagedOperationParameter est facultative mais dans ce cas le nom des paramètres de l'opération apparaîtront sous la forme p1, p2, ... dans le client JMX.

 

93.4.5. L'annotation @ManagedMetric

A partir de Spring 3.0, l'annotation @ManagedMetric permet d'exposer une propriété sous la forme d'un metric JMX.

Cette annotation ne peut être utilisée que sur une méthode de type getter.

Elle possède plusieurs attributs qui seront utilisés pour configurer le Model MBean :

Attribut

Rôle

String category

Préciser la catégorie(optionnel)

int currencyTimeLimit

Préciser combien de temps la valeur de retour est valide avant de devoir être rafraîchie : < 0 jamais, 0 toujours et >0 la durée en seconde (optionnel)

String description

Fournir une description de la propriété (optionnel)

String displayName

(optionnel)

MetricType metricType

Indiquer comment la valeur évolue dans le temps (optionnel). COUNTER indique que la valeur ne fait qu'augmenter. GAUGE indique que la valeur peut varier pour augmenter ou diminuer.

int persistPeriod

Préciser la durée en secondes applicable pour les persistPolicy NoMoreOftenThan et OnTimer (optionnel)

String persistPolicy

Préciser à quel moment les valeurs sont rendues persistantes (optionnel). Les valeurs possibles sont : OnUpdate, OnTimer, NoMoreOftenThan, Always, Never (valeur par défaut)

String unit

Préciser l'unité de la valeur (optionnel)


Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import javax.management.Notification;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.support.MetricType;

@ManagedResource(objectName = "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
public class MonBean implements  {
  private String  maPropriete;
  
  @ManagedAttribute(description = "Description de ma premiere propriete.")
  public String getMaPropriete() {
    return maPropriete;
  }
  
  @ManagedMetric(description = "Nombre d'éléments", 
    currencyTimeLimit = 20, category = "Utilisation",
    metricType = MetricType.GAUGE, displayName = "Nb éléments", unit = "éléments")
  public long getTaille() {
    return (long) (Math.random() * 100l);
  }
  
  public void maSecondeOperation() {
    System.out.println("Seconde operation");
  }
  
  @ManagedAttribute(description = "Description de ma premiere propriete.")
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
  }
}

 

93.4.6. Les annotations @ManagedNotifications et @ManagedNotification

Depuis Spring 2.0, il est possible d'utiliser les annotations @ManagedNotifications et @ManagedNotification pour préciser qu'une ou plusieurs notifications peuvent être émises par le MBean.

L'annotation @ManagedNotifications est un conteneur pour une ou plusieurs annotations @ManagedNotification. Elle s'utilise sur une classe.

Elle possède un seul attribut :

Attribut

Rôle

ManagedNotification[] value

Les annotations de type @ManagedNotification associées (obligatoire)


L'annotation @ManagedNotification permet de fournir des informations sur une notification qui peut être émise grâce à JMX par le MBean.

Elle possède plusieurs attributs :

Attribut

Rôle

String name

Le nom de la notification (obligatoire)

String[] notificationTypes

(obligatoire)

String description

La description de la notification (optionnel)


Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.Notification;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedNotification;
import org.springframework.jmx.export.annotation.ManagedNotifications;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;

@ManagedResource(objectName = "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
@ManagedNotifications({
  @ManagedNotification(name = "javax.management.Notification", 
    notificationTypes = { "MiseAJour" }) })
public class MonBean implements NotificationPublisherAware {
  private String                maPropriete;
  private NotificationPublisher notificationPublisher;
  private final AtomicInteger   sequence = new AtomicInteger();
  
  @ManagedAttribute(description = "Description de ma propriete.")
  public String getMaPropriete() {
    return maPropriete;
  }
  
  @ManagedOperation(description = "Description de mon operation.")
  public void monOperation() {
    System.out.println(maPropriete);
  }
  
  @ManagedAttribute(description = "Description de ma propriete.")
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
    notificationPublisher.sendNotification(new Notification("MiseAJour", this,
        sequence.getAndIncrement(),
        "Modification de la propriété avec la valeur " + maPropriete));
  }
  
  @Override
  public void setNotificationPublisher(final NotificationPublisher notificationPublisher) {
    this.notificationPublisher = notificationPublisher;
  }
}

 

93.4.7. L'utilisation du tag <mbean-server>

Ce tag facilite la recherche du serveur de MBeans par défaut de la plate-forme pour Java 1.5 et supérieur, Weblogic 9 et supérieur et Websphere 5.1 et supérieur. Si aucun serveur de MBeans n'est trouvé, alors une nouvelle instance est créée et ajoutée à la plate-forme.

Par défaut, le nom du bean de type MBeanServer est mbeanServer. L'attribut id permet de définir l'identifiant du bean qui encapsule le serveur de MBeans et ainsi modifier sa valeur par défaut.

Exemple :
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  
  <context:mbean-server id="mbeanServer" />
   ...
  </beans>

 

93.4.8. L'utilisation du tag <mbean-export>

A partir de Spring 2.5, il est possible d'utiliser le tag <context:mbean-export> pour demander au conteneur Spring d'enregistrer les beans annotés avec @ManagedXXX comme des MBeans.

Ainsi, plutôt que de définir explicitement un composant de type AnnotationMBeanExporter, si les valeurs par défaut sont suffisantes, il est possible d'utiliser le tag <context:mbean-export>.

Le tag <context:mbean-export> est équivalent à une déclaration explicite dans le contexte d'un bean de type AnnotationMBeanExporter ou d'un bean de type MBeanExporter associé à un MetadataNamingStrategy et à un AnnotationJmxAttributeSource. L'instance de type MBeanExporter créée dans le contexte a pour nom mbeanExporter.

Le tag <context:mbean-export> possède plusieurs attributs :

Attribut

Rôle

registration

Définir le comportement lors de l'enregistrement d'un MBean qui est déjà dans le serveur de MBeans. Les valeurs possibles sont failOnExisting (valeur par défaut), replaceExisting, ignoreExisting

server

Préciser le serveur de MBeans dans lequel les MBeans seront enregistrés

default-domain

Définir le nom du domaine par défaut utilisé dans l'objectName pour enregistrer les MBeans


Exemple :
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  
  <context:mbean-export server="mbeanServer" default-domain="fr.jmdoudouxbeans"/>
   ...
  </beans>

La valeur replaceExisting pour l'attribut registration est particulièrement utile si les MBeans peuvent être enregistrés plusieurs fois dans le serveur de MBeans : c'est notamment le cas si le contexte Spring est recréé. Un exemple de cas concret est le redéploiement d'une application web dans un serveur Tomcat.

Les packages des beans concernés doivent être précisés dans le tag <context:component-scan>.

Exemple :
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package=" fr.jmdoudoux.dej.spring.jmx"/>

  <context:mbean-export registration="replaceExisting"/>

</beans>

 

93.5. Le développement d'un client JMX

Spring propose deux solutions pour permettre l'accès à un serveur de MBeans distant :

  • une fabrique permettant de créer de manière déclarative une instance pour se connecter au serveur de MBeans et utiliser l'API JMX
  • une fabrique qui permet de créer un proxy pour accéder à un MBean

 

93.5.1. La connexion à un serveur de MBeans

La classe MBeanServerConnectionFactoryBean est une fabrique pour créer des instances de type MBeanServerConnection.

La propriété la plus importante est serviceUrl qui permet de préciser l'url de connexion au serveur de MBeans.

La méthode getObject() renvoie une instance de la classe javax.management.MBeanServerConnection qui encapsule une connexion sur un serveur de MBeans grâce à un connecteur de type JMXServerConnector configuré et démarré.

Il est possible de configurer un bean de type MBeanServerConnectionFactoryBean dans le contexte Spring.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="mbeanServerConnection"
    class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
    <property name="serviceUrl"
      value="service:jmx:rmi://localhost/jndi/rmi://localhost:8099/monconnector"/>
  </bean>
</beans>

La propriété serviceUrl permet de préciser l'url de connexion au serveur de MBeans. L'exemple ci-dessus utilise RMI, l'exemple ci-dessous utilise le protocole JMXMP.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="mbeanServerConnection"
    class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
    <property name="serviceUrl" value="service:jmx:jmxmp://localhost:9875"/>
  </bean>
</beans>

Il est alors possible d'obtenir du contexte l'instance de type MBeanServerConnection et de l'utiliser pour interagir avec les MBeans enregistrés dans le serveur en utilisant l'API JMX.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import java.io.IOException;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainClient {
  public static void main(final String[] args) throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext(
      "appContext.xml");
    MBeanServerConnection mbeanServerConnection = (MBeanServerConnection) context
       .getBean("mbeanServerConnection");
    ObjectName mbeanName;
    
    try {
      mbeanName = new ObjectName("monAppli:type=mesBeans,name=MonBean");
      String maPropriete = (String) mbeanServerConnection.getAttribute(
        mbeanName, "MaPropriete");
      System.out.println("maPropriete=" + maPropriete);
      mbeanServerConnection.setAttribute(mbeanName, new Attribute(
        "MaPropriete", maPropriete + " modifie"));
      mbeanServerConnection.invoke(mbeanName,
        "monOperation", new Object[] {}, new String[] {});
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException 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();
    }
    System.in.read();
  }
}

 

93.5.2. L'utilisation d'un proxy

Pour accéder à un MBean, Spring peut utiliser un proxy de type MBeanProxy sur la base d'une interface qui décrit les fonctionnalités du MBean.

Pour utiliser un proxy, le bean doit être décrit dans une interface.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;

public interface IMonBean {
  @ManagedAttribute(description = "Description de ma premiere propriete.")
  public abstract String getMaPropriete();

  @ManagedOperation(description = "Description de l'operation.")
  public abstract void monOperation();

  @ManagedAttribute(description = "Description de ma premiere propriete.")
  public abstract void setMaPropriete(final String maPropriete);
}

La classe du MBean doit implémenter l'interface.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import javax.management.Notification;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;

@ManagedResource(objectName = "monAppli:type=mesBeans,name=MonBean", 
  description = "Mon MBean de test")
public class MonBean implements NotificationPublisherAware, IMonBean {
  private String maPropriete;
  private NotificationPublisher notificationPublisher;
  private int sequence;
  
  @Override
  public String getMaPropriete() {
    return maPropriete;
  }
  
  @Override
  public void monOperation() {
    System.out.println(maPropriete);
  }
  
  @Override
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
    notificationPublisher.sendNotification(new Notification("MiseAJour", this,
        sequence++, "Modification de la propriété avec la valeur "
            + maPropriete));
  }
  
  @Override
  public void setNotificationPublisher(
      final NotificationPublisher notificationPublisher) {
    this.notificationPublisher = notificationPublisher;
  }
}

Pour le client JMX, il faut définir un bean dans le contexte Spring qui soit du type org.springframework.jmx.access.MBeanProxyFactoryBean en initialisant plusieurs propriétés :

  • objectName : l'objectName sous lequel le MBean a été enregistré dans le serveur de MBeans
  • proxyInterface : le nom pleinement qualifié de l'interface du MBean
Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="mbeanServerConnection"
    class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
    <property name="serviceUrl"
      value="service:jmx:rmi://localhost/jndi/rmi://localhost:8099/monconnector"/>
  </bean>
  
  <bean id="monBeanProxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
    <property name="server" ref="mbeanServerConnection" />
    <property name="objectName"
      value="monAppli:type=mesBeans,name=MonBean" />
    <property name="proxyInterface"
      value="fr.jmdoudoux.dej.spring.jmx.IMonBean" />
  </bean>
</beans>

Le bean peut alors être injecté par le client dans l'application qui sera alors en mesure d'invoquer les méthodes du proxy.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import javax.management.MBeanServerConnection;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainProxyClient {
  public static void main(final String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "appProxyContext.xml");
    MBeanServerConnection mbeanServerConnection = (MBeanServerConnection) context
        .getBean("mbeanServerConnection");
    IMonBean monBeanProxy = (IMonBean) context.getBean("monBeanProxy");
    String maPropriete = monBeanProxy.getMaPropriete();
    System.out.println(maPropriete);
    monBeanProxy.setMaPropriete(maPropriete + " modifie");
    monBeanProxy.monOperation();
  }
}

L'utilisation d'un proxy évite d'avoir à manipuler l'API JMX directement pour interagir avec le MBean.

Pour que le client puisse fonctionner, il faut ajouter les biliothèques suivantes au classpath : org.springframework.aop-3.0.5.RELEASE.jar, org.springframework.asm-3.0.5.RELEASE.jar, org.springframework.beans-3.0.5.RELEASE.jar, org.springframework.context-3.0.5.RELEASE.jar, org.springframework.core-3.0.5.RELEASE.jar, org.springframework.expression-3.0.5.RELEASE.jar, com.springsource.org.apache.commons.logging-1.1.1.jar et com.springsource.org.aopalliance-1.0.0.jar.

 

93.6. Les notifications

La spécification JMX propose la notion de notifications qui sont comme des événements émis par des MBeans et traités par des clients JMX auxquels ils sont abonnés.

 

93.6.1. L'émission de notifications

Spring facilite l'émission de notifications JMX au moyen de l'interface NotificationPublisher.

Cette interface ne définit qu'une méthode :

Méthode 

Rôle

void sendNotification(Notification notification)

Envoyer une notification au serveur de MBeans


Pour obtenir une instance de type NotificationPublisher injectée par le contexte Spring, il faut que la classe qui encapsule le MBean implémente l'interface NotificationPublisherAware. Cette injection se fait lorsqu'une instance est enregistrée dans le serveur de MBeans par un MBeanExporter.

Chaque instance enregistrée dans un serveur de MBeans se voit injecter sa propre instance de type NotificationPublisher.

L'interface NotificationPublisherAware ne définit qu'une seule méthode :

Méthode

Rôle

void setNotificationPublisher(NotificationPublisher notificationPublisher)

Méthode de type setter pour injecter une instance de type NotificationPublisher


Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import javax.management.Notification;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;

@ManagedResource(objectName = "monAppli:type=mesBeans,name=MonBean",
  description = "Mon MBean de test")
public class MonBean implements NotificationPublisherAware {
  private String maPropriete;
  private NotificationPublisher notificationPublisher;
  private int sequence;
  
  @ManagedAttribute(description = "Description de ma premiere propriete.")
  public String getMaPropriete() {
    return maPropriete;
  }
  
  public void setMaPropriete(final String maPropriete) {
    this.maPropriete = maPropriete;
    notificationPublisher.sendNotification(new Notification("MiseAJour", this,
        sequence++, "Modification de la propriété avec la valeur "
            + maPropriete));
  }
  
  @Override
  public void setNotificationPublisher(
      final NotificationPublisher notificationPublisher) {
    this.notificationPublisher = notificationPublisher;
  }
}

Dans l'exemple ci-dessus, chaque modification de la valeur de la propriété émet une notification.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import java.io.IOException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
  public static void main(final String[] args) throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "appContext.xml");
    MonBean monBean = context.getBean("monBean", MonBean.class);
    System.in.read();
    monBean.setMaPropriete("valeur 1");
    monBean.setMaPropriete("valeur 2");
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

 

93.6.2. La réception de notifications

La réception de notifications JMX se fait au moyen d'une classe qui implémente l'interface javax.management.NotificationListener.

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import javax.management.Notification;
import javax.management.NotificationListener;

public class MonNotificationListener implements NotificationListener {

  @Override
  public void handleNotification(final Notification notification,
      final Object handback) {
    if (notification.getType().equals("MiseAJour")) {
      System.out.println(notification.getSource() + " : "
          + notification.getType() + " - " + notification.getSequenceNumber());
    }
  }
}

Il suffit de récupérer l'instance de type MBeanServerConnection du contexte de Spring et d'utiliser l'API JMX pour enregistrer une nouvelle instance du listener en utilisant la méthode addNotificationListener().

Exemple :
package fr.jmdoudoux.dej.spring.jmx;
      
import java.io.IOException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainClient {
  public static void main(final String[] args) throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "appContext.xml");
        
    MBeanServerConnection mbeanServerConnection = (MBeanServerConnection) context
        .getBean("mbeanServerConnection");
    ObjectName mbeanName;
    try {
      mbeanName = new ObjectName("monAppli:type=mesBeans,name=MonBean");
      mbeanServerConnection.addNotificationListener(mbeanName,
          new MonNotificationListener(), null, null);
    } catch (MalformedObjectNameException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    } catch (InstanceNotFoundException e) {
      e.printStackTrace();
    }
    System.in.read();
  }
}

Il est possible d'enregistrer des NotificationListener dans un MBeanExporter en utilisant sa propriété notificationListenerMappings. Cette propriété attend une Map dont les clés sont des objectNames et les valeurs associées, les noms pleinement qualifiés des classes de type NotificationListener.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd">  
  <bean id="monBean" class="fr.jmdoudoux.dej.spring.jmx.MonBean" />
  <bean id="mbeanServer" 
    class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
  </bean>
  <bean id="monNotificationListener"
    class="fr.jmdoudoux.dej.spring.jmx.MonNotificationListener" />
  <bean id="mbeanExporter"
    class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
    <property name="server" ref="mbeanServer" />
    <property name="notificationListenerMappings">
      <map>
        <entry key="monAppli:type=mesBeans,name=MonBean"
          value-ref="monNotificationListener" />
      </map>
    </property>
  </bean>
</beans>

 


[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

78 commentaires Donner une note à l´article (5)

 

Copyright (C) 1999-2022 Jean-Michel DOUDOUX. Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes de la Licence de Documentation Libre GNU, Version 1.1 ou toute autre version ultérieure publiée par la Free Software Foundation; les Sections Invariantes étant constitués du chapitre Préambule, aucun Texte de Première de Couverture, et aucun Texte de Quatrième de Couverture. Une copie de la licence est incluse dans la section GNU FreeDocumentation Licence. La version la plus récente de cette licence est disponible à l'adresse : GNU Free Documentation Licence.