Friday, August 19, 2011

JMX et MBeans 101

Java Management eXtension ou JMX est idéal pour superviser et configurer des application Java. Outre sa simplicité de développement, JMX permet notamment :
  • de publier méthodes de configurations, sondes et notifications automatiquement dans un serveur facile d'accès
  • d'y accéder à partir de tous les outils pour les développeurs comme JConsole, JRockit Mission Control ou pour les administrateurs comme Oracle Enterprise Manager
  • d'apparaître au même niveau que les composants de supervision et de configuration des plateformes Java SE ou EE 
  • d'intégrer les autres paradigmes des plateformes Java à commencer par l'infrastructure de sécurité
Bref, JMX est inestimable pour la mise en oeuvre de vos projets. C'est aussi d'une extrême simplicité comme vous pourrez vous en rendre compte dans l'exemple de mise en oeuvre qui suit qui s'appuie sur Glassfish et qui permet de mettre à jour une propriété de configuration de votre application

Une application simple comme support

Pour débuter, il faut une application. L'objectif n'est pas vraiment de développer une application complexe ; vous ferez facilement le parallèle. Voici donc une application minimaliste constituée d'une Servlet config.java dont le code est ci-dessous et qui affiche une page HTML. Les informations associées sont issues d'un fichier de propriétés :
package com.arkzoyd.jmxdemo.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "config", urlPatterns = {"/config"})
public class config extends HttpServlet {

    protected void processRequest(HttpServletRequest request, 
                                  HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            String myproperty;
            try {
                InputStream inputStream = this.getClass().getClassLoader()
                                 .getResourceAsStream("config.properties");  
                Properties properties = new Properties();  
                properties.load(inputStream);
                myproperty = properties.getProperty("myproperty");                  
            } catch (IOException e) {
                myproperty="File Not Found";
            }

            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet config</title>");  
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>My Property Value:</h1>");
            out.println(myproperty);
            out.println("</body>");
            out.println("</html>");
             
        } finally {            
            out.close();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }
}
Pour les besoins de la démonstration, vous créerez donc le fichier de propriété config.properties dont le contenu est le suivant :
myproperty=unset
Une fois l'application déployée, l'écran associé ressemble à ceci :

Créer un MBean et l'enregistrer dans le serveur de MBean

Notre MBean est extrêmement simple dans ce premier exemple. Quoiqu'il en soit, vous devrez procéder en 3 étapes :
  • Créer le MBean et l'implémenter
  • Instancier le MBean et l'enregistrer dans le Serveur JMX de votre infrastructure
  • Modifier vos classes pour accéder au MBean

Créer le MBean et l'implémenter

Dans cette première étape, vous devrez créer une Interface qui décrive votre MBean; Comme il s'agit d'un exemple simple, nous allons simplement implémenter un attribut et donc les getters et setters associés; voici notre interface :
package com.arkzoyd.jmxdemo.jmx;

public interface JMXDemoMBean {
    public String getMyProperty();    
    public void setMyProperty(String myproperty);
}
Une fois l'interface créée, il faut l'implémenter. L'implémentation accéde au fichier de ressource pour mettre à jour la propriété associée à l'attribut de notre MBean :
package com.arkzoyd.jmxdemo.jmx;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.StandardMBean;

public class JMXDemoMBeanImpl extends StandardMBean
        implements JMXDemoMBean {

    private String myproperty = "empty";

    public JMXDemoMBeanImpl() throws
            javax.management.NotCompliantMBeanException {
        super(JMXDemoMBean.class);
            try {
                InputStream inputStream = this.getClass().getClassLoader()
                                 .getResourceAsStream("config.properties");
                Properties properties = new Properties();
                properties.load(inputStream);
                myproperty = properties.getProperty("myproperty");
                inputStream.close();
            } catch (IOException ex) {
                Logger.getLogger(JMXDemoMBeanImpl.class.getName())
                                      .log(Level.SEVERE, null, ex);
                myproperty = "FileNotFound";
            }
    }

    @Override
    public String getMyProperty() {
        return myproperty;
    }

    @Override
    public void setMyProperty(String pproperty) {
        myproperty = pproperty;
        Properties properties = new Properties();
        properties.setProperty("myproperty", pproperty);
        File pfile = new File(this.getClass().getClassLoader()
                  .getResource("config.properties").getFile());
        try {
            OutputStream out = new FileOutputStream(pfile);
            properties.store(out, myproperty);
            out.flush();
            out.close();
            ResourceBundle.clearCache();
        } catch (IOException ex) {
            Logger.getLogger(JMXDemoMBeanImpl.class.getName())
                                  .log(Level.SEVERE, null, ex);
        }

    }
}

