This is the first part in a series of blogs, that demonstrate how to add management capability to your own application using ​​JMX MBeans​​.

In this first blog entry, we will learn:

  • How to implement a custom MBean to manage configuration associated with an application.
  • How to package the resulting code and configuration as part of the application's ear file.
  • How to register our MBean upon application startup, and unregistered them upon application stop (or undeployment).
  • How to use generic JMX clients such as ​​JConsole​​ to browse and edit our application's MBean.


The complete code sample and associated build files are available as a ​​zip file​​. The code has been tested against WebLogic Server 10.3.1 and JDK6.

Implementing the MBean

We chose to implement an MBean that can be used to manage properties associated with an application. To keep things interesting those properties will be persisted, and originally packaged with the deployed application's ear file.

Among the different types of MBeans, we choose to implement an ​​MXBean​​. MXBeans have the main advantage to expose model specific types as Open Types. This means that clients interacting with MXBeans do not need to include any jar files containing application custom classes. For our simple example, this doesn't really come into play, but this also doesn't complicate our task either. So MXBean it is.

If you want to know more about MXBean, a tutorial is available ​​here​

MBean interface

The MBean interface declares the JMX management interface exposed by the MBean. Java methods translate to JMX attributes ( Java bean getter/setter pattern ) or to JMX operations ( the remaining methods ). More info on the mapping from Java methods to JMX attributes and operations is available in the ​​JMX specification​​ or in this ​​tutorial​​. The same mapping rules apply to both Standard MBeans and MXBeans.

package blog.wls.jmx.appmbean;import java.util.Map;import java.io.IOException;public interface PropertyConfigMXBean {    public String setProperty(String key, String value) throws IOException;\    public String getProperty(String key);    public Map getProperties();}

The above interface declares one attribute: ​Properties​, and two operations: ​setProperty​ and ​getProperty​. Those are the attribute and operations exposed by our MBean to JMX clients.

MBean implemetation

Our MBean implementation relies on the JDK's ​​Properties​​ class to manage and persist the underlying properties. The complete code for the MBean implementation is included below, and specific sections are discussed thereafter.

