Skip navigation

The number one question I get about the JMX API at conferences and other public events is whether there will be support for defining MBeans using annotations. People see that they can make EJBs or Web Services just by adding annotations to a POJO, and they ask why they can't make MBeans the same way. In version 2.0 of the JMX API, being defined by JSR 255, this will be possible.

The exact details are still subject to change as a result of discussions within the JSR 255 Expert Group, but here's a snapshot of where we are now. I think the final version will be fairly close to this.

In addition to defining MBeans with annotations, there are some new proposed annotations that will also apply to MBeans defined in the existing ways.

Prior art

Several projects already exist that provide this functionality, but the most developed is probably Spring. So our starting point was Spring's MBean annotations (see also API documentation).

Defining an MBean

In the proposed design, an MBean can be defined through annotations to achieve the same effect as a Standard MBean. In this and other examples, I'll show what you write with today's API, and what you'll be able to write with the new annotations.

         
TodayTomorrow
public interface CacheMBean {
    public int getSize();
    public void setSize(int size);

    public int getUsed();

    public int dropOldest(int n);
}

public class Cache implements CacheMBean {

    public int getSize() {...}

    public void setSize(int size) {...}


    public int getUsed() {...}


    public int dropOldest(int n) {...}
}
 







@MBean
public class Cache {
    @ManagedAttribute
    public int getSize() {...}
    @ManagedAttribute
    public void setSize(int size) {...}

    @ManagedAttribute
    public int getUsed() {...}

    @ManagedOperation
    public int dropOldest(int n) {...}
}

This defines an MBean with read-only attributeUsed, read-write attribute Size, and operation dropOldest.

I'll call an MBean defined this way an @MBean.

One way to look at this is that with the existing Standard MBeans, public methods from the class are picked out as being management methods by virtue of being in the Standard MBean interface that the class implements. So in this example theCacheMBean interface defines which methods inCache are the management methods. In the new form, the methods are picked out by being annotated, and there is no need to define an interface.

Pros and cons of @MBeans

The new style appears considerably more convenient than Standard MBeans. You only have to maintain one source file, rather than managing a class and an interface.

There is a downside, however, which may show up in bigger projects. The advantage of the Standard MBean approach is that the MBean interface tells you exactly what the attributes and operations of the MBean are. There is no extraneous information in the MBean interface: every method defines an attribute or an operation.

On the other hand, with @MBeans the management attributes and operations are potentially mixed in with many other methods, public or private. So it is not immediately obvious what the management interface of the MBean is.

This disadvantage applies both when reading the source code and when looking at the Javadoc output.

A second disadvantage is that it is no longer possible to construct a proxy. Proxies simplify client code by allowing it to access attributes and operations as compiler-checked method calls. They don't matter if you are only going to interact with your MBeans through a graphical interface like JConsole, but they are a big help if you are writing an application that will interact with your MBeans specifically.

For smallish projects, these disadvantages are likely to be minor. Furthermore, it should be possible to define an annotation processor that extracts a Standard MBean interface from an @MBean, so it can be used for documentation and proxying. In the example above, the annotation processor could create theCacheMBean interface every time you compile your program, based on the @ManagedAttribute and@ManagedOperation annotations in theCache class.

Defining an MXBean

The existing @MXBean annotation can be used instead of@MBean to define an MXBean rather than a Standard MBean.

@MXBean
public class Cache {
    ...remainder as above...
}

Descriptions

Although the JMX API allows for textual descriptions to be associated with attributes, operations, and parameters, when you use a Standard MBean today these descriptions have meaningless default values. I've written before about how you can add meaningful descriptions, but it isn't easy. This is a really obvious use for annotations.

The proposed new @Description annotation can be used with Standard MBeans, MXBeans, and @MBeans. (Notice that both columns use the new API here!)

         
Tomorrow's Standard MBeanTomorrow's @MBean
@Description("some sort of cache")
public interface CacheMBean {

    @Description("number of cache slots in use")
    public int getUsed();
    ...
}

public class Cache implements CacheMBean {

    public int getUsed() {...}
    ...
}
@Description("some sort of cache")
public class Cache {
    @ManagedAttribute
    @Description("number of cache slots in use")
    public int getUsed() {...}
    ...
}






We international types will of course be thinking about internationalization, and I'll have more to say about that below.

Finding the MBeanServer and/or ObjectName

Often an MBean needs to know what MBean Server it is registered in, or what its name is in that MBean Server. To do this it currently needs to implement the MBeanRegistration callback interface. The required values are passed to that interface's preRegister method. But the interface contains three other methods, which the MBean must implement even if it has nothing interesting to do in them.