Instancier le MBean et l'enregistrer dans le Serveur JMX

Notre MBean créé et ajouté au projet, vous devez l'enregistrer au démarrage de l'application dans le serveur de MBeans. L'implémentation de cet enregistrement dépend de votre plateforme et parfois même du serveur que vous voulez utiliser. Dans le cas d'un MBean associé à des servlets, le plus simple est d'effectuer cette opération dans la méthode init d'une "servlet filter" comme ceci :
package com.arkzoyd.jmxdemo.servlet;

import com.arkzoyd.jmxdemo.jmx.JMXDemoMBean;
import com.arkzoyd.jmxdemo.jmx.JMXDemoMBeanImpl;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter(urlPatterns = {"/*"})
public class JMXInitServlet implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        try {
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            ObjectName mbeanName = 
                    new ObjectName("com.arkzoyd:type=JMXDemoMBean");
            if (!server.isRegistered(mbeanName)) {
                JMXDemoMBean jmx = new JMXDemoMBeanImpl();
                server.registerMBean(jmx, mbeanName);
            }

        } catch (Exception ex) {
            Logger.getLogger(JMXInitServlet.class.getName())
                              .log(Level.SEVERE, null, ex);
        }

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) 
                         throws IOException, ServletException {
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        try {
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            ObjectName mbeanName = 
                    new ObjectName("com.arkzoyd:type=JMXDemoMBean");
            if (server.isRegistered(mbeanName)) {
                server.unregisterMBean(mbeanName);
            }

        } catch (Exception ex) {
            Logger.getLogger(JMXInitServlet.class.getName())
                                .log(Level.SEVERE, null, ex);
        }
    }
}
Evidemment c'est très dépendant de l'application et des outils à votre disposition :

Accéder au MBean depuis l'application

Vous pouvez ensuite simplement accéder aux méthodes du MBean depuis votre application pour changer sont comportement ou alimenter des indicateurs. Voici par exemple comment modifier la servlet présentée précédemment pour accéder aux attribut du MBean plutôt qu'au fichier de propriété :
package com.arkzoyd.jmxdemo.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "config", urlPatterns = {"/config"})
public class config extends HttpServlet {

    protected void processRequest(HttpServletRequest request, 
                                  HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            String myproperty;
            try {
                MBeanServer server = ManagementFactory.getPlatformMBeanServer();
                ObjectName mbeanName = new ObjectName("com.arkzoyd:type=JMXDemoMBean");
                if (!server.isRegistered(mbeanName)) myproperty="Not Found";
                else myproperty=(String) server.getAttribute(mbeanName, "MyProperty");                
            } catch (Exception e) {
                myproperty="File Not Found";
            }
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet config</title>");  
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>My Property Value:</h1>");
            out.println(myproperty);
            out.println("</body>");
            out.println("</html>");
             
        } finally {            
            out.close();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }
    
}

Utiliser le MBean

Pour utiliser le MBean, il suffit de déployer l'application sur Glassfish par exemple; vous verrez que la propriété est accédée depuis le MBean :


Vous pouvez ensuite accéder au MBean avec l'outil de votre choix. Le plus simple étant probablement JConsole qui s'attache directement à votre JVM en local comme vous le voyez ci-dessous :

L'attribut étant éditable, vous pouvez le modifier et recharger la page de votre application
Voilà, vous n'avez plus d'excuse pour ne pas intrumenter vos applications avec JMX dès que possible...
Notes:
vous n'aurez aucun mal à mettre en oeuvre des indicateurs métiers pertinents sur ce principe. Si vous êtes intéressé par plus d'info, visitez les "tutorials" JMX sur le site d'Oracle (ex Sun) ainsi que la documentation JMX de Java SE 7. Par ailleurs, vous pouvez télécharger le projet associé à cet article sous la forme d'un projet NetBeans

No comments:

Post a Comment