Ok, here's another stab at this. I didn't like the fact that I ended up having to copy method bodies from either of the two alternative implementations. I concluded that I could reduce this copying if I made both the "strategy" and "assembler" custom classes be subclasses of the "metadata" versions. The basic idea is that after checking for the existence of the "@ManagedResource" annotation on the class, I can either call the superclass method, or inline the "non-metadata" version, which in all cases was less code than in the "metadata" version.
In the case of the "strategy" class, since the relevant method in the "non-metadata" version was public, I could still create a local instance of the "strategy" class and just call the relevant method, instead of inlining the method body.
For the "assembler" class, I found that I didn't have to copy the bodies of any methods in the "metadata" version, but I found that I had to override additional methods, and I did have to copy in the method body of one method in the "super super class" because I can't access that directly. The resulting class is a little shorter than my first try, so I suppose it's better, even with those slightly gnarly bits.
To fully clean this up, this would have to be integrated into Spring to allow for the best refactoring. The functionality that this provides seems like a good thing to have, so I'll file a ticket to at least ask for this, and I'll post these classes in the ticket.
In this version, I fully refactored the implementation into a "AnnotationOrDefaultMBeanExporter" class, and then my actual application-specific class extends that (which I don't need to show here).
Here's the main exporter class:
package <package>;
import java.util.logging.Logger;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
public class AnnotationOrDefaultMBeanExporter extends MBeanExporter {
protected static Logger logger = Logger.getLogger(AnnotationOrDefaultMBeanExporter.class.getName());
private AnnotationJmxAttributeSource annotationSource = new AnnotationJmxAttributeSource();
protected MetadataOrKeyNamingStrategy metadataOrKeyNamingStrategy = new MetadataOrKeyNamingStrategy(annotationSource);
protected MetadataOrSimpleReflectiveMBeanInfoAssembler metadataOrSimpleReflectiveMBeanInfoAssembler = new MetadataOrSimpleReflectiveMBeanInfoAssembler(annotationSource);
public AnnotationOrDefaultMBeanExporter() {
setNamingStrategy(metadataOrKeyNamingStrategy);
setAssembler(metadataOrSimpleReflectiveMBeanInfoAssembler);
setAutodetectMode(AUTODETECT_ALL);
}
/**
* Specify the default domain to be used for generating ObjectNames
* when no source-level metadata has been specified.
* <p>The default is to use the domain specified in the bean name
* (if the bean name follows the JMX ObjectName syntax); else,
* the package name of the managed bean class.
* @see MetadataNamingStrategy#setDefaultDomain
*/
public void setDefaultDomain(String defaultDomain) {
this.metadataOrKeyNamingStrategy.setDefaultDomain(defaultDomain);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
this.annotationSource.setBeanFactory(beanFactory);
}
}
Following this is the "strategy" class. The somewhat gnarly bit here is wrapping a checked exception with throwing a RuntimeException().
package <package>;
import java.io.IOException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.springframework.aop.support.AopUtils;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.metadata.JmxAttributeSource;
import org.springframework.jmx.export.naming.KeyNamingStrategy;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
public class MetadataOrKeyNamingStrategy extends MetadataNamingStrategy {
private KeyNamingStrategy keyNamingStrategy = new KeyNamingStrategy();
public MetadataOrKeyNamingStrategy() { }
public MetadataOrKeyNamingStrategy(JmxAttributeSource attributeSource) {
super(attributeSource);
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
try {
keyNamingStrategy.afterPropertiesSet();
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Override
public ObjectName getObjectName(Object managedBean, String beanKey)
throws MalformedObjectNameException {
Class<?> managedClass = AopUtils.getTargetClass(managedBean);
if (managedClass.getAnnotation(ManagedResource.class) != null)
return super.getObjectName(managedBean, beanKey);
return keyNamingStrategy.getObjectName(managedBean, beanKey);
}
}
And here's the "assembler" class:
package <package>;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import javax.management.Descriptor;
import org.springframework.aop.support.AopUtils;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
import org.springframework.jmx.export.metadata.JmxAttributeSource;
public class MetadataOrSimpleReflectiveMBeanInfoAssembler extends MetadataMBeanInfoAssembler {
public MetadataOrSimpleReflectiveMBeanInfoAssembler() { }
public MetadataOrSimpleReflectiveMBeanInfoAssembler(JmxAttributeSource attributeSource) {
super(attributeSource);
}
@Override
protected boolean includeReadAttribute(Method method, String beanKey) {
if (method.getDeclaringClass().getAnnotation(ManagedResource.class) != null)
return super.includeReadAttribute(method, beanKey);
return true;
}
@Override
protected boolean includeWriteAttribute(Method method, String beanKey) {
if (method.getDeclaringClass().getAnnotation(ManagedResource.class) != null)
return super.includeWriteAttribute(method, beanKey);
return true;
}
@Override
protected boolean includeOperation(Method method, String beanKey) {
if (method.getDeclaringClass().getAnnotation(ManagedResource.class) != null)
return super.includeOperation(method, beanKey);
return true;
}
@Override
protected String getDescription(Object managedBean, String beanKey) {
if (managedBean.getClass().getAnnotation(ManagedResource.class) != null)
return super.getDescription(managedBean, beanKey);
return superSuperGetDescription(managedBean, beanKey);
}
/** Copied from AbstractMBeanInfoAssembler.getDescription(), the super.superclass of this class, which can't be easily called. */
private String superSuperGetDescription(Object managedBean, String beanKey) {
String targetClassName = getTargetClass(managedBean).getName();
if (AopUtils.isAopProxy(managedBean)) {
return "Proxy for " + targetClassName;
}
return targetClassName;
}
@Override
protected String getAttributeDescription(PropertyDescriptor propertyDescriptor, String beanKey) {
Method readMethod = propertyDescriptor.getReadMethod();
if (readMethod != null && readMethod.getDeclaringClass().getAnnotation(ManagedResource.class) != null)
return super.getAttributeDescription(propertyDescriptor, beanKey);
return propertyDescriptor.getDisplayName();
}
@Override
protected String getOperationDescription(Method method, String beanKey) {
if (method.getDeclaringClass().getAnnotation(ManagedResource.class) != null)
return super.getOperationDescription(method, beanKey);
return method.getName();
}
@Override
protected void populateAttributeDescriptor(Descriptor desc, Method getter, Method setter, String beanKey) {
Method methodToUse = getter;
if (methodToUse == null)
methodToUse = setter;
if (methodToUse != null) {
if (methodToUse.getDeclaringClass().getAnnotation(ManagedResource.class) != null)
super.populateAttributeDescriptor(desc, getter, setter, beanKey);
else
applyDefaultCurrencyTimeLimit(desc);
}
else
applyDefaultCurrencyTimeLimit(desc);
}
@Override
protected void populateMBeanDescriptor(Descriptor descriptor, Object managedBean, String beanKey) {
if (managedBean.getClass().getAnnotation(ManagedResource.class) != null)
super.populateMBeanDescriptor(descriptor, managedBean, beanKey);
else
applyDefaultCurrencyTimeLimit(descriptor);
}
@Override
protected void populateOperationDescriptor(Descriptor desc, Method method, String beanKey) {
if (method != null) {
if (method.getClass().getAnnotation(ManagedResource.class) != null)
super.populateOperationDescriptor(desc, method, beanKey);
else
applyDefaultCurrencyTimeLimit(desc);
}
else
applyDefaultCurrencyTimeLimit(desc);
}
}