JEP draft: JMX Specific Annotations for Registration of Managed Resources

AuthorJaroslav Bachorik
OwnerJaroslav Bachorík
Created2014/06/02 10:38
Updated2017/03/01 14:08
TypeFeature
StatusSubmitted
Componentcore-svc / javax.management
ScopeSE
Discussionjmx dash dev at openjdk dot java dot net
EffortS
DurationM
Priority3
Reviewed byMikael Vidstedt, Staffan Larsen
Endorsed byMikael Vidstedt
Issue8044507

Summary

Provide a set of annotations for registration and configuration of manageable service - also known as MBeans.

Goals

The primary goal is to make it easier for developers to register and configure MBeans. The secondary goal is improving the readability and coherency of the source code by keeping all parts of an MBean declaration in one place.

Non-Goals

Deprecation and replacement of the current ways to register and configure MBeans.

Motivation

Current mechanism of defining an MBean requires to provide an MBean interface and its implementation. The interface and the implementation must conform to the strict naming and visibility rules in order for the introspection to be able to bind them.

At least the same level of verbosity is required when adding an MBeanInfo to generate MBean metadata.

All this leads to a rather verbose code containing a lot of repeating boilerplate parts even for the most simple MBean registrations.

There is a 3rd party implementation of MBean registration and configuration annotations in Spring and it became de-facto standard among developers - indicating that this approach should be a part of the JMX standard.

Description

The implementation will consist of a set of annotations for marking certain classes for publishing via JMX mechanisms and specifying their attributes and operations.

There will be an annotation processor validating the placed annotations and their attributes.

The managed resources specified by the annotations will be fully usable within the current JMX system and accessible to older clients without any changes necessary.

All the annotations will be placed in javax.management.annotations package not to further clutter the javax.management one.

@ManagedService

Marking a managed service implementation. It must be applied to non-abstract classes only. By using this annotation each instance of the annotated class will become MXBean compatible.

Arguments

Definition

@interface ManagedService {
    String objectName() default "";
    String description() default "";
    Class<?> service() default Object.class;
    Tag[] tags() default {}'
}

@ManagedAttribute

Annotates a field or a method conforming to the getter/setter pattern within a class annotated by @ManagedService annotation to become accessible as a managed attribute.

Arguments

Definition

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface ManagedAttribute {
    String name() default "";
    String description() default "";
    Access access() default AttributeAccess.READWRITE;
    String getter() default "";
    String setter() default "";
    String units() default "";
    Tag[] tags() default {};
}

@ManagedOperation

Annotates a method within a class annotated by @ManagedService annotation to become accessible as a managed operation.

Arguments

Definition

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ManagedOperation {
    String name() default "";
    String description() default "";
    Impact impact() default Impact.UNKNOWN;
    String units() default "";
    Tag[] tags() default {};
}

@ParameterInfo

Annotates a method parameter within a class annotated by @ManagedService annotation to provide more specific metadata.

Arguments

Definition

@Target(value = ElementType.PARAMETER)
@Retention(value = RetentionPolicy.RUNTIME)
@interface ParameterInfo {
    String name() default "";
    String description() default "";
    String units() default "";
    Tag[] tags() default {};
}

@NotificationInfo

When a field of NotificationSender is annotated by this annotation it will get injected by the actual implementation allowing to emit notifications.

Arguments

Definition

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotificationInfos.class)
public @interface NotificationInfo {
    Class<? extends javax.management.Notification> implementation() default javax.management.Notification.class;
    String description() default "";
    String[] types();
    int severity() default "";
    Tag[] tags() default {};
}

@NotificationInfos

When a field of NotificationSender is annotated by this annotation it will get injected by the actual implementation allowing to emit notifications. The enclosed NotificationInfo anotatations will be used for filling in the MBeanNotificationInfo array in the associated MBeanInfo instance.

Arguments

Definition

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface NotificationInfos {
    NotificationInfo[] value();
}

@RegistrationHandler

A convenient way to express that the MBean is interested in the registration lifecycle events. A method annotated by this annotation will be called for the lifecycle events. The annotated method must take exactly one parameter of type RegistrationEvent.

If a managed service wishes to intercept the fine grained registration callbacks it just needs to implement MBeanRegistration interface.

Definition

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface RegistrationHandler {
}

Enumeration Impact

Closely related to the MBeanManagedOperation#(INFO|ACTION|ACTION_INFO|UNKNOWN) constants.

public static enum Impact {
    INFO, ACTION, ACTION_INFO, UNKNOWN
}

Enumeration RegistrationKind

public enum RegistrationKind {
    REGISTER, DEREGISTER, FAIL
}

Enumeration AttributeAccess

public enum AttributeAccess {
    READ, WRITE, READWRITE
}

