Rework EndpointMBeanExporter to prevent name clashes and to provide more flexibility in naming of endpoint MBeans

pull/184/head
Christian Dupuis 11 years ago
parent b81930fcce
commit 65d6757a10

@ -16,15 +16,19 @@
package org.springframework.boot.actuate.autoconfigure;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} to enable JMX export for
@ -33,13 +37,41 @@ import org.springframework.jmx.export.MBeanExporter;
* @author Christian Dupuis
*/
@Configuration
@ConditionalOnBean({ MBeanExporter.class })
@AutoConfigureAfter({ EndpointAutoConfiguration.class })
@ConditionalOnExpression("${endpoints.jmx.enabled:true}")
class EndpointMBeanExportAutoConfiguration {
private RelaxedPropertyResolver environment;
@Autowired
public void setEnvironment(Environment environment) {
this.environment = new RelaxedPropertyResolver(environment);
}
@Bean
public EndpointMBeanExporter endpointMBeanExporter() {
return new EndpointMBeanExporter();
EndpointMBeanExporter mbeanExporter = new EndpointMBeanExporter();
String domain = this.environment.getProperty("endpoints.jmx.domain");
if (StringUtils.hasText(domain)) {
mbeanExporter.setDomain(domain);
}
Boolean ensureUnique = this.environment.getProperty("endpoints.jmx.unique_names",
Boolean.class, Boolean.FALSE);
mbeanExporter.setEnsureUniqueRuntimeObjectNames(ensureUnique);
mbeanExporter.setObjectNameStaticProperties(getObjectNameStaticProperties());
return mbeanExporter;
}
private Properties getObjectNameStaticProperties() {
String staticNames = this.environment.getProperty("endpoints.jmx.static_names");
if (StringUtils.hasText(staticNames)) {
return StringUtils.splitArrayElementsIntoProperties(
StringUtils.commaDelimitedListToStringArray(staticNames), "=");
}
return new Properties();
}
}

