From 151f7ef3256930a5a285b1ab4dbbe6ba292d1c0e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 16 Aug 2017 13:37:24 +0200 Subject: [PATCH] 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 --- .../DefaultEndpointObjectNameFactory.java | 49 ++++++- ...dpointInfrastructureAutoConfiguration.java | 11 +- .../JmxEndpointExporterProperties.java | 77 +++++++++++ ...itional-spring-configuration-metadata.json | 6 - ...DefaultEndpointObjectNameFactoryTests.java | 122 ++++++++++++++++++ .../appendix-application-properties.adoc | 21 ++- .../asciidoc/production-ready-features.adoc | 12 +- 7 files changed, 267 insertions(+), 31 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporterProperties.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactoryTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java index 6bd648e01ec..3cfc0835b8e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java @@ -16,12 +16,16 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; +import java.util.Map; + +import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import org.springframework.boot.endpoint.jmx.EndpointMBean; import org.springframework.boot.endpoint.jmx.EndpointObjectNameFactory; import org.springframework.jmx.support.ObjectNameManager; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -32,15 +36,50 @@ import org.springframework.util.StringUtils; */ 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 public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException { - StringBuilder builder = new StringBuilder(); - builder.append(this.domain); - builder.append(":type=Endpoint"); - builder.append(",name=" + StringUtils.capitalize(mBean.getEndpointId())); + String baseObjectName = this.properties.getDomain() + + ":type=Endpoint" + + ",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()); } + 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 name : this.properties.getStaticNames().entrySet()) { + builder.append(",").append(name.getKey()).append("=").append(name.getValue()); + } + return builder.toString(); + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java index 9bd3a32bf49..a228d133d3f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 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.EndpointType; 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.Configuration; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -56,6 +58,7 @@ import org.springframework.util.StringUtils; */ @Configuration @AutoConfigureAfter(JmxAutoConfiguration.class) +@EnableConfigurationProperties(JmxEndpointExporterProperties.class) public class EndpointInfrastructureAutoConfiguration { private final ApplicationContext applicationContext; @@ -94,18 +97,20 @@ public class EndpointInfrastructureAutoConfiguration { @ConditionalOnSingleCandidate(MBeanServer.class) @Bean - public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, - JmxAnnotationEndpointDiscoverer endpointDiscoverer, + public JmxEndpointExporter jmxMBeanExporter(JmxEndpointExporterProperties properties, + MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer, ObjectProvider objectMapper) { EndpointProvider endpointProvider = new EndpointProvider<>( this.applicationContext.getEnvironment(), endpointDiscoverer, EndpointType.JMX); EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar( - mBeanServer, new DefaultEndpointObjectNameFactory()); + mBeanServer, new DefaultEndpointObjectNameFactory(properties, + mBeanServer, ObjectUtils.getIdentityHexString(this.applicationContext))); return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar, objectMapper.getIfAvailable(ObjectMapper::new)); } + @Configuration @ConditionalOnWebApplication static class WebInfrastructureConfiguration { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporterProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporterProperties.java new file mode 100644 index 00000000000..c079a7e7dfe --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporterProperties.java @@ -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; + } + +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 752a0e348e1..96d1fd3770b 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -52,12 +52,6 @@ "type": "java.lang.String", "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", "type": "java.lang.String", diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactoryTests.java new file mode 100644 index 00000000000..728c824408c --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactoryTests.java @@ -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; + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 563c71fe9c8..0a6d0dd07e4 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -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.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.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. @@ -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.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]) management.trace.include=request-headers,response-headers,cookies,errors # Items to be included in the trace. diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index a459d12eb93..c12b1d6c915 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -734,10 +734,10 @@ under the `org.springframework.boot` domain. [[production-ready-custom-mbean-names]] === Customizing MBean names 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 -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. 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] ---- - endpoints.jmx.domain=myapp - endpoints.jmx.unique-names=true + management.jmx.domain=com.example.myapp + management.jmx.unique-names=true ---- [[production-ready-disable-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`: [source,properties,indent=0] ---- - endpoints.jmx.enabled=false + endpoints.all.jmx.enabled=false ----