From 1a3f08d7729aef6009e8f29d4ef321ff5a14ddb3 Mon Sep 17 00:00:00 2001 From: artsiom Date: Fri, 3 Aug 2018 00:45:11 +0300 Subject: [PATCH 1/2] Add global support for JMX unique names See gh-13990 --- .../boot/autoconfigure/jmx/JmxAutoConfiguration.java | 6 ++++++ .../META-INF/additional-spring-configuration-metadata.json | 6 ++++++ .../boot/autoconfigure/jmx/JmxAutoConfigurationTests.java | 4 ++++ .../src/main/asciidoc/appendix-application-properties.adoc | 1 + 4 files changed, 17 insertions(+) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java index 7c68d99d5d..99b31a66d7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java @@ -49,6 +49,7 @@ import org.springframework.util.StringUtils; * * @author Christian Dupuis * @author Madhura Bhave + * @author Artsiom Yudovin */ @Configuration @ConditionalOnClass({ MBeanExporter.class }) @@ -93,6 +94,11 @@ public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware if (StringUtils.hasLength(defaultDomain)) { namingStrategy.setDefaultDomain(defaultDomain); } + + boolean uniqueName = this.environment.getProperty("spring.jmx.unique-names", + Boolean.class, false); + namingStrategy.setEnsureUniqueRuntimeObjectNames(uniqueName); + return namingStrategy; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ca4e754d7d..4bf7b6b747 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -290,6 +290,12 @@ "description": "MBeanServer bean name.", "defaultValue": "mbeanServer" }, + { + "name": "spring.jmx.unique-names", + "type": "java.lang.Boolean", + "description": "Whether to ensure that ObjectNames are modified in case of conflict.", + "defaultValue": false + }, { "name": "spring.jpa.open-in-view", "defaultValue": true diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfigurationTests.java index fd9f3ae837..a5e7c96702 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfigurationTests.java @@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link JmxAutoConfiguration}. * * @author Christian Dupuis + * @author Artsiom Yudovin */ public class JmxAutoConfigurationTests { @@ -92,6 +93,7 @@ public class JmxAutoConfigurationTests { MockEnvironment env = new MockEnvironment(); env.setProperty("spring.jmx.enabled", "true"); env.setProperty("spring.jmx.default-domain", "my-test-domain"); + env.setProperty("spring.jmx.unique-names", "true"); this.context = new AnnotationConfigApplicationContext(); this.context.setEnvironment(env); this.context.register(TestConfiguration.class, JmxAutoConfiguration.class); @@ -102,6 +104,8 @@ public class JmxAutoConfigurationTests { .getField(mBeanExporter, "namingStrategy"); assertThat(ReflectionTestUtils.getField(naming, "defaultDomain")) .isEqualTo("my-test-domain"); + assertThat(ReflectionTestUtils.getField(naming, "ensureUniqueRuntimeObjectNames")) + .isEqualTo(true); } @Test diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 45a6118717..2d2f8d9c28 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -100,6 +100,7 @@ content into your application. Rather, pick only the properties that you need. spring.jmx.default-domain= # JMX domain name. spring.jmx.enabled=true # Expose management beans to the JMX domain. spring.jmx.server=mbeanServer # MBeanServer bean name. + spring.jmx.unique-names=false # Set if unique runtime object names should be ensured. # Email ({sc-spring-boot-autoconfigure}/mail/MailProperties.{sc-ext}[MailProperties]) spring.mail.default-encoding=UTF-8 # Default MimeMessage encoding. From e6b44189e0e1a92da165009a4c9ced88d6eb9d73 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 6 Aug 2018 14:32:44 +0200 Subject: [PATCH 2/2] Polish "Add global support for JMX unique names" This commit ensures that the new "spring.jmx.unique-names" property deprecates the Endpoint's specific property as they share the same goal. If both are set with an incompatible value, an exception is thrown inviting the user to update their configuration. Closes gh-13990 --- .../jmx/DefaultEndpointObjectNameFactory.java | 24 ++++++++++++-- .../jmx/JmxEndpointAutoConfiguration.java | 5 +-- .../endpoint/jmx/JmxEndpointProperties.java | 12 ++++--- ...DefaultEndpointObjectNameFactoryTests.java | 32 +++++++++++++++++-- .../jmx/JmxAutoConfiguration.java | 2 -- ...itional-spring-configuration-metadata.json | 2 +- .../appendix-application-properties.adoc | 3 +- .../asciidoc/production-ready-features.adoc | 7 ++-- 8 files changed, 68 insertions(+), 19 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java index c513b4933e..7bfff096a0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java @@ -22,6 +22,7 @@ import javax.management.ObjectName; import org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; +import org.springframework.core.env.Environment; import org.springframework.jmx.support.ObjectNameManager; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -40,11 +41,30 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { private final String contextId; + private final boolean uniqueNames; + DefaultEndpointObjectNameFactory(JmxEndpointProperties properties, - MBeanServer mBeanServer, String contextId) { + Environment environment, MBeanServer mBeanServer, String contextId) { this.properties = properties; this.mBeanServer = mBeanServer; this.contextId = contextId; + this.uniqueNames = determineUniqueNames(environment, properties); + } + + @SuppressWarnings("deprecation") + private static boolean determineUniqueNames(Environment environment, + JmxEndpointProperties properties) { + Boolean uniqueName = environment.getProperty("spring.jmx.unique-names", + Boolean.class); + Boolean endpointUniqueNames = properties.getUniqueNames(); + if (uniqueName == null) { + return (endpointUniqueNames != null) ? endpointUniqueNames : false; + } + else if (endpointUniqueNames != null & !uniqueName.equals(endpointUniqueNames)) { + throw new IllegalArgumentException( + "Configuration mismatch, 'management.endpoints.jmx.unique-names' is deprecated, use only 'spring.jmx.unique-names'"); + } + return uniqueName; } @Override @@ -57,7 +77,7 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { if (this.mBeanServer != null && hasMBean(baseName)) { builder.append(",context=" + this.contextId); } - if (this.properties.isUniqueNames()) { + if (this.uniqueNames) { String identity = ObjectUtils.getIdentityHexString(endpoint); builder.append(",identity=" + identity); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java index df7d5505bd..54cb45d92b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java @@ -45,6 +45,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.util.ObjectUtils; /** @@ -84,11 +85,11 @@ public class JmxEndpointAutoConfiguration { @Bean @ConditionalOnSingleCandidate(MBeanServer.class) public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, - ObjectProvider objectMapper, + Environment environment, ObjectProvider objectMapper, JmxEndpointsSupplier jmxEndpointsSupplier) { String contextId = ObjectUtils.getIdentityHexString(this.applicationContext); EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory( - this.properties, mBeanServer, contextId); + this.properties, environment, mBeanServer, contextId); JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper( objectMapper.getIfAvailable()); return new JmxEndpointExporter(mBeanServer, objectNameFactory, responseMapper, diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointProperties.java index 02faf967d3..f77cf02b9b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointProperties.java @@ -21,6 +21,7 @@ import java.util.Properties; import java.util.Set; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; @@ -41,9 +42,9 @@ public class JmxEndpointProperties { private String domain = "org.springframework.boot"; /** - * Whether to ensure that ObjectNames are modified in case of conflict. + * Whether unique runtime object names should be ensured. */ - private boolean uniqueNames = false; + private Boolean uniqueNames; /** * Additional static properties to append to all ObjectNames of MBeans representing @@ -70,11 +71,14 @@ public class JmxEndpointProperties { this.domain = domain; } - public boolean isUniqueNames() { + @Deprecated + @DeprecatedConfigurationProperty(replacement = "spring.jmx.unique-names") + public Boolean getUniqueNames() { return this.uniqueNames; } - public void setUniqueNames(boolean uniqueNames) { + @Deprecated + public void setUniqueNames(Boolean uniqueNames) { this.uniqueNames = uniqueNames; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java index 08ebe3e580..19522be106 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java @@ -22,7 +22,9 @@ import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.mock.env.MockEnvironment; @@ -39,6 +41,9 @@ import static org.mockito.Mockito.mock; */ public class DefaultEndpointObjectNameFactoryTests { + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private final MockEnvironment environment = new MockEnvironment(); private final JmxEndpointProperties properties = new JmxEndpointProperties( @@ -72,7 +77,18 @@ public class DefaultEndpointObjectNameFactoryTests { @Test public void generateObjectNameWithUniqueNames() { + this.environment.setProperty("spring.jmx.unique-names", "true"); + assertUniqueObjectName(); + } + + @Test + @Deprecated + public void generateObjectNameWithUniqueNamesDeprecatedProperty() { this.properties.setUniqueNames(true); + assertUniqueObjectName(); + } + + private void assertUniqueObjectName() { ExposableJmxEndpoint endpoint = endpoint("test"); String id = ObjectUtils.getIdentityHexString(endpoint); ObjectName objectName = generateObjectName(endpoint); @@ -80,6 +96,18 @@ public class DefaultEndpointObjectNameFactoryTests { "org.springframework.boot:type=Endpoint,name=Test,identity=" + id); } + @Test + @Deprecated + public void generateObjectNameWithUniqueNamesDeprecatedPropertyMismatchMainProperty() { + this.environment.setProperty("spring.jmx.unique-names", "false"); + this.properties.setUniqueNames(true); + + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("spring.jmx.unique-names"); + this.thrown.expectMessage("management.endpoints.jmx.unique-names"); + generateObjectName(endpoint("test")); + } + @Test public void generateObjectNameWithStaticNames() { this.properties.getStaticNames().setProperty("counter", "42"); @@ -107,8 +135,8 @@ public class DefaultEndpointObjectNameFactoryTests { private ObjectName generateObjectName(ExposableJmxEndpoint endpoint) { try { - return new DefaultEndpointObjectNameFactory(this.properties, this.mBeanServer, - this.contextId).getObjectName(endpoint); + return new DefaultEndpointObjectNameFactory(this.properties, this.environment, + this.mBeanServer, this.contextId).getObjectName(endpoint); } catch (MalformedObjectNameException ex) { throw new AssertionError("Invalid object name", ex); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java index 99b31a66d7..4bcd4ac82f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java @@ -94,11 +94,9 @@ public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware if (StringUtils.hasLength(defaultDomain)) { namingStrategy.setDefaultDomain(defaultDomain); } - boolean uniqueName = this.environment.getProperty("spring.jmx.unique-names", Boolean.class, false); namingStrategy.setEnsureUniqueRuntimeObjectNames(uniqueName); - return namingStrategy; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 4bf7b6b747..e6c5126dc3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -293,7 +293,7 @@ { "name": "spring.jmx.unique-names", "type": "java.lang.Boolean", - "description": "Whether to ensure that ObjectNames are modified in case of conflict.", + "description": "Whether unique runtime object names should be ensured.", "defaultValue": false }, { diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 2d2f8d9c28..7343cefff1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -100,7 +100,7 @@ content into your application. Rather, pick only the properties that you need. spring.jmx.default-domain= # JMX domain name. spring.jmx.enabled=true # Expose management beans to the JMX domain. spring.jmx.server=mbeanServer # MBeanServer bean name. - spring.jmx.unique-names=false # Set if unique runtime object names should be ensured. + spring.jmx.unique-names=false # Whether unique runtime object names should be ensured. # Email ({sc-spring-boot-autoconfigure}/mail/MailProperties.{sc-ext}[MailProperties]) spring.mail.default-encoding=UTF-8 # Default MimeMessage encoding. @@ -1214,7 +1214,6 @@ content into your application. Rather, pick only the properties that you need. management.endpoints.jmx.exposure.include=* # Endpoint IDs that should be included or '*' for all. management.endpoints.jmx.exposure.exclude= # Endpoint IDs that should be excluded or '*' for all. management.endpoints.jmx.static-names= # Additional static properties to append to all ObjectNames of MBeans representing Endpoints. - management.endpoints.jmx.unique-names=false # Whether to ensure that ObjectNames are modified in case of conflict. # ENDPOINTS WEB CONFIGURATION ({sc-spring-boot-actuator-autoconfigure}/endpoint/web/WebEndpointProperties.{sc-ext}[WebEndpointProperties]) management.endpoints.web.exposure.include=health,info # Endpoint IDs that should be included or '*' for all. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 9606047882..3525ab8e84 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -1200,17 +1200,16 @@ The name of the MBean is usually generated from the `id` of the endpoint. For ex `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 -`management.endpoints.jmx.unique-names` property to `true` so that MBean names are always -unique. +names clash. To solve this problem, you can set the `spring.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. The following settings show an example of doing so in `application.properties`: [source,properties,indent=0] ---- + spring.jmx.unique-names=true management.endpoints.jmx.domain=com.example.myapp - management.endpoints.jmx.unique-names=true ----