@ -19,15 +19,9 @@ package org.springframework.boot.actuate.endpoint.jmx;
import java.util.List;
import java.util.Map;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.export.naming.SelfNaming;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -39,24 +33,16 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Christian Dupuis
*/
@ManagedResource
public class EndpointMBean implements SelfNaming {
private AnnotationJmxAttributeSource annotationSource = new AnnotationJmxAttributeSource();
private MetadataNamingStrategy metadataNamingStrategy = new MetadataNamingStrategy(
this.annotationSource);
public class EndpointMBean {
private Endpoint<?> endpoint;
private String beanName;
private ObjectMapper mapper = new ObjectMapper();
public EndpointMBean(String beanName, Endpoint<?> endpoint) {
Assert.notNull(beanName, "BeanName must not be null");
Assert.notNull(endpoint, "Endpoint must not be null");
this.endpoint = endpoint;
this.beanName = beanName;
}
@ManagedAttribute(description = "Returns the class of the underlying endpoint")
@ -69,11 +55,6 @@ public class EndpointMBean implements SelfNaming {
return this.endpoint.isSensitive();
}
@Override
public ObjectName getObjectName() throws MalformedObjectNameException {
return this.metadataNamingStrategy.getObjectName(this, this.beanName);
}
public Endpoint<?> getEndpoint() {
return this.endpoint;
}

@ -18,24 +18,31 @@ package org.springframework.boot.actuate.endpoint.jmx;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.SmartLifecycle;
import org.springframework.jmx.export.MBeanExportException;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.util.Assert;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.export.naming.SelfNaming;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ObjectUtils;
/**
* {@link ApplicationListener} that registers all known {@link Endpoint}s with an
@ -44,10 +51,21 @@ import org.springframework.util.Assert;
*
* @author Christian Dupuis
*/
public class EndpointMBeanExporter implements SmartLifecycle, ApplicationContextAware {
public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecycle,
BeanFactoryAware {
public static final String DEFAULT_DOMAIN = "org.springframework.boot";
private static Log logger = LogFactory.getLog(EndpointMBeanExporter.class);
private final AnnotationJmxAttributeSource attributeSource = new AnnotationJmxAttributeSource();
private final MetadataMBeanInfoAssembler assembler = new MetadataMBeanInfoAssembler(
this.attributeSource);
private final MetadataNamingStrategy defaultNamingStrategy = new MetadataNamingStrategy(
this.attributeSource);
private Set<Endpoint<?>> registeredEndpoints = new HashSet<Endpoint<?>>();
private volatile boolean autoStartup = true;
@ -58,45 +76,65 @@ public class EndpointMBeanExporter implements SmartLifecycle, ApplicationContext
private final ReentrantLock lifecycleLock = new ReentrantLock();
private ApplicationContext applicationContext;
private ListableBeanFactory beanFactory;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
private String domain = DEFAULT_DOMAIN;
private boolean ensureUniqueRuntimeObjectNames = false;
private Properties objectNameStaticProperties = new Properties();
public EndpointMBeanExporter() {
super();
setAutodetect(false);
setNamingStrategy(this.defaultNamingStrategy);
setAssembler(this.assembler);
}
protected void doStart() {
try {
MBeanExporter mbeanExporter = this.applicationContext
.getBean(MBeanExporter.class);
locateAndRegisterEndpoints(mbeanExporter);
@Override
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
if (beanFactory instanceof ListableBeanFactory) {
this.beanFactory = (ListableBeanFactory) beanFactory;
}
catch (NoSuchBeanDefinitionException nsbde) {
if (logger.isDebugEnabled()) {
logger.debug("Could not obtain MBeanExporter. No Endpoint JMX export will be attemted.");
}
else {
logger.info("EndpointMBeanExporter not running in a ListableBeanFactory: "
+ "autodetection of Endpoints not available.");
}
}
public void setDomain(String domain) {
this.domain = domain;
}
@Override
public void setEnsureUniqueRuntimeObjectNames(boolean ensureUniqueRuntimeObjectNames) {
super.setEnsureUniqueRuntimeObjectNames(ensureUniqueRuntimeObjectNames);
this.ensureUniqueRuntimeObjectNames = ensureUniqueRuntimeObjectNames;
}
public void setObjectNameStaticProperties(Properties objectNameStaticProperties) {
this.objectNameStaticProperties = objectNameStaticProperties;
}
protected void doStart() {
locateAndRegisterEndpoints();
}
@SuppressWarnings({ "rawtypes" })
protected void locateAndRegisterEndpoints(MBeanExporter mbeanExporter) {
Assert.notNull(mbeanExporter, "MBeanExporter must not be null");
Map<String, Endpoint> endpoints = this.applicationContext
.getBeansOfType(Endpoint.class);
protected void locateAndRegisterEndpoints() {
Map<String, Endpoint> endpoints = this.beanFactory.getBeansOfType(Endpoint.class);
for (Map.Entry<String, Endpoint> endpointEntry : endpoints.entrySet()) {
if (!this.registeredEndpoints.contains(endpointEntry.getValue())) {
registerEndpoint(endpointEntry.getKey(), endpointEntry.getValue(),
mbeanExporter);
registerEndpoint(endpointEntry.getKey(), endpointEntry.getValue());
this.registeredEndpoints.add(endpointEntry.getValue());
}
}
}
protected void registerEndpoint(String beanName, Endpoint<?> endpoint,
MBeanExporter mbeanExporter) {
protected void registerEndpoint(String beanName, Endpoint<?> endpoint) {
try {
mbeanExporter.registerManagedResource(getEndpointMBean(beanName, endpoint));
registerBeanNameOrInstance(getEndpointMBean(beanName, endpoint), beanName);
}
catch (MBeanExportException ex) {
logger.error("Could not register MBean for endpoint [" + beanName + "]", ex);
@ -110,6 +148,42 @@ public class EndpointMBeanExporter implements SmartLifecycle, ApplicationContext
return new DataEndpointMBean(beanName, endpoint);
}
@Override
protected ObjectName getObjectName(Object bean, String beanKey)
throws MalformedObjectNameException {
if (bean instanceof SelfNaming) {
return ((SelfNaming) bean).getObjectName();
}
if (bean instanceof EndpointMBean) {
StringBuilder builder = new StringBuilder();
builder.append(this.domain);
builder.append(":type=Endpoint");
builder.append(",name=" + beanKey);
if (this.ensureUniqueRuntimeObjectNames) {
builder.append(",identity="
+ ObjectUtils.getIdentityHexString(((EndpointMBean) bean)
.getEndpoint()));
}
builder.append(getStaticNames());
return ObjectNameManager.getInstance(builder.toString());
}
return this.defaultNamingStrategy.getObjectName(bean, beanKey);
}
private String getStaticNames() {
if (this.objectNameStaticProperties.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
for (Object key : this.objectNameStaticProperties.keySet()) {
builder.append("," + key + "=" + this.objectNameStaticProperties.get(key));
}
return builder.toString();
}
// SmartLifeCycle implementation
public final int getPhase() {

@ -16,13 +16,24 @@
package org.springframework.boot.actuate.autoconfigure;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ObjectUtils;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@ -53,7 +64,10 @@ public class EndpointMBeanExportAutoConfigurationTests {
@Test(expected = NoSuchBeanDefinitionException.class)
public void testEndpointMBeanExporterIsNotInstalled() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("endpoints.jmx.enabled", "false");
this.context = new AnnotationConfigApplicationContext();
this.context.setEnvironment(environment);
this.context.register(EndpointAutoConfiguration.class,
EndpointMBeanExportAutoConfiguration.class);
this.context.refresh();
@ -61,6 +75,35 @@ public class EndpointMBeanExportAutoConfigurationTests {
fail();
}
@Test
public void testEndpointMBeanExporterWithProperties() throws IntrospectionException,
InstanceNotFoundException, MalformedObjectNameException, ReflectionException {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("endpoints.jmx.domain", "test-domain");
environment.setProperty("endpoints.jmx.unique_names", "true");
environment.setProperty("endpoints.jmx.static_names", "key1=value1, key2=value2");
this.context = new AnnotationConfigApplicationContext();
this.context.setEnvironment(environment);
this.context.register(EndpointAutoConfiguration.class,
EndpointMBeanExportAutoConfiguration.class);
this.context.refresh();
this.context.getBean(EndpointMBeanExporter.class);
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
assertNotNull(mbeanExporter.getServer().getMBeanInfo(
ObjectNameManager.getInstance(getObjectName("test-domain",
"healthEndpoint", this.context).toString()
+ ",key1=value1,key2=value2")));
}
private ObjectName getObjectName(String domain, String beanKey,
ApplicationContext applicationContext) throws MalformedObjectNameException {
return ObjectNameManager.getInstance(String.format(
"%s:type=Endpoint,name=%s,identity=%s", domain, beanKey,
ObjectUtils.getIdentityHexString(applicationContext.getBean(beanKey))));
}
@Configuration
@EnableMBeanExport
public static class TestConfiguration {

@ -17,6 +17,9 @@
package org.springframework.boot.actuate.endpoint.jmx;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.management.MBeanInfo;
import javax.management.MalformedObjectNameException;
@ -27,10 +30,11 @@ import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ObjectUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -58,20 +62,15 @@ public class EndpointMBeanExporterTests {
new RootBeanDefinition(EndpointMBeanExporter.class));
this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
TestEndpoint.class));
this.context.registerBeanDefinition(
"mbeanExporter",
new RootBeanDefinition(MBeanExporter.class, null,
new MutablePropertyValues(Collections.singletonMap(
"ensureUniqueRuntimeObjectNames", "false"))));
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(MBeanExporter.class);
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
MBeanInfo mbeanInfo = mbeanExporter.getServer().getMBeanInfo(
getObjectName("endpoint1", this.context));
assertNotNull(mbeanInfo);
assertEquals(5, mbeanInfo.getOperations().length);
assertEquals(5, mbeanInfo.getAttributes().length);
assertEquals(3, mbeanInfo.getOperations().length);
assertEquals(3, mbeanInfo.getAttributes().length);
}
@Test
@ -83,14 +82,9 @@ public class EndpointMBeanExporterTests {
TestEndpoint.class));
this.context.registerBeanDefinition("endpoint2", new RootBeanDefinition(
TestEndpoint.class));
this.context.registerBeanDefinition(
"mbeanExporter",
new RootBeanDefinition(MBeanExporter.class, null,
new MutablePropertyValues(Collections.singletonMap(
"ensureUniqueRuntimeObjectNames", "false"))));
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(MBeanExporter.class);
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
assertNotNull(mbeanExporter.getServer().getMBeanInfo(
getObjectName("endpoint1", this.context)));
@ -98,6 +92,69 @@ public class EndpointMBeanExporterTests {
getObjectName("endpoint2", this.context)));
}
@Test
public void testRegistrationWithDifferentDomain() throws Exception {
this.context = new GenericApplicationContext();
this.context.registerBeanDefinition(
"endpointMbeanExporter",
new RootBeanDefinition(EndpointMBeanExporter.class, null,
new MutablePropertyValues(Collections.singletonMap("domain",
"test-domain"))));
this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
TestEndpoint.class));
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
assertNotNull(mbeanExporter.getServer().getMBeanInfo(
getObjectName("test-domain", "endpoint1", false, this.context)));
}
@Test
public void testRegistrationWithDifferentDomainAndIdentity() throws Exception {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("domain", "test-domain");
properties.put("ensureUniqueRuntimeObjectNames", true);
this.context = new GenericApplicationContext();
this.context.registerBeanDefinition("endpointMbeanExporter",
new RootBeanDefinition(EndpointMBeanExporter.class, null,
new MutablePropertyValues(properties)));
this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
TestEndpoint.class));
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
assertNotNull(mbeanExporter.getServer().getMBeanInfo(
getObjectName("test-domain", "endpoint1", true, this.context)));
}
@Test
public void testRegistrationWithDifferentDomainAndIdentityAndStaticNames()
throws Exception {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("domain", "test-domain");
properties.put("ensureUniqueRuntimeObjectNames", true);
Properties staticNames = new Properties();
staticNames.put("key1", "value1");
staticNames.put("key2", "value2");
properties.put("objectNameStaticProperties", staticNames);
this.context = new GenericApplicationContext();
this.context.registerBeanDefinition("endpointMbeanExporter",
new RootBeanDefinition(EndpointMBeanExporter.class, null,
new MutablePropertyValues(properties)));
this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
TestEndpoint.class));
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
assertNotNull(mbeanExporter.getServer().getMBeanInfo(
ObjectNameManager.getInstance(getObjectName("test-domain", "endpoint1",
true, this.context).toString()
+ ",key1=value1,key2=value2")));
}
@Test
public void testRegistrationWithParentContext() throws Exception {
this.context = new GenericApplicationContext();
@ -105,19 +162,13 @@ public class EndpointMBeanExporterTests {
new RootBeanDefinition(EndpointMBeanExporter.class));
this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
TestEndpoint.class));
this.context.registerBeanDefinition(
"mbeanExporter",
new RootBeanDefinition(MBeanExporter.class, null,
new MutablePropertyValues(Collections.singletonMap(
"ensureUniqueRuntimeObjectNames", "false"))));
GenericApplicationContext parent = new GenericApplicationContext();
this.context.setParent(parent);
parent.refresh();
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(MBeanExporter.class);
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
assertNotNull(mbeanExporter.getServer().getMBeanInfo(
getObjectName("endpoint1", this.context)));
@ -125,10 +176,25 @@ public class EndpointMBeanExporterTests {
parent.close();
}
private ObjectName getObjectName(String beanKey, ApplicationContext applicationContext)
private ObjectName getObjectName(String beanKey, GenericApplicationContext context)
throws MalformedObjectNameException {
return new DataEndpointMBean(beanKey,
(Endpoint<?>) applicationContext.getBean(beanKey)).getObjectName();
return getObjectName("org.springframework.boot", beanKey, false, context);
}
private ObjectName getObjectName(String domain, String beanKey,
boolean includeIdentity, ApplicationContext applicationContext)
throws MalformedObjectNameException {
if (includeIdentity) {
return ObjectNameManager
.getInstance(String.format("%s:type=Endpoint,name=%s,identity=%s",
domain, beanKey, ObjectUtils
.getIdentityHexString(applicationContext
.getBean(beanKey))));
}
else {
return ObjectNameManager.getInstance(String.format(
"%s:type=Endpoint,name=%s", domain, beanKey));
}
}
public static class TestEndpoint extends AbstractEndpoint<String> {

Loading…
Cancel
Save