Reinstate JMX customizations of Endpoints ObjectName

This commit restores the configuration properties used to configure how
the ObjectName of an endpoint is generated. For consistency, those
properties have been renamed to `management.jmx`

Closes gh-10005
pull/10012/merge
Stephane Nicoll 7 years ago
parent 35cf0c56a8
commit 151f7ef325

@ -16,12 +16,16 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
import javax.management.ObjectName; import javax.management.ObjectName;
import org.springframework.boot.endpoint.jmx.EndpointMBean; import org.springframework.boot.endpoint.jmx.EndpointMBean;
import org.springframework.boot.endpoint.jmx.EndpointObjectNameFactory; import org.springframework.boot.endpoint.jmx.EndpointObjectNameFactory;
import org.springframework.jmx.support.ObjectNameManager; import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -32,15 +36,50 @@ import org.springframework.util.StringUtils;
*/ */
class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
private String domain = "org.springframework.boot"; private final JmxEndpointExporterProperties properties;
private final MBeanServer mBeanServer;
private final String contextId;
DefaultEndpointObjectNameFactory(JmxEndpointExporterProperties properties,
MBeanServer mBeanServer, String contextId) {
this.properties = properties;
this.mBeanServer = mBeanServer;
this.contextId = contextId;
}
@Override @Override
public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException { public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException {
StringBuilder builder = new StringBuilder(); String baseObjectName = this.properties.getDomain() +
builder.append(this.domain); ":type=Endpoint" +
builder.append(":type=Endpoint"); ",name=" + StringUtils.capitalize(mBean.getEndpointId());
builder.append(",name=" + StringUtils.capitalize(mBean.getEndpointId())); StringBuilder builder = new StringBuilder(baseObjectName);
if (this.mBeanServer != null && hasMBean(baseObjectName)) {
builder.append(",context=").append(this.contextId);
}
if (this.properties.isUniqueNames()) {
builder.append(",identity=").append(ObjectUtils.getIdentityHexString(mBean));
}
builder.append(getStaticNames());
return ObjectNameManager.getInstance(builder.toString()); return ObjectNameManager.getInstance(builder.toString());
} }
private boolean hasMBean(String baseObjectName) throws MalformedObjectNameException {
ObjectName query = new ObjectName(baseObjectName + ",*");
return this.mBeanServer.queryNames(query, null).size() > 0;
}
private String getStaticNames() {
if (this.properties.getStaticNames().isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
for (Map.Entry<Object, Object> name : this.properties.getStaticNames().entrySet()) {
builder.append(",").append(name.getKey()).append("=").append(name.getValue());
}
return builder.toString();
}
} }