In the new proposal, the @Resource annotation from javax.annotation can be used instead of implementing MBeanRegistration when all that's needed is to discover what the MBeanServer or ObjectName is:

         
TodayTomorrow
 
public class Cache
        implements CacheMBean, MBeanRegistration {

    private volatile MBeanServer mbs;

    private volatile ObjectName myName;

    public ObjectName preRegister(
            MBeanServer mbs, ObjectName name) {
        this.mbs = mbs;
        this.myName = name;
        return name;
    }
    public void postRegister(Boolean done) {}
    public void preDeregister() {}
    public void postDeregister() {}

    ...
}
@MBean
public class Cache {

    @Resource
    private volatile MBeanServer mbs;
    @Resource
    private volatile ObjectName myName;











    ...
}

When the MBean is registered, the MBean Server will injectthe appropriate values into these fields.

This possibility is open to all types of MBeans, not just @MBeans. You could continue to have a Standard MBean as today, but stop implementing MBeanRegistration in favour of@Resource annotations.

Simplified notification handling

Today, if an MBean emits notifications then it must implement the NotificationBroadcaster or NotificationEmitter interface. This means it must keep track of the set of listeners, as listeners are added and removed. It must also define the list of notification types that it can emit, by implementing getNotificationInfo().

In practice, everybody uses the NotificationBroadcasterSupport class instead of doing all this work themselves. In the simplest case, you just inherit from that class, and pass the list of notification types to the superclass constructor. If you already have a superclass, then you need to have a privateNotificationBroadcasterSupport instance and delegate the NotificationBroadcaster methods to it.

New annotations allow you to define the list of notification types more simply, and to emit notifications without having to keep track of listeners.

         
TodayTomorrow
 


public class Cache
        extends NotificationBroadcasterSupport
        implements CacheMBean {
    public Cache() {
        super(new MBeanNotificationInfo[] {
            new MBeanNotificationInfo(
                new String[] {"my.notif.type"},
                Notification.class.getName(),
                "my notification"
            )}
        );
    }

    ...
    void somethingHappened() {
        Notification n = new Notification(...);
        super.sendNotification(n);
    }
    ...
}
@MBean
@NotificationInfo(types={"my.notif.type"},
    description=@Description("my notification"))
public class Cache {


    @Resource
    private volatile SendNotification send;








    ...
    void somethingHappened() {
        Notification n = new Notification(...);
        send.sendNotification(n);
    }
    ...
}

The @NotificationInfo annotation is what allows you to avoid constructing an MBeanNotificationInfo as in the messy "Today" code.

The new SendNotification interface contains just the method sendNotification. When you register this MBean in the MBean Server, it will inject an object into thesend field which allows the MBean to send notifications. The MBean no longer has to be concerned with managing listeners, which happens somewhere behind the scenes.

Resource injection of SendNotification is available to all types of MBeans. Defining the notification types with@NotificationInfo is not available to Dynamic MBeans, which are expected to provide a complete MBeanInfo, including theMBeanNotificationInfo[] array.

More detail than you want to know about@NotificationInfo appears below.

You can stop reading now

If your eyes are already glazing over with all this code, you can safely stop here, and you'll have seen the main ideas. The remainder of this entry is about secondary items, and further details about the main ones.

Descriptor contents

In the JMX API included in the Java SE 6 platform, we introduced a way to define your own annotations to specify Descriptor contents. So you might define @Units like this:

@Documented @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Units {
    @DescriptorKey("units")
    String value();
}

The new API accepts such annotations on classes or methods that also have the @ManagedX annotations. For example:

@MBean
public class Cache {
    ...
    @Units("bytes")
    @ManagedAttribute
    public int getUsed() {...}
    ...
}

We've also added a new @DesriptorFields annotation, since the use of @DescriptorKey is somewhat non-obvious, and overkill for occasional use. So you can achieve the same effect like this:

@MBean
public class Cache {
    ...
    @DescriptorFields("units=bytes")
    @ManagedAttribute
    public int getUsed() {...}
    ...
}

This annotation can be used in Standard MBeans and MXBeans as well as @MBeans and @MXBeans.

Operation impact

The @ManagedOperation annotation has an optional element impact of type Impact. This is a new enum with values {INFO, ACTION, ACTION_INFO, UNKNOWN} corresponding to the integer codes defined by MBeanOperationInfo. So you can do this:

@MBean
public class Cache {
    ...
    @ManagedOperation(impact = Impact.ACTION)
    public int dropOldest(int n) {...}
}

You can also apply @ManagedOperation to a method in a Standard MBean interface or an MXBean interface in order to specify the impact.