package blog.wls.jmx.appmbean;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.File;import java.net.URL;import java.util.Map;import java.util.Properties;import javax.management.MBeanServer;import javax.management.ObjectName;import javax.management.MBeanRegistration;public class PropertyConfig implements PropertyConfigMXBean, MBeanRegistration {    private String relativePath_ = null;     private Properties props_ = null;    private File resource_ = null;    public PropertyConfig(String relativePath) throws Exception {        props_ = new Properties();        relativePath_ = relativePath;    }    public String setProperty(String key,String value) throws IOException {        String oldValue = null;        if (value == null) {            oldValue = String.class.cast(props_.remove(key));        } else {            oldValue = String.class.cast(props_.setProperty(key, value));              }        save();        return oldValue;    }    public String getProperty(String key) {        return props_.getProperty(key);    }    public Map getProperties() {        return (Map) props_;    }    private void load() throws IOException {                InputStream is = new FileInputStream(resource_);        try {            props_.load(is);        }        finally {            is.close();        }    }     private void save() throws IOException {          OutputStream os = new FileOutputStream(resource_);        try {            props_.store(os, null);        }        finally {            os.close();        }    }    public ObjectName preRegister(MBeanServer server, ObjectName name)        throws Exception {        // MBean must be registered from an application thread        // to have access to the application ClassLoader        ClassLoader cl = Thread.currentThread().getContextClassLoader();        URL resourceUrl = cl.getResource(relativePath_);        resource_ = new File(resourceUrl.toURI());        load();             return name;    }    public void postRegister(Boolean registrationDone) { }    public void preDeregister() throws Exception {}    public void postDeregister() {}     }

Our MBean implements the ​​PropertyConfigMXBean​​ interface, as well as the ​​MBeanRegistration​​ interface.

We use the ​​MBeanRegistration.preRegister​​ method to load the persisted properties upon MBean registration. The original properties are included as part of the deployed ear, and accessed using the application's Context ClassLoader:

ClassLoader cl = Thread.currentThread().getContextClassLoader();        URL resourceUrl = cl.getResource(relativePath_);        resource_ = new File(resourceUrl.toURI());        load();

The interesting part in the above code is the use of the application's Context ClassLoader to extract the path under which the deployed ear's property file is located. For this to work we must ensure that the ​​preRegister​​ method is triggered by an application Thread, so that we can access the proper ClassLoader. More on this later.

The ​​load​​ and ​​save​​ methods are quite straight forward. The ​​load​​ method use the ​​Properties​​ class to read the properties from the application's property file into the MBean state. The ​​save​​ method use the ​​Properties​​ class to persist the properties from the MBean state back into the application's property file.

Any new property added using the ​​setProperty​​ method is immediately persisted by calling the ​​save​​ method within the ​​setProperty​​ method. On a more complex project, one could expose ​​save​​ as a JMX operation, and build an associated Configuration Session MBean. This is beyond this blog's topic thought.

MBean life-cycle implementation

To register our MBean upon application start, and unregister it upon application stop (or undeploy), we use WebLogic's ​​ApplicationLifecycleListener​​ class. Note: We could have used the J2EE ​​ServletContextListener​​ class in place, but we chose the ​​ApplicationLifecycleListener​​ as it also works for applications that do not include web modules. The complete code is included below:

package blog.wls.jmx.appmbean;import weblogic.application.ApplicationLifecycleListener; import weblogic.application.ApplicationLifecycleEvent; import weblogic.application.ApplicationException; import javax.management.ObjectName; import javax.management.MBeanServer; import javax.naming.InitialContext; public class ApplicationMBeanLifeCycleListener extends ApplicationLifecycleListener {    public void postStart(ApplicationLifecycleEvent evt) throws ApplicationException {         try {             InitialContext ctx = new InitialContext();             MBeanServer mbs  =                 MBeanServer.class.cast( ctx.lookup("java:comp/jmx/runtime") );             PropertyConfig mbean = new PropertyConfig("config/properties.data");            ObjectName oname = new ObjectName(                "blog.wls.jmx.appmbean:type=PropertyConfig,name=myAppProperties");             mbs.registerMBean(mbean, oname);         }         catch (Exception e) {            // Deal with exception            e.printStackTrace();        }     }     public void preStop(ApplicationLifecycleEvent evt) throws ApplicationException {         try {             InitialContext ctx = new InitialContext();             MBeanServer mbs  =                 MBeanServer.class.cast( ctx.lookup("java:comp/jmx/runtime") );             ObjectName oname = new ObjectName(                "blog.wls.jmx.appmbean:type=PropertyConfig,name=myAppProperties");            if ( mbs.isRegistered(oname) ) {                  mbs.unregisterMBean(oname);             }        }         catch (Exception e) {            // Deal with exception            e.printStackTrace();        }     }        }

In the above code we register our MBean in WebLogic's ​​Runtime MBeanServer​​: ​​ctx.lookup("java:comp/jmx/runtime")​​. This MBeanServer is present on all WebLogic's processes, and can be used to register application's custom MBeans.

We construct an instance of our MBean: ​​new PropertyConfig("config/properties.data")​​ passing the relative path to the application's property file. The path is relative to the root of the ear file, as we will see in the next section. Remember from the previous section we used the application Context ClassLoader to load the application property file using the relative path provided above: ​​cl.getResource(relativePath_);​​. This is made possible by the ​​​postStart​​​ method that is executed by an application Thread.

We register our MBean under the ​​"blog.wls.jmx.appmbean:type=PropertyConfig,name=myAppProperties"​​ ​​ObjectName​​. We followed the ​​JMX best practices​​ when picking that name. We recommend you do the same as this greatly helps:

Avoid naming collisions. Organize MBeans in MBean browsers. For instance JConsole's MBean tree. Provide a consistent MBean naming structure when MBeans from many different sources are registered in a single MBeanServer.

 

Another point worth mentioning is the importance of unregistering the MBean when the application is stopped. If the MBean is left registered, the application ClassLoader(s) will be pinned and resource will be leaked.

Packaging the ear file

Now we need to package our code and property file so that both can be loaded by the application Context ClassLoader.

The MBean and life cycle code is packaged in a jar as follow:

$ jar tvf sample-mbean-app-mbeans.jar     0 Tue Oct 07 16:35:28 PDT 2009 META-INF/   121 Tue Oct 07 16:35:26 PDT 2009 META-INF/MANIFEST.MF     0 Tue Oct 07 16:35:26 PDT 2009 blog/     0 Tue Oct 07 16:35:26 PDT 2009 blog/wls/     0 Tue Oct 07 16:35:26 PDT 2009 blog/wls/jmx/     0 Tue Oct 07 16:35:26 PDT 2009 blog/wls/jmx/appmbean/  1337 Tue Oct 07 16:35:26 PDT 2009             blog/wls/jmx/appmbean/ApplicationMBeanLifeCycleListener.class  2417 Tue Oct 07 16:35:26 PDT 2009 blog/wls/jmx/appmbean/PropertyConfig.class   408 Tue Oct 07 16:35:26 PDT 2009            blog/wls/jmx/appmbean/PropertyConfigMXBean.class$

The jar's manifest includes the following entry: ​​Class-Path: ../..​​. This will ensure that the application's property file can be accessed from the application's ClassLoader. More on this later.

The ear file is packaged as follow:

$ jar tvf sample-mbean-app.ear     0 Tue Oct 07 16:35:28 PDT 2009 META-INF/   102 Tue Oct 07 16:35:26 PDT 2009 META-INF/MANIFEST.MF   602 Tue Oct 0714:50:34 PDT 2009 META-INF/application.xml   719 Tue Oct 07 16:35:26 PDT 2009 app.war     0 Tue Oct 07 16:35:28 PDT 2009 APP-INF/     0 Tue Oct 07 16:35:28 PDT 2009 APP-INF/lib/  3328 Tue Oct 07 16:35:26 PDT 2009 APP-INF/lib/sample-mbean-app-mbeans.jar     0 Tue Oct 07 16:35:28 PDT 2009 config/   105 Tue Oct 07 30 12:46:28 PDT 2009 config/properties.data   422 Tue Oct 07 13:10:52 PDT 2009 META-INF/weblogic-application.xml$

The MBean jar file ​​sample-mbean-app-mbeans.jar​​ is added under the ​​APP-INF/lib​​ directory. Any jars located under that directory will be added to the application's ClassLoader.

The application's property file is added under ​​config/properties.data​​, and can be accessed by the application's Context ClassLoader using that path. This works because we added the ​​Class-Path: ../..​​ entry in ​​sample-mbean-app-mbeans.jar​​'s manifest, and we placed that jar under the ​​APP-INF/lib​​ directory.

​META-INF/application.xml​​ declares a single war module ​​app.war​​, that is empty, and whose only purpose is to make our application valid by including at least one J2EE module.

​META-INF/weblogic-application.xml​​ declares our ​​​ApplicationLifecycleListener​​​:

<?xml version="1.0" encoding="UTF-8"?><weblogic-application xmlns="http://www.bea.com/ns/weblogic/90"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://www.bea.com/ns/weblogic/90   http://www.bea.com/ns/weblogic/90/weblogic-application.xsd">  <listener>    <listener-class>         blog.wls.jmx.appmbean.ApplicationMBeanLifeCycleListener    </listener-class>  </listener></weblogic-application>

Download the code and build file

The above code and associated build files are available as a ​​zip file​​. Once you have downloaded the zip file, extract its content, and cd to the ​​app_mbean_part1​​ directory.

In order to build the code and create the application's ear file, you first need to define the ​​WL_HOME​​ environment property to point to your WebLogic binary install. Make sure ​​$WL_HOME/lib/weblogic.jar​​ is valid. Then just execute:

​​ant​

If everything goes well the ​​sample-mbean-app.ear​​ file should now be located under the build directory.

Deploying the ear

The ​​sample-mbean-app.ear​​ file can be deployed as follow:

​​java -classpath $WL_HOME/lib/weblogic.jar weblogic.Deployer -noexit -name sample-mbean-app -source build/sample-mbean-app.ear -targets jrfServer_admin -adminurl t3://140.87.10.42:7086 -user weblogic -password gumby1234 -deploy ​

Make sure to substitute:

Your WebLogic server host and port in place of ​​140.87.10.42:7086​​. The port value for your WebLogic server is available from the <WLS_INSTANCE_HOME>/config/config.xml file: ​
<server>    <name>jrfServer_admin</name>    <listen-port>7086</listen-port>    <listen-address>140.87.10.42</listen-address>  </server>
Make sure you look under the correct server if several servers are defined as part of your config.xml. For instance in the above case we are connecting to the server identified as "jrfServer_admin ".

 

​ Your WebLogic server name in place of ​​jrfServer_admin ​​ Your WebLogic server administrator's login and password in place of ​​weblogic​​ and ​​gumby1234​

 

The following command can be used to undeploy the ​​sample-mbean-app ​​application:

​​java -classpath $WL_HOME/lib/weblogic.jar weblogic.Deployer -noexit -name sample-mbean-app -targets jrfServer_admin -adminurl t3://140.87.10.42:7086 -user weblogic -password gumby1234 -undeploy ​

Using JConsole to browse and edit our application MBean

Using JConsole to connect to WebLogic's MBeanServers was the subject of an ​​earlier blog entry​​. I recommand reading through ​​that blog​​ if you want to know more. Here I will just fast forward to the command line used:

​​jconsole -J-Djava.class.path=$JAVA_HOME/lib/jconsole.jar:$JAVA_HOME/lib/tools.jar:$WL_HOME/lib/wljmxclient.jar -J-Djmx.remote.protocol.provider.pkgs=weblogic.management.remote -debug ​

Once JConsole is started select the Remote process connection, and fill out the target MBeanServer URI, Username and Password fields:

​​service:jmx:iiop://140.87.10.42:7086/jndi/weblogic.management.mbeanservers.runtime​

Make sure you replace the host and port values with the ones you used in your deployment command line. The ​​username​​ and ​​password​​ values are also the ones you used as part of your deployment command line.

You should now be connected and able to see our application MBean under the ​​blog.wls.jmx.appmbean​​ tree entry:

You can experience with creating new properties using the ​​setProperty​​ operation, and browsing them with either the ​​getProperty​​ operation or ​​Properties​​ attribute. Notice that the ​​Properties​​ attribute is exposed to JMX clients as a ​​​TabularData​​​:

The conversion between the ​​Map​​ instance returned by our ​​public Map getProperties()​​ method and the ​​​TabularData​​​ exposed to JMX clients is performed by the MXBean implementation that is part of the JDK. More info on MXBeans and how they convert native types to Open Types is available ​​here​​.

You might wonder where is the property file persisted. The answer is it depends on the deployment staging mode, and whether the application is deployed as an ear file or an exploded directory. In most cases the file will be located in a temporary directory under the target server. For instance:

​​servers/jrfServer_admin/tmp/_WL_user/sample-mbean-app/dgiyyk/config/properties.data​

In this case, re-deploying the ear (provided the ear was modified) will overwrite the existing property file with the one contained in the ear file. Any new property added since the application was last deployed will be lost. It is also not possible to share the application's properties among several WebLogic servers, as each server gets its own local copy of the property file.

We can work around this by either:

Removing the property file from the ear file, and access it from a well known external location using regular file I/O. Deploying the application as an exploded ear accessible from all targeted WebLogic servers. java -classpath $WL_HOME/lib/weblogic.jar weblogic.Deployer -noexit -name sample-mbean-app -source build/exploded-ear -targets jrfServer_admin -adminurl t3://140.87.10.42:7086 -user weblogic -password gumby1234 -deploy
Where the exploded-ear directory contains: jar xvf sample-mbean-app.ear.
In this case the property file will be modified under its original location: build/exploded-ear/config/properties.data.

 

Note: If the property file is shared among several MBeans, then some form of synchronization will need to be provided across those MBeans when reading/writing the property file. We won't get into this in this blog.

Aside from JConsole we can also use a Java client to browse and edit our MBean. This is demonstrated in an ​​earlier blog entry​​. Another possibility is to use Oracle's Enterprise's Manager.

 

What's next?

Even thought our MBean is functional, it cannot be considered production ready. It is lacking descriptions for the MBean itself, its attributes, operations and operation parameters. The operation parameters are also lacking meaningful names as demonstrated below:

Our goal next time around will be to fix the above issues.