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.