MBean constructors

Each public constructor in an @MBean or @MXBean is converted into an MBeanConstructorInfo inside the MBean'sMBeanInfo. This is exactly the same as for existing Standard MBeans and MXBeans.

StandardMBean class

The class StandardMBean can be used to customize an @MBean or an @MXBean in the same way as for a Standard MBean or MXBean today. You simply supply null for thembeanInterface parameter in the constructor.

Details on Resource injection

The @Resource annotation can be used to inject anObjectName, MBeanServer, orSendNotification. The annotation can be applied to a field or to a void method with a single parameter. For example:

    @Resource  // field injection
    private volatile ObjectName name;

    private MBeanServer mbs;
    @Resource  // method injection
    private synchronized void setMBeanServer(MBeanServer mbs) {
        this.mbs = mbs;
    }

The MBean Server determines what to inject based on the type. The type is either the declared type of the field or parameter, or it is specified explicitly in the @Resourceannotation. For example, the following annotations have the same effect:

    @Resource
    private volatile ObjectName name;

    @Resource(type = ObjectName.class)
    private volatile Object name;

I don't think the second form will be used very often, but it might be used to inject the MBeanServer into a field of type MBeanServerConnection, for example.

The ObjectName (etc) will be injected as many times as there are appropriate @Resource annotations, including in parent classes.

@Resource annotations that don't match one of the given types are ignored. (Perhaps they are for some other API.) But even if the type is not recognized, @Resource fields and methods must be instance (not static), and@Resource methods must have exactly one parameter and return void.

I've used volatile in all these examples because the Java Memory Model would not otherwise guarantee that the MBean would actually see the injected values. For method-based injection,synchronized is an alternative, provided the MBean also uses synchronized to access the injected value. Notice that the same considerations apply to the existingMBeanRegistration technique.

Resource injection happens after the MBean's preRegister method (if any) is called, but before the MBean is registered in the MBean Server. If an injection method throws an exception, then postRegister(false) will be called and the exception will be thrown in the same way as for preRegister.

More on descriptions

In addition to the description text, the@Description annotation can specify the values of thedescriptionResourceBundleBaseName anddescriptionResourceKey fields in the correspondingDescriptor. This is enough to allow for internationalization:

@Description(value="some sort of cache",
             key="cache.mbean.description",
             bundleBaseName="MyResources")

To complete the story here, we need to have something that is able to apply these Descriptor fields to localize theMBeanInfo. We have some ideas on what that something might look like, but they are not yet fully formed.

More on @NotificationInfo

As I threatened, here is more information than you wanted to know about @NotificationInfo.

If an MBean has a @NotificationInfo annotation, then that annotation is translated into an MBeanNotificationInfo in the MBean's MBeanInfo. MBeanNotificationInfo includes aname which is the name of the notification class. It is usually"javax.management.Notification", but it might be a subclass. So @NotificationInfo has an optionalnotificationClass element which is aClass<? extends Notification>. For example:

@NotificationInfo(types = {AttributeChangeNotification.ATTRIBUTE_CHANGE},
                  notificationClass = AttributeChangeNotification.class)
   

If the MBean can emit more than one class of MBean, then it can use @NotificationInfos:

@NotificationInfos(
    @NotificationInfo(types = {"my.first.notif", "my.second.notif"})
    @NotificationInfo(types = {AttributeChangeNotification.ATTRIBUTE_CHANGE},
                      notificationClass = AttributeChangeNotification.class)
)

The @NotificationInfo is applied to an MBean class, but a @Description on that class applies to the MBean, not its notifications. The existence of@NotificationInfos is another reason why we cannot use@Description straightforwardly.

This is why there is an optional element of type@Description inside @NotificationInfo, so you would write:

@NotificationInfo(types={"my.notif.type"},
      description=@Description(value="my notification", key="my.notif.descr"))

You cannot use @DescriptorFields, for the same reason as @Description, so there's another optional element that allows you to write:

@NotificationInfo(types={"my.notif.type"},
      descriptorFields={"foo=bar"})

Ideas still in progress

We're studying the possibility of providing a way to cause a notification that is sent every time a given operation is completed.

We're looking at ways in which an MBean could say what its ObjectName is. Probably the MBean would only provide a subset of the information needed to construct the name. It's still unclear exactly what this might look like.

What next?

This is still work in progress, as you'll have gathered. I'm very much interested in comments and suggestions, either here or atjmx-spec-comments@sun.com. Thanks!

[Tags: jmx jsr jsr255 annotations]

The JMX Best Practices guide says notifications can sometimes be lost. Why is that? When might it happen? Read on.

