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 c513b4933ef..7bfff096a0b 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 df7d5505bd8..54cb45d92be 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 02faf967d30..f77cf02b9b6 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 08ebe3e580c..19522be1066 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 7c68d99d5da..4bcd4ac82fd 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,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 ca4e754d7d0..e6c5126dc34 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 unique runtime object names should be ensured.", + "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 fd9f3ae8374..a5e7c967020 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 45a61187177..7343cefff1e 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 # 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. @@ -1213,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 96060478828..3525ab8e845 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 ----