@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper; import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
import org.springframework.boot.endpoint.EndpointType; import org.springframework.boot.endpoint.EndpointType;
import org.springframework.boot.endpoint.OperationParameterMapper; import org.springframework.boot.endpoint.OperationParameterMapper;
@ -44,6 +45,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -56,6 +58,7 @@ import org.springframework.util.StringUtils;
*/ */
@Configuration @Configuration
@AutoConfigureAfter(JmxAutoConfiguration.class) @AutoConfigureAfter(JmxAutoConfiguration.class)
@EnableConfigurationProperties(JmxEndpointExporterProperties.class)
public class EndpointInfrastructureAutoConfiguration { public class EndpointInfrastructureAutoConfiguration {
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
@ -94,18 +97,20 @@ public class EndpointInfrastructureAutoConfiguration {
@ConditionalOnSingleCandidate(MBeanServer.class) @ConditionalOnSingleCandidate(MBeanServer.class)
@Bean @Bean
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, public JmxEndpointExporter jmxMBeanExporter(JmxEndpointExporterProperties properties,
JmxAnnotationEndpointDiscoverer endpointDiscoverer, MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer,
ObjectProvider<ObjectMapper> objectMapper) { ObjectProvider<ObjectMapper> objectMapper) {
EndpointProvider<JmxEndpointOperation> endpointProvider = new EndpointProvider<>( EndpointProvider<JmxEndpointOperation> endpointProvider = new EndpointProvider<>(
this.applicationContext.getEnvironment(), endpointDiscoverer, this.applicationContext.getEnvironment(), endpointDiscoverer,
EndpointType.JMX); EndpointType.JMX);
EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar( EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar(
mBeanServer, new DefaultEndpointObjectNameFactory()); mBeanServer, new DefaultEndpointObjectNameFactory(properties,
mBeanServer, ObjectUtils.getIdentityHexString(this.applicationContext)));
return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar, return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar,
objectMapper.getIfAvailable(ObjectMapper::new)); objectMapper.getIfAvailable(ObjectMapper::new));
} }
@Configuration
@ConditionalOnWebApplication @ConditionalOnWebApplication
static class WebInfrastructureConfiguration { static class WebInfrastructureConfiguration {

@ -0,0 +1,77 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
import java.util.Properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* Configuration properties for JMX export of endpoints.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@ConfigurationProperties("management.jmx")
public class JmxEndpointExporterProperties {
/**
* Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set.
*/
private String domain = "org.springframework.boot";
/**
* Ensure that ObjectNames are modified in case of conflict.
*/
private boolean uniqueNames = false;
/**
* Additional static properties to append to all ObjectNames of MBeans representing
* Endpoints.
*/
private final Properties staticNames = new Properties();
public JmxEndpointExporterProperties(Environment environment) {
String defaultDomain = environment.getProperty("spring.jmx.default-domain");
if (StringUtils.hasText(defaultDomain)) {
this.domain = defaultDomain;
}
}
public String getDomain() {
return this.domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public boolean isUniqueNames() {
return this.uniqueNames;
}
public void setUniqueNames(boolean uniqueNames) {
this.uniqueNames = uniqueNames;
}
public Properties getStaticNames() {
return this.staticNames;
}
}

@ -52,12 +52,6 @@
"type": "java.lang.String", "type": "java.lang.String",
"description": "Endpoint URL path." "description": "Endpoint URL path."
}, },
{
"name": "endpoints.jmx.enabled",
"type": "java.lang.Boolean",
"description": "Enable JMX export of all endpoints.",
"defaultValue": true
},
{ {
"name": "endpoints.mappings.path", "name": "endpoints.mappings.path",
"type": "java.lang.String", "type": "java.lang.String",

@ -0,0 +1,122 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
import java.util.Collections;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.junit.Test;
import org.springframework.boot.endpoint.jmx.EndpointMBean;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DefaultEndpointObjectNameFactory}.
*
* @author Stephane Nicoll
*/
public class DefaultEndpointObjectNameFactoryTests {
private final MockEnvironment environment = new MockEnvironment();
private final JmxEndpointExporterProperties properties = new JmxEndpointExporterProperties(this.environment);
private final MBeanServer mBeanServer = mock(MBeanServer.class);
private String contextId;
@Test
public void generateObjectName() {
ObjectName objectName = generateObjectName(endpoint("Test"));
assertThat(objectName.toString()).isEqualTo(
"org.springframework.boot:type=Endpoint,name=Test");
}
@Test
public void generateObjectNameWithCapitalizedId() {
ObjectName objectName = generateObjectName(endpoint("test"));
assertThat(objectName.toString()).isEqualTo(
"org.springframework.boot:type=Endpoint,name=Test");
}
@Test
public void generateObjectNameWithCustomDomain() {
this.properties.setDomain("com.example.acme");
ObjectName objectName = generateObjectName(endpoint("test"));
assertThat(objectName.toString()).isEqualTo(
"com.example.acme:type=Endpoint,name=Test");
}
@Test
public void generateObjectNameWithUniqueNames() {
this.properties.setUniqueNames(true);
EndpointMBean endpoint = endpoint("test");
String id = ObjectUtils.getIdentityHexString(endpoint);
ObjectName objectName = generateObjectName(endpoint);
assertThat(objectName.toString()).isEqualTo(
"org.springframework.boot:type=Endpoint,name=Test,identity=" + id);
}
@Test
public void generateObjectNameWithStaticNames() {
this.properties.getStaticNames().setProperty("counter", "42");
this.properties.getStaticNames().setProperty("foo", "bar");
ObjectName objectName = generateObjectName(endpoint("test"));
assertThat(objectName.getKeyProperty("counter")).isEqualTo("42");
assertThat(objectName.getKeyProperty("foo")).isEqualTo("bar");
assertThat(objectName.toString()).startsWith(
"org.springframework.boot:type=Endpoint,name=Test,");
}
@Test
public void generateObjectNameWithDuplicate() throws MalformedObjectNameException {
this.contextId = "testContext";
given(this.mBeanServer.queryNames(new ObjectName(
"org.springframework.boot:type=Endpoint,name=Test,*"), null))
.willReturn(Collections.singleton(
new ObjectName("org.springframework.boot:type=Endpoint,name=Test")));
ObjectName objectName = generateObjectName(endpoint("test"));
assertThat(objectName.toString()).isEqualTo(
"org.springframework.boot:type=Endpoint,name=Test,context=testContext");
}
private ObjectName generateObjectName(EndpointMBean endpointMBean) {
try {
return new DefaultEndpointObjectNameFactory(this.properties, this.mBeanServer,
this.contextId).generate(endpointMBean);
}
catch (MalformedObjectNameException ex) {
throw new AssertionError("Invalid object name", ex);
}
}
private EndpointMBean endpoint(String id) {
EndpointMBean endpointMBean = mock(EndpointMBean.class);
given(endpointMBean.getEndpointId()).willReturn(id);
return endpointMBean;
}
}

@ -1119,17 +1119,6 @@ content into your application; rather pick only the properties that you need.
endpoints.cors.exposed-headers= # Comma-separated list of headers to include in a response. endpoints.cors.exposed-headers= # Comma-separated list of headers to include in a response.
endpoints.cors.max-age=1800 # How long, in seconds, the response from a pre-flight request can be cached by clients. endpoints.cors.max-age=1800 # How long, in seconds, the response from a pre-flight request can be cached by clients.
# JMX ENDPOINT ({sc-spring-boot-actuator}/autoconfigure/EndpointMBeanExportProperties.{sc-ext}[EndpointMBeanExportProperties])
endpoints.jmx.domain= # JMX domain name. Initialized with the value of 'spring.jmx.default-domain' if set.
endpoints.jmx.enabled=true # Enable JMX export of all endpoints.
endpoints.jmx.static-names= # Additional static properties to append to all ObjectNames of MBeans representing Endpoints.
endpoints.jmx.unique-names=false # Ensure that ObjectNames are modified in case of conflict.
# JOLOKIA ({sc-spring-boot-actuator}/autoconfigure/jolokia/JolokiaProperties.{sc-ext}[JolokiaProperties])
management.jolokia.config.*= # Jolokia settings. See the Jolokia manual for details.
management.jolokia.enabled=true # Enable Jolokia.
management.jolokia.path=/jolokia # Path at which Jolokia will be available.
# MANAGEMENT HTTP SERVER ({sc-spring-boot-actuator}/autoconfigure/ManagementServerProperties.{sc-ext}[ManagementServerProperties]) # MANAGEMENT HTTP SERVER ({sc-spring-boot-actuator}/autoconfigure/ManagementServerProperties.{sc-ext}[ManagementServerProperties])
management.add-application-context-header=false # Add the "X-Application-Context" HTTP header in each response. management.add-application-context-header=false # Add the "X-Application-Context" HTTP header in each response.
management.address= # Network address that the management endpoints should bind to. management.address= # Network address that the management endpoints should bind to.
@ -1184,6 +1173,16 @@ content into your application; rather pick only the properties that you need.
management.info.git.enabled=true # Enable git info. management.info.git.enabled=true # Enable git info.
management.info.git.mode=simple # Mode to use to expose git information. management.info.git.mode=simple # Mode to use to expose git information.
# JMX ENDPOINT ({sc-spring-boot-actuator}/autoconfigure/endpoint.infrastructure/JmxEndpointExporterProperties.{sc-ext}[JmxEndpointExporterProperties])
management.jmx.domain=org.springframework.boot # Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set.
management.jmx.static-names=false # Additional static properties to append to all ObjectNames of MBeans representing Endpoints.
management.jmx.unique-names=false # Ensure that ObjectNames are modified in case of conflict.
# JOLOKIA ({sc-spring-boot-actuator}/autoconfigure/jolokia/JolokiaProperties.{sc-ext}[JolokiaProperties])
management.jolokia.config.*= # Jolokia settings. See the Jolokia manual for details.
management.jolokia.enabled=true # Enable Jolokia.
management.jolokia.path=/jolokia # Path at which Jolokia will be available.
# TRACING ({sc-spring-boot-actuator}/trace/TraceProperties.{sc-ext}[TraceProperties]) # TRACING ({sc-spring-boot-actuator}/trace/TraceProperties.{sc-ext}[TraceProperties])
management.trace.include=request-headers,response-headers,cookies,errors # Items to be included in the trace. management.trace.include=request-headers,response-headers,cookies,errors # Items to be included in the trace.

@ -734,10 +734,10 @@ under the `org.springframework.boot` domain.
[[production-ready-custom-mbean-names]] [[production-ready-custom-mbean-names]]
=== Customizing MBean names === Customizing MBean names
The name of the MBean is usually generated from the `id` of the endpoint. For example The name of the MBean is usually generated from the `id` of the endpoint. For example
the `health` endpoint is exposed as `org.springframework.boot/Endpoint/healthEndpoint`. the `health` endpoint is exposed as `org.springframework.boot:type=Endpoint,name=Health`.
If your application contains more than one Spring `ApplicationContext` you may find that If your application contains more than one Spring `ApplicationContext` you may find that
names clash. To solve this problem you can set the `endpoints.jmx.unique-names` property names clash. To solve this problem you can set the `management.jmx.unique-names` property
to `true` so that MBean names are always unique. to `true` so that MBean names are always unique.
You can also customize the JMX domain under which endpoints are exposed. Here is an You can also customize the JMX domain under which endpoints are exposed. Here is an
@ -745,20 +745,20 @@ example `application.properties`:
[source,properties,indent=0] [source,properties,indent=0]
---- ----
endpoints.jmx.domain=myapp management.jmx.domain=com.example.myapp
endpoints.jmx.unique-names=true management.jmx.unique-names=true
---- ----
[[production-ready-disable-jmx-endpoints]] [[production-ready-disable-jmx-endpoints]]
=== Disabling JMX endpoints === Disabling JMX endpoints
If you don't want to expose endpoints over JMX you can set the `endpoints.jmx.enabled` If you don't want to expose endpoints over JMX you can set the `endpoints.all.jmx.enabled`
property to `false`: property to `false`:
[source,properties,indent=0] [source,properties,indent=0]
---- ----
endpoints.jmx.enabled=false endpoints.all.jmx.enabled=false
---- ----

Loading…
Cancel
Save