Here's the relevant text from the Best Practices guide:

It is important to be aware of the semantics of notification delivery when defining how notifications are used in a model. Remote clients cannot assume that they will receive all notifications for which they are listening. The JMX Remote API only guarantees a weaker condition:

A client either receives all notifications for which it is listening, or can discover that notifications may have been lost.

This text might seem somewhat alarming. First of all, notice that it only applies to remote clients. A local client (within the same Java VM) will reliably get all notifications it asks for.

Secondly, the text is describing something that will only happen in unusual circumstances. Notifications will only be lost when they arrive so fast that they cannot be delivered to the remote client quickly enough, or if there is a long network outage during which enough notifications arrive to overflow the notification buffer on the server. If you're sure that the rate of notifications is always low then you probably don't need to worry. Long network outages will probably trigger other problems in your client, so you'll need to deal with them more generally than just worrying about lost notifications.

Careful clients

But if you have many notifications, you probably want to follow the advice in the subsequent paragraphs of the Best Practices guide:

Notifications should never be used to deliver information that is not also available in another way. The typical client observes the initial state of the information model, then reacts to changes in the model signalled by notifications. If it sees that notifications may have been lost, it goes back and observes the state of the model again using the same logic as it used initially. The information model must be designed so that this is always possible. Losing a notification must not mean losing information irretrievably.

When a notification signals an event that might require intervention from the client, the client should be able to retrieve the information needed to react. This might be an attribute in an MBean that contains the same information as was included in the notification. If the information is just that a certain event occurred, it is often enough just to have a counter of how many times it occurred. Then a client can detect that the event occurred just by seeing that the counter has changed.

Stateless servers

The design of the existing standard connectors is such that notification loss can happen when there are many notifications coming from the MBeans in the MBean Server. This is true even for clients that are only listening for a small subset of those notifications. In the extreme case, a client that is listening for a very rare notification might not see it, because other MBeans are generating frequent notifications that nobody is listening to. Once again, the client can tell that this has happened (via JMXConnector.addConnectionNotificationListener).

The existing connectors behave like this because they have been designed to have no non-transient state on the server. A consequence is that the server has no non-transient record of which clients are interested in which notifications. Therefore it has to store all notifications in its buffer, in case some client it doesn't remember is interested in them.

The servers were designed to have no non-transient state for better scalability. In retrospect, this was probably a design mistake. In many client/server systems, you have one server, or just a few servers, and a large number of clients. So limiting state in the server is an excellent idea, because it allows the server to handle many more clients. But in management systems, the situation is usually the opposite: you typically have one client (a management program such as JConsole) that may connect to and manage many servers. There are no common use cases where a server might have a large number of JMX clients.

In version 2.0 of the JMX API, being defined by JSR 255, we are adding anEvent Service. Among other things, this will fix the problem where a client might lose notifications that it is interested in because there are many other notifications that it is not interested in.

Notification loss is inevitable

Even with the new Event Service, notification loss will still be possible, however. Can't we get rid of it?

To answer this question, consider what happens when notifications are produced faster than they can be handled. This might be because of network delays, or because the client needs to do some work for each notification. Suppose this situation persists. What should the system do?

There are basically three possibilities:

  1. Some notifications are eventually dropped. This is what the JMX Remote API does, and it is also what the new Event Service will do.
  2. Notification senders are slowed down. This is what usually happens in the local case. An MBean sends a notification to a local listener by invoking the listener's handleNotification method. Unless it has multiple threads, the MBean will wait for that method to complete before doing anything else, including sending any more notifications.
  3. Notifications accumulate in an unbounded buffer. This is actually the worst solution. In the real world there is no such thing as an unbounded buffer. And even if you save the notifications in a giant disk, which is effectively unbounded, you still haven't fixed the problem that the client is getting further and further behind the server. When the client finally gets a notification that was sent yesterday, is that still any use?

When we were designing the JMX Remote API, we assumed that most MBeans that send notifications were not expecting sending to be slow. In the local case, sending is just invoking a method, and that method is usually punctual. If we had wanted to apply solution 2, slowing down senders, that could have broken the assumptions of existing MBeans. Coding MBeans so that they can cope with a blocked send would also be considerably more difficult. So, even though this solution (flow control) is arguably better, we were reluctant to impose it.

The future: JMX Event Service

As I mentioned, in version 2.0 of the JMX API we are designing a new Event Service. This will be part of the JDK 7 platform. Though it will not eliminate notification loss, it will significantly reduce the likelihood of such loss. And it will also allow you to plug in your own transport for notifications. In particular you could plug in the Java Message Service to use an existing message bus.

[Tags: jmx jms.]