Class RegistrationEvent

/**
 * Registration event type. May be obtained in a method annotated by
 * {@linkplain RegistrationHandler} annotation.
 */
final public class RegistrationEvent {
    private final RegistrationKind kind;
    private final MBeanServer mbs;
    private final ObjectName on;

    public RegistrationEvent(RegistrationKind kind, MBeanServer mbs, ObjectName on) {
        this.kind = kind;
        this.mbs = mbs;
        this.on = on;
    }

    /**
     * Registration event kind.
     * @return A {@linkplain RegistrationKind} value
     */
    public RegistrationKind getKind() {
        return kind;
    }

    /**
     * The {@linkplain MBeanServer} this event was generated by.
     * @return The associated {@linkplain MBeanServer} instance
     */
    public MBeanServer getMBeanServer() {
        return mbs;
    }

    /**
     * The name of the MBean for which the event was generated.
     * @return The MBean's {@linkplain ObjectName}
     */
    public ObjectName getObjectName() {
        return on;
    }
}

Interface NotificationSender

/**
 * <p>Interface for marking a class as being able to send notifications</p>
 *
 * @since 1.9
*/
public interface NotificationSender {
    /**
     * <p>Sends a standard notification.</p>
     * 
     * @param type The notification type.
     * @param message The notification message.
     * @param userData The user data. It is used for whatever data
     * the notification source wishes to communicate to its consumers.
     */
    void sendNotification(String type, String message, Object userData);

    /**
     * Sends a custom notification.
     *
     * @param notification The notification to send.
     */
    void sendNotification(javax.management.Notification notification);
}

Usage

Define the Service

// The following class is a managed service implementation
// It will be registered under the provided objectName if not overridden
// The "service" parameter is optional; it serves as a hint for creating the service proxy and the managed service does not necessarily need to implement the interface
@ManagedService(
    objectName="net.java.jmx:type=StandardService", 
    description="A simple service exposed as an MXBean", 
    service=Service.class, 
    tags = {
        @Tag(name = "tag1", value = "val1"), 
        @Tag(name = "tag2", value = "val2")
    }
)
public class SimpleService {
    @NotificationInfo(types = "test.mbean.label", description = "Label was set")
    @NotificationInfo(types = "test.mbean.threshold", description = "Counter threshold reached")
    private NotificationSender ns;
    
    // A read-only attribute measured in "ticks"
    @ManagedAttribute(access = AttributeAccess.READ, units = "ticks")
    int counter = 1;

    // A read-only attribute being an array of strings
    @ManagedAttribute(access = AttributeAccess.READ)
    String[] arr = new String[]{"sa", "ba", "ca"};

    // Declare a read-only attribute ...
    @ManagedAttribute(access = AttributeAccess.READ)
    private String label = "the label";

    // ... and augment it with a complex setter later on
    @ManagedAttribute
    public void setLabel(String l) {
        ns.sendNotification("test.mbean.label", "Label set", l);
        label = l;
    }
    
    // an operation modifying the 'counter' attribute and sending a custom notification
    @ManagedOperation(impact = Impact.ACTION, description = "Increases the associated counter by 1")
    public int count() {
        if (counter >= 5) {
            ns.sendNotification("test.mbean.threshold", "Threshold reached", counter);
        }
        return ++counter;
    }

    // an operation declaring custom 'units' metadata for one of its parameters
    @ManagedOperation
    public void checkTime(@Parameter(units = "ms") long ts) {
        System.err.println(new Date(ts));
    }

    // handle the registration/unregsitration of the MBean
    @RegistrationHandler
    public void onRegistration(RegistrationEvent re) {
        switch(re.getKind()) {
            case REGISTER: {
                System.err.println("Registered " + re.getObjectName().getCanonicalName());
                break;
            }
            case UNREGISTER: {
               System.err.println("Unregistered " + re.getObjectName().getCanonicalName());
               break;
            }
        }
    }
}

Alternatives

Adopting Spring annotations

Spring JMX Annotations

Instead of creating a similar but new set of annotations fitted for the in-JDK JMX implementation the already existing Spring annotations could be used.

Pros

Cons

Re-introducing JMX 2.0 annotations

JMX 2.0 Annotations Proposal

This proposal was the part of JSR 255. It served as an inspiration for this JEP while trying to improve the ease of use and reduce complexity by implementing just the most important features.

The JMX 2.0 annotations implementation is cca. 90% complete with test cases.

Pros

Cons

Testing

Unit tests with good code coverage will be provided. Current reg tests and JCK tests must still pass unchanged.

Risks and Assumptions

There are no major risks associated with this feature. Similar work has already been done by the others so this is no uncharted territory.

Dependences

No currently known dependencies.

Impact