Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Management Extensions


July, 2004: Java Management Extensions

Managing devices, applications, and service-driven networks

Paul is a member of the technical staff at AudioAudit Inc. He can be contacted at paultremblett.ca.


Java Management Extensions (JMX) technology provides the architecture, design patterns, APIs, and services you need to manage resources in distributed applications. In this article, I discuss the JMX architecture and show how to create Managed Beans (MBeans), which are the objects you use to instrument resources so as to render them suitable for management. In the process, I present a television broadcast simulation application that uses the JMX API.

The Java Management Extensions architecture (http://java.sun.com/products/JavaManagement/) is comprised of instrumentation, agent, and adaptors levels. The components in these three levels form an entity called a JMX Agent, shown inside the dotted line in Figure 1.

At the instrumentation level, you find those resources that have been instrumented for management. They can include software entities such as queues, servlets, JavaServer Pages, Enterprise JavaBeans, and even nonJava legacy applications, provided they have been suitably wrapped. Software that interacts with hardware resources (such as printers) can also be instrumented for management. The mechanism used for instrumentation is the MBean.

MBeans representing manageable resources are registered in a repository owned and managed by an MBeanServer, which is an interface in the package javax.management.MBeanServer. MBeanServer is the core of the JMX agent and is found at the agent level in Figure 1. MBeans managed by the MBeanServer are identified by unique identifiers called "object names." An object name, represented by the class javax.Management.ObjectName, consists of the domain name and an unordered set of one or more key/value pairs; for example, DrDobbs:name=ourfirstbean,level=simple. The domain is separated from the key properties by a colon. To avoid naming conflicts, use a domain name according to the convention used by Java packages; for instance, the reverse DNS name of your organization (I use ca.tremblett). The ObjectName class defines methods for testing object names for equality, extracting the components of the name, and performing pattern matching.

Besides providing a managed repository, the MBeanServer acts as a proxy through which all operations performed on MBeans are routed. If you want to query or change an attribute of an MBean or invoke one of the operations it exposes, you cannot do so directly. All such actions must be done through MBeanServer.

MBeanServer is an interface that you typically do not implement; rather, you obtain an object that implements the interface by invoking one of the methods in the javax.management.MBeanServerFactory class.

At the adaptors level, you find protocol adaptors and/or connectors. Protocol adaptors provide a management view of JMX agents through a given protocol. Two of the more common protocol adaptors are the HTTP and SNMP adaptors. An alternative to adaptors are connectors, which consist of a connector server and connector client. The server is attached to an MBean server and listens for connection requests from clients. A connection client, which typically runs in a different JVM and often on a different computer, establishes a connection to the server and communicates with the remote MBeans via the remote JMX agent using the management interface provided by the distributed services on the manager side. The difference between applications that manage via protocol adaptors and applications that manage via connectors is that the former are aware of the underlying protocol, which is transparent to the latter. The JMX specification defines a Remote Method Interface (RMI) connector that can use either the Java Remote Method Protocol (JRMP) or Internet Inter-ORB Protocol (IIOP). It also defines an optional generic connector that uses the JMX Messaging Protocol (JMXMP), which is based on TCP sockets. The specification also provides for user-defined protocols.

Creating Simple MBeans

The simplest kind of MBean is the Standard MBean. Listing One is a standard MBean called "TrivialExample." This is an implementation class that implements a management interface. The relationship between the name of the implementation class and the name of the management interface follows a pattern. If the name of the implementation class is Widget, then the name of the management interface is WidgetMBean. Using this convention, it follows that the management interface implemented by TrivialExample is TrivialExampleMBean (Listing Two).

In Listing Two are a number of getters and setters like those in regular JavaBeans. There isn't a 1:1 relationship between the number of getters and setters. If an attribute is both readable and writeable, it has a getter and setter. If it is read-only, it only has a getter. In addition to the getters and setters, the interface defines other methods that represent operations that can be performed on the MBean.

The MBeanServer uses introspection and reflection to discover and expose attributes and operations for management. This introspection and reflection relies on proper naming conventions being observed in the management interface.

Observing an MBean in Action

To facilitate working with the MBeans, the program SimpleAgent.java (Listing Three):

  • Invokes the static method createMBeanServer() of the MBeanFactory class. This method returns an instance of an object that implements the MBeanServer interface.
  • Creates an HTMLAdaptorServer, which lets an HTML browser manage all MBeans in the agent. HTMLAdaptorServer is itself an MBean.
  • Registers the HTMLAdaptorServer with the MBeanServer.

When you start the agent by typing "java SimpleAgent," the program displays a message indicating that it is accepting connections on port 8787. If you access the URL http://localhost:8787 from a browser, you see something like Figure 2 that lists registered MBeans by domain. In SimpleAgent.java, you see this code:

private static final String ADAPTOR_NAME_BASE = "Adaptor:name=
html,port=";

The MBean that represents the HTMLAdaptorServer is identified by an ObjectName I created using a string resulting from the concatenation of ADAPTOR_NAME_BASE and the variable port, which is either the default port of 8787 or a value passed as a command-line argument. Remember that the domain is that part of the object name that appears before the colon; therefore, the adaptor instance is listed under the domain Adaptor. Click on "name=html,port=8787" and you see a list of the attributes of the adaptor exposed for management.

Whenever an MBeanServer object is instantiated, an MBeanServerDelegate is created. This delegate is an MBean that represents the MBeanServer from a management point of view. In this agent, it is registered in the JMImplementation domain. Click on "type=MBeanServerDelegate" and you see a list of the attributes of the MBeanServer exposed for management. They are read only.

To create an instance of the TrivialExample MBean in Listing One, click on the Admin button. After you fill in the resulting form (Figure 3) and click the Send Request button, the browser displays the message "The MBean [DrDobbs:name=ourFirstMBean,id=1] was successfully instantiated and registered." When you click on "Go To MBean View," the view of the MBean (Figure 4) is displayed. You can see the attribute named anAttributeYouCanOnlyRead has read-only (RO) access. If you examine TrivialExampleMBean (Listing Two), you see that it only has a getter method. Even though it is read-only from a management point of view, it is writeable by the MBean itself. In TrivialExample.java, you see that the getAnAttributeYouCanOnlyRead() method sets the value of the variable anAttributeYouCanOnlyRead each time it is invoked. To demonstrate this, enter a value of 10 into the entry field labeled "Reload period in seconds" and watch the display. You see the date/time updated each time the screen is refreshed. The attribute anAttributeYouCanReadAndWrite has both a getter and setter method, so it has RW (read-write) access. You can experiment with changing its contents by typing a new value in the entry field and clicking the Apply button.

In addition to the getters and setters, TrivialExampleMBean defines methods representing operations (Figure 4) that can be carried out by the MBean. If you enter a value into the entry field to the right of the first operation and click the invokeMethodArgument button, you see that it changes the value of the attribute named anAttributeYouCanSetByInvokingSomeMethod. If you click on the button associated with the second operation, it resets the attribute anAttributeYouCanReadAndWrite to the default value of "Change me."

While TrivialExample doesn't do much, it does provide the mechanisms you need to perform more advanced management. To manage a printer queue, for example, you could expose a read-only attribute that displays values such as DRAINED, QUEUEING, STOPPED, ERROR, and CRITICAL. If you saw that ERROR was displayed for successive screen refreshes, you could check the value of another read-only attribute that displays messages such as "paper jammed," "out of paper," "ink cartridge empty," and take the appropriate action. If CRITICAL is displayed, you might determine that print jobs are being delivered to the queue faster than they can be printed. You could examine the current job and, if you saw it contained complex graphics were taking a long time to print, you could change the value of a read-write attribute named maxAllowablePrintJobs. The setter method for that attribute could then grow the queue to accommodate more jobs. You could reset the value after the large job finished printing. MBean operations might include hold, resume, and drain.

Dynamic MBeans

The TrivialExample MBean is static. The attributes and operations exposed were defined when I wrote the code and remain constant for the life of the MBean. Another kind of MBean, the DynamicMBean (DynamicTrivialExample is an example of DynamicMBean and is available electronically; see "Resource Center," page 3), defines attributes and operations at runtime. The interface implemented by this MBean is AnInterfaceYouMightNotExpect:

public interface AnInterfaceYouMightNotExpect {
}

The relationship between AnInterfaceYouMightNotExpect and DynamicTrivialExample does not follow the pattern Widget/WidgetMBean. Moreover, it defines no getters, setters, or operations.

In spite of this, if you use the Admin function specifying DrDobbs as the domain, name=anotherExample as the key, and DynamicTrivialExample as the class name, the browser displays the message: "The MBean [DrDobbs:name=anotherExample] was successfully instantiated and registered." When you navigate to the MBean View, two attributes and one operation have been exposed for management. Furthermore, instead of the rather generic MBean description "Information on the management interface of the MBean," you see a description that is clearly specific to this MBean. If you click any of the hyperlinks in the list of attributes and operations, a popup window containing a description is displayed. Finally, if you perform the turnMeOff operation by clicking the button bearing that label, not only does the Status attribute change to "I am turned off," but turnMeOff is replaced by turnMeOn in the list of MBean operations.

How was all this accomplished starting with an interface that defined no getters, setters, or operations? The fact that DynamicTrivialExample contains 200 lines of code compared to 30 lines of code in TrivialExample should give a hint—it's all done by code at runtime.

There are several ways you can create a DynamicMBean. If you examine the javadocs for javax.management.DynamicMBean, you see that DynamicMBean is an interface and one of its known implementing classes is StandardMBean. The javadocs for StandardMBean show that it does provide implementations of all six methods defined by the DynamicMBean interface. These implementations return generic values. If you want to return values specific to your application, you can subclass StandardMBean and provide your own implementations of these methods. That is the approach I take with DynamicTrivialExample.

StandardMBean has a constructor reserved to subclasses that takes an MBean interface class as an argument. I use this constructor and pass AnInterfaceYouMightNotExpect.getClass() as an argument. After I invoke the constructor of the superclass, I create arrays containing MBeanConstructorInfo and MBeanAttributeInfo objects.

getMBeanInfo() checks if the instance variable mbInfo is null; if it is, I create an array containing a single MBeanOperationInfo object that defines the turnMeOff operation. I then create an MBeanInfo object by passing the class of my MBean (ca.tremblett.ddj.DynamicTrivialExample) a description of the MBean, the arrays of MBeanConstructorInfo, and MBeanAttributeInfo objects I created in the constructor, the array containing the MBeanOperationInfo object and a zero-length array of MBeanNotificationInfo objects to the constructor of the MBeanInfo class. I store the reference to this MBeanInfo object in mbInfo and my getMBeanInfo() method always returns this reference.

I also provide an implementation of getAttribute(), which returns the value of a single specified attribute and getAttributes(), which returns the values of several attributes. Since both attributes in my MBean are read only, I do not implement the setAttribute() or setAttributes() methods, but rely on the implementation inherited from the superclass.

Finally, I provide an implementation of invoke(), which invokes either turnMeOn() or turnMeOff() depending on the name of the operation it receives as an argument. Both turnMeOn() and turnMeOff() invoke toggleOnOff(), passing true or false, respectively, as an argument. If toggleOnOff() receives true as an argument, it creates an array containing a single MBeanOperationInfo object that defines the operation turnMeOff; otherwise, the array contains an MBeanOperationInfo object that defines the operation turnMeOn. The invoke() method then creates an MBeanInfo object using all of the arguments that getMBeanInfo() used to create mbInfo, with the exception of the fifth argument. For this argument it uses newOps. I store the newly created MBeanInfo object in mbInfo so the next time this method is invoked, you see the new operation. This explains why the contents of the MBean operations list changes each time you perform an operation.

The attributes I defined in the code can also be read from an XML file that users create and pass to the agent at runtime. This provides even greater flexibility and is easy for users if they utilize a tool that generates the XML.

Creating MBeans From Programs

While I used the JMX agent to create instances of both MBeans, you can also create MBeans programatically. TelevisionReceiver (available electronically) is a program that creates an MBean. This class represents a receiver that is capable of detecting a broadcast stream from network and cable television transmitters. For testing purposes, I use a BroadcastSimulator (available electronically) to generate a broadcast stream. Since only a single instance of BroadcastSimulator is needed, my getBroadcastSimulator() method in TelevisionReceiver uses the MBeanServer's queryMBeans() method to determine if such an MBean is already registered with the server. If the server reports that no such MBean is registered, I create one. I do this by passing two arguments to the MBeanServer's createMBean() method. The first argument is the class name of the MBean I want to create (ca.tremblett.BroadcastSimulator). The second argument is the ObjectName used to identify the MBean. I create the ObjectName using the string "DrDobbs:name=BroadcastSimulator."

I determined if a BroadcastSimulator MBean was registered by iterating over the elements of the Set returned by the queryMBeans() method. The JMX API provides another mechanism that can accept queries in the form of expressions. The API also provides the ability to define relations and roles, and you can use a RoleInfo object to define the minimum and maximum number of MBeans that can assign a role. Queries, relations, and roles are beyond the scope of this article.

Interacting with the Registration Process

I want to ensure that every instance of TelevisionReceiver has a property list of the form name=TelevisionReceiver,id=n, where n is an integer that identifies the receiver. If my MBean implements the MBeanRegistration interface and I provide implementations of postDeregister(), postRegister(), preDeregister(), and preRegister(), I can interact with and control the registration process. In this case, my implementation of preRegister() contains code that examines any domain or keys users might enter using web browsers and adjusts them to conform to the syntax I require. I provide empty implementations of the other three methods.

MBean Notifications

Polling a resource to determine its status is inherently evil. The better way is to be notified when some condition in the resource in which you have expressed an interest requires your attention. MBeans can emit notifications as described by the javax.management.Notification class. A Notification object contains the fields in Table 1.

An MBean that is intended to emit notifications must implement the javax.management.NotificationBroadcaster interface. This interface defines three methods: addNotificationListener(), removeNotificationListener(), and getNotificationInfo(). The notification mechanism used by JMX is the same as the one you have used from the JSDK, so you are already familiar with the first two methods. The third method returns an array of MBeanNotificationInfo objects, each of which describes the characteristics of a kind of notification emitted by an MBean.

An example of an MBean that emits notifications is BroadcastSimulator. At a specified interval, it generates and emits notifications with user data objects that contain information corresponding to a television commercial that has been steganographically encoded in a manner suitable for detection by a tuner with specialized hardware. It maintains its listeners in an ArrayList and each time it generates a Notification object with user data that is a BroadcastStreamEvent (available electronically), it iterates through the listeners. When listeners register themselves with the NotificationBroadcaster, they specify a NotificationFilter object to which the NotificationBroadcaster should send notifications by invoking the filter's isNotificationEnabled() method. If this method returns false, the NotificationBroadcaster takes no further action; otherwise, it invokes the listener's handleNotification() method passing the Notification object as an argument.

The broadcast stream events generated by BroadcastSimulator are detected by instances of Tuner (available electronically). A TelevisionReceiver can have a Tuner for each frequency it needs to monitor. My Tuner class implements NotificationListener and provides an implementation of the handleNotification() method defined by this interface. In my case, I simply extract data from the Notification's user data and use it to update attributes in the Tuner MBean. My Tuner class also implements NotificationFilter and provides an implementation of the isNotificationEnabled() method defined by this interface.

Putting It All Together

The management of the resources involved in the broadcast simulation application uses a sufficient number of the classes and interfaces from the JMX API to provide a good idea of how it all works. If you want to experiment, you could use the web interface to:

  • Create a BroadcastSimulator MBean. Specify DrDobbs as the domain name, name=BroadcastSimulator as the key, and ca.tremblett.ddj.BroadcastSimulator as the class.
  • Try to create another BroadcastSimulator MBean using a different domain and a different key. You will receive an MBean Failure message stating that multiple instances of BroadcastSimulator are not permitted. If you examine BroadcastSimulator's constructor, you see code that prevents multiple instances.
  • Create a TelevisionReceiver MBean. Specify any domain name you wish. Use id=1 as the key and ca.tremblett.ddj.TelevisionReceiver as the class name. After the MBean has been created, note that no matter what you used as a domain, the MBean ends up in the DrDobbs domain. Examine the TelevisionReceiver's preRegister() method and you see code that makes the adjustment to the object name. The TelevisionReceiver MBean locates and uses the instance of BroadcastSimulator just created.
  • Create several other TelevisionReceiver MBeans using id=2, id=3, and so on as keys.
  • Create several Tuner Mbeans. The key for each Tuner takes the form "receiver=n,slot=m," where n is the ID of the TelevisionReceiver in which the Tuner is located and m the slot it occupies. When a Tuner MBean is first created, its status is "unconfigured." As you create each Tuner, configure it by setting the call letters to the name of a television station (WABC, for example), the channel to a number between 1 and 32, and the frequency to any double since it is not used in this version. When all configuration values have been entered, the status changes from "unconfigured" to "suspended." Start the tuner by performing the "resume" operation.
  • Add System.out.println statements at those points in the code where you want to learn more about what it is doing.

Conclusion

The importance of resource management cannot be overstated. As you develop a new resource, you should ask yourself whether it should participate in resource management. You should make this determination early in the development process. If the answer is "yes," MBeans and the JMX API provide everything you need to accomplish the task.

DDJ



Listing One

import java.util.Date;
public class TrivialExample implements TrivialExampleMBean {
  private Date readOnlyAttribute = new Date();
  private String readWriteAttribute = "Change me";
  private String settableAttribute;
  public Date getanAttributeYouCanOnlyRead() {
    readOnlyAttribute = new Date();
    return readOnlyAttribute;
  }
  public String getanAttributeYouCanReadAndWrite() {
    return readWriteAttribute;
  }
  public void setanAttributeYouCanReadAndWrite(String a) {
    readWriteAttribute = a;
  }
  public String getanAttributeYouCanSetByInvokingSomeMethod() {
    return settableAttribute;
  }
  public void invokeMethodWithoutArgument() {
    readWriteAttribute = "Change me";
  }
  public void invokeMethodWithArgument(String s) {
    settableAttribute = s;
  }
}
Back to article


Listing Two
import java.util.Date;
public interface TrivialExampleMBean {
  public Date getanAttributeYouCanOnlyRead();
  public String getanAttributeYouCanReadAndWrite();
  public void setanAttributeYouCanReadAndWrite(String s);
  public String getanAttributeYouCanSetByInvokingSomeMethod();
  public void invokeMethodWithoutArgument();
  public void invokeMethodWithArgument(String arg);
}
Back to article


Listing Three
import com.sun.jdmk.comm.HtmlAdaptorServer;
import javax.management.ObjectName;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;

public class SimpleAgent {
  private static final int DEFAULT_PORT = 8787;
  private static final String ADAPTOR_NAME_BASE =
    "Adaptor:name=html,port=";
  public static void main(String[] args) {
    int port = DEFAULT_PORT;
    System.out.print("Creating server ");
    try {
      switch (args.length) {
        case 0:
          System.out.println("using default port (" + DEFAULT_PORT + ")");
          break;
        case 1:
          port = Integer.parseInt(args[0]);
          System.out.println("using port " + port);
          break;
        default:
          port = Integer.parseInt(args[0]);
          System.out.println("using port " + port);
          System.out.println("Ignoring " + args.length +
            " extraneous argument" + ((args.length == 2) ? 
            "" : "s"));
          break;
      }
    }
    catch (NumberFormatException e) {
      System.err.println("\nUnable to parse " + args[0] + " as port number");
      System.exit(1);
    }
    MBeanServer server = MBeanServerFactory.createMBeanServer();
    HtmlAdaptorServer adaptor = new HtmlAdaptorServer(port);
    ObjectName adaptorObjectName = null;
    String adaptorName = null;
    try {
      adaptorName = ADAPTOR_NAME_BASE + Integer.toString(port);
      adaptorObjectName = new ObjectName(adaptorName);
      server.registerMBean(adaptor, adaptorObjectName);
    }
    catch(Exception e) {
      System.err.println("\nUnable to create HTML adaptor named: " 
           + adaptorName);
      e.printStackTrace();
      System.exit(1);;
    }
    adaptor.start();    
    System.out.println("HTMLAdaptor started");
    System.out.println("Accepting connections on port " + port);
  }   
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.