I've written a custom Spring MBeanExporter that takes a collection of pre-created objects and creates the mbeans for them. It apparently uses the "default" strategy for determining attributes and operations, just taking the existing properties and operations of the associated class.
I just have an "afterPropertiesSet()" method that does some work, populates the base "beans" list, and then calls its superclass method. This works reasonably well.
I now want to see if I can get it to utilize any "@Managed..." annotations on the associated class. For my first try, I simply put the expected annotations on the associated class without changing how the "beans" list is populated and processed. Unfortunately, this didn't work. I added several "description" attributes to the class, attributes, and operations, but these didn't show up in VisualVM.
Is there something I can do to make the MBeanExporter mechanism use the @Managed... annotations on the associated class?
Note that my current class extends MBeanExporter. If I change it to extend AnnotationMBeanExporter, then it fails on the classes that do NOT have @Managed... annotations. I need something that defaults to what "MBeanExporter" does, unless it finds @Managed... annotations in a class.
I guess I need to show some code, but this will be mostly just pseudocode.
My MBeanExporter looks something like this:
public class MyMBeanExporter extends MBeanExporter {
@Override
public void afterPropertiesSet() {
// Do some pre-work to determine the list of beans to use.
Map<String, Object> beans = new HashMap<String, Object>();
... stuff
setBeans(beans);
// Now let the superclass create mbeans for all of the beans we found.
super.afterPropertiesSet();
}
One of the beans that gets put into the list has a class that looks like this:
@ManagedResource(objectName = ":name=fancystuff", description = "This is some stuff")
public class Stuff {
private int howMuchStuff;
@ManagedAttribute(description = "This tells us how much stuff we have")
public int getHowMuchStuff() { return howMuchStuff; }
public void setHowMuchStuff(int howMuchStuff) { this.howMuchStuff = howMuchStuff; }
@ManagedOperation(description = "Use this to add more stuff")
public void makeSomeMoreStuff(int stuffToAdd) {
howMuchStuff += stuffToAdd;
}
}
When this gets rendered in VisualVM, none of the metadata described in the @Managed... annotations is used. I can tell this for certain because the resulting ObjectName isn't the overriding value that I specified in the "@ManagedResource" annotation.
If I instead change the base class to "AnnotationMBeanExporter", then the bean associated with this class does get the metadata that I specified in the annotations. However, all the other beans that are associated with classes that do NOT have a "@ManagedResource" annotation all fail with exceptions like this:
InvalidMetadataException: No ManagedResource attribute found for class: class ...
My temporary workaround is simply to define my MBeanExporter subclass so it can behave as either a plain MBeanExporter or an AnnotationMBeanExporter, depending on a constructor flag. Then, I can simply define two instances of it, one with the flag, and one without, and with a different set of paths to process. This works.
My next thing to try is to have a single "fake" MBeanExporter that internally manages an MBeanExporter and an AnnotationMBeanExporter. It will build the initial beans list, but then process each one, looking at the class associated with the bean to see if the @ManagedResource annotation is present. That will indicate whether it will end up in the list of beans to be processed by the AnnotationMBeanExporter or the regular one.
Update: I've hit a problem with this strategy, as I can't just create a raw AnnotationMBeanExporter and call "afterPropertiesSet()" on it. It fails with:
MBeanExportException: Cannot autodetect MBeans if not running in a BeanFactory
Ok, I think I have something that works now. I'm not sure whether this is all correct. As Martin described, I created new "assembler" and "naming strategy" classes that combine the behavior of the "default" and "annotation" variations, such that if the class in scope has a "ManagedResource" annotation, then it uses the behavior from MetadataMBeanInfoAssembler & MetadataNamingStrategy, otherwise SimpleReflectiveMBeanInfoAssembler & KeyNamingStrategy.
The implementation of the "naming strategy" subclass was somewhat simple, but the "assembler" implementation was a little more annoying because I simply had to copy the body of all the methods in MetadataMBeanInfoAssembler that were overridden and insert them in a wrapper check for the class annotation. There was no way to simply have an embedded "MetadataMBeanInfoAssembler" and call methods on it.
Here is my "naming strategy" subclass (I could use some hints on how to get code samples to cleanly display in here):
Here is the "assembler" subclass:
I should define a reusable "AnnotationOrDefaultMBeanExporter", and then subclass that, but I'm presently using this from a custom MBeanExporter subclass, with these relevant pieces:
Instance variables:
Constructor body:
And then:
I could use some ideas that might possibly simplify this.