Commit e4e8311a authored by Stephane Nicoll's avatar Stephane Nicoll

Merge pull request #13990 from ayudovin:support-ensureUniqueRuntimeObjectNames-globally

* pr/13990:
  Polish "Add global support for JMX unique names"
  Add global support for JMX unique names
parents c071f34a e6b44189
...@@ -22,6 +22,7 @@ import javax.management.ObjectName; ...@@ -22,6 +22,7 @@ import javax.management.ObjectName;
import org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory; import org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory;
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
import org.springframework.core.env.Environment;
import org.springframework.jmx.support.ObjectNameManager; import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -40,11 +41,30 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { ...@@ -40,11 +41,30 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
private final String contextId; private final String contextId;
private final boolean uniqueNames;
DefaultEndpointObjectNameFactory(JmxEndpointProperties properties, DefaultEndpointObjectNameFactory(JmxEndpointProperties properties,
MBeanServer mBeanServer, String contextId) { Environment environment, MBeanServer mBeanServer, String contextId) {
this.properties = properties; this.properties = properties;
this.mBeanServer = mBeanServer; this.mBeanServer = mBeanServer;
this.contextId = contextId; 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 @Override
...@@ -57,7 +77,7 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { ...@@ -57,7 +77,7 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
if (this.mBeanServer != null && hasMBean(baseName)) { if (this.mBeanServer != null && hasMBean(baseName)) {
builder.append(",context=" + this.contextId); builder.append(",context=" + this.contextId);
} }
if (this.properties.isUniqueNames()) { if (this.uniqueNames) {
String identity = ObjectUtils.getIdentityHexString(endpoint); String identity = ObjectUtils.getIdentityHexString(endpoint);
builder.append(",identity=" + identity); builder.append(",identity=" + identity);
} }
......
...@@ -45,6 +45,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties ...@@ -45,6 +45,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.ApplicationContext; 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.env.Environment;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
...@@ -84,11 +85,11 @@ public class JmxEndpointAutoConfiguration { ...@@ -84,11 +85,11 @@ public class JmxEndpointAutoConfiguration {
@Bean @Bean
@ConditionalOnSingleCandidate(MBeanServer.class) @ConditionalOnSingleCandidate(MBeanServer.class)
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer,
ObjectProvider<ObjectMapper> objectMapper, Environment environment, ObjectProvider<ObjectMapper> objectMapper,
JmxEndpointsSupplier jmxEndpointsSupplier) { JmxEndpointsSupplier jmxEndpointsSupplier) {
String contextId = ObjectUtils.getIdentityHexString(this.applicationContext); String contextId = ObjectUtils.getIdentityHexString(this.applicationContext);
EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory( EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory(
this.properties, mBeanServer, contextId); this.properties, environment, mBeanServer, contextId);
JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper( JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper(
objectMapper.getIfAvailable()); objectMapper.getIfAvailable());
return new JmxEndpointExporter(mBeanServer, objectNameFactory, responseMapper, return new JmxEndpointExporter(mBeanServer, objectNameFactory, responseMapper,
......
...@@ -21,6 +21,7 @@ import java.util.Properties; ...@@ -21,6 +21,7 @@ import java.util.Properties;
import java.util.Set; import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -41,9 +42,9 @@ public class JmxEndpointProperties { ...@@ -41,9 +42,9 @@ public class JmxEndpointProperties {
private String domain = "org.springframework.boot"; 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 * Additional static properties to append to all ObjectNames of MBeans representing
...@@ -70,11 +71,14 @@ public class JmxEndpointProperties { ...@@ -70,11 +71,14 @@ public class JmxEndpointProperties {
this.domain = domain; this.domain = domain;
} }
public boolean isUniqueNames() { @Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.jmx.unique-names")
public Boolean getUniqueNames() {
return this.uniqueNames; return this.uniqueNames;
} }
public void setUniqueNames(boolean uniqueNames) { @Deprecated
public void setUniqueNames(Boolean uniqueNames) {
this.uniqueNames = uniqueNames; this.uniqueNames = uniqueNames;
} }
......
...@@ -22,7 +22,9 @@ import javax.management.MBeanServer; ...@@ -22,7 +22,9 @@ import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
import javax.management.ObjectName; import javax.management.ObjectName;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
...@@ -39,6 +41,9 @@ import static org.mockito.Mockito.mock; ...@@ -39,6 +41,9 @@ import static org.mockito.Mockito.mock;
*/ */
public class DefaultEndpointObjectNameFactoryTests { public class DefaultEndpointObjectNameFactoryTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final MockEnvironment environment = new MockEnvironment(); private final MockEnvironment environment = new MockEnvironment();
private final JmxEndpointProperties properties = new JmxEndpointProperties( private final JmxEndpointProperties properties = new JmxEndpointProperties(
...@@ -72,7 +77,18 @@ public class DefaultEndpointObjectNameFactoryTests { ...@@ -72,7 +77,18 @@ public class DefaultEndpointObjectNameFactoryTests {
@Test @Test
public void generateObjectNameWithUniqueNames() { public void generateObjectNameWithUniqueNames() {
this.environment.setProperty("spring.jmx.unique-names", "true");
assertUniqueObjectName();
}
@Test
@Deprecated
public void generateObjectNameWithUniqueNamesDeprecatedProperty() {
this.properties.setUniqueNames(true); this.properties.setUniqueNames(true);
assertUniqueObjectName();
}
private void assertUniqueObjectName() {
ExposableJmxEndpoint endpoint = endpoint("test"); ExposableJmxEndpoint endpoint = endpoint("test");
String id = ObjectUtils.getIdentityHexString(endpoint); String id = ObjectUtils.getIdentityHexString(endpoint);
ObjectName objectName = generateObjectName(endpoint); ObjectName objectName = generateObjectName(endpoint);
...@@ -80,6 +96,18 @@ public class DefaultEndpointObjectNameFactoryTests { ...@@ -80,6 +96,18 @@ public class DefaultEndpointObjectNameFactoryTests {
"org.springframework.boot:type=Endpoint,name=Test,identity=" + id); "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 @Test
public void generateObjectNameWithStaticNames() { public void generateObjectNameWithStaticNames() {
this.properties.getStaticNames().setProperty("counter", "42"); this.properties.getStaticNames().setProperty("counter", "42");
...@@ -107,8 +135,8 @@ public class DefaultEndpointObjectNameFactoryTests { ...@@ -107,8 +135,8 @@ public class DefaultEndpointObjectNameFactoryTests {
private ObjectName generateObjectName(ExposableJmxEndpoint endpoint) { private ObjectName generateObjectName(ExposableJmxEndpoint endpoint) {
try { try {
return new DefaultEndpointObjectNameFactory(this.properties, this.mBeanServer, return new DefaultEndpointObjectNameFactory(this.properties, this.environment,
this.contextId).getObjectName(endpoint); this.mBeanServer, this.contextId).getObjectName(endpoint);
} }
catch (MalformedObjectNameException ex) { catch (MalformedObjectNameException ex) {
throw new AssertionError("Invalid object name", ex); throw new AssertionError("Invalid object name", ex);
......
...@@ -49,6 +49,7 @@ import org.springframework.util.StringUtils; ...@@ -49,6 +49,7 @@ import org.springframework.util.StringUtils;
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Madhura Bhave * @author Madhura Bhave
* @author Artsiom Yudovin
*/ */
@Configuration @Configuration
@ConditionalOnClass({ MBeanExporter.class }) @ConditionalOnClass({ MBeanExporter.class })
...@@ -93,6 +94,9 @@ public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware ...@@ -93,6 +94,9 @@ public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware
if (StringUtils.hasLength(defaultDomain)) { if (StringUtils.hasLength(defaultDomain)) {
namingStrategy.setDefaultDomain(defaultDomain); namingStrategy.setDefaultDomain(defaultDomain);
} }
boolean uniqueName = this.environment.getProperty("spring.jmx.unique-names",
Boolean.class, false);
namingStrategy.setEnsureUniqueRuntimeObjectNames(uniqueName);
return namingStrategy; return namingStrategy;
} }
......
...@@ -290,6 +290,12 @@ ...@@ -290,6 +290,12 @@
"description": "MBeanServer bean name.", "description": "MBeanServer bean name.",
"defaultValue": "mbeanServer" "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", "name": "spring.jpa.open-in-view",
"defaultValue": true "defaultValue": true
......
...@@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link JmxAutoConfiguration}. * Tests for {@link JmxAutoConfiguration}.
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Artsiom Yudovin
*/ */
public class JmxAutoConfigurationTests { public class JmxAutoConfigurationTests {
...@@ -92,6 +93,7 @@ public class JmxAutoConfigurationTests { ...@@ -92,6 +93,7 @@ public class JmxAutoConfigurationTests {
MockEnvironment env = new MockEnvironment(); MockEnvironment env = new MockEnvironment();
env.setProperty("spring.jmx.enabled", "true"); env.setProperty("spring.jmx.enabled", "true");
env.setProperty("spring.jmx.default-domain", "my-test-domain"); env.setProperty("spring.jmx.default-domain", "my-test-domain");
env.setProperty("spring.jmx.unique-names", "true");
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
this.context.setEnvironment(env); this.context.setEnvironment(env);
this.context.register(TestConfiguration.class, JmxAutoConfiguration.class); this.context.register(TestConfiguration.class, JmxAutoConfiguration.class);
...@@ -102,6 +104,8 @@ public class JmxAutoConfigurationTests { ...@@ -102,6 +104,8 @@ public class JmxAutoConfigurationTests {
.getField(mBeanExporter, "namingStrategy"); .getField(mBeanExporter, "namingStrategy");
assertThat(ReflectionTestUtils.getField(naming, "defaultDomain")) assertThat(ReflectionTestUtils.getField(naming, "defaultDomain"))
.isEqualTo("my-test-domain"); .isEqualTo("my-test-domain");
assertThat(ReflectionTestUtils.getField(naming, "ensureUniqueRuntimeObjectNames"))
.isEqualTo(true);
} }
@Test @Test
......
...@@ -100,6 +100,7 @@ content into your application. Rather, pick only the properties that you need. ...@@ -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.default-domain= # JMX domain name.
spring.jmx.enabled=true # Expose management beans to the JMX domain. spring.jmx.enabled=true # Expose management beans to the JMX domain.
spring.jmx.server=mbeanServer # MBeanServer bean name. 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]) # Email ({sc-spring-boot-autoconfigure}/mail/MailProperties.{sc-ext}[MailProperties])
spring.mail.default-encoding=UTF-8 # Default MimeMessage encoding. 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. ...@@ -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.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.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.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]) # 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. management.endpoints.web.exposure.include=health,info # Endpoint IDs that should be included or '*' for all.
......
...@@ -1200,17 +1200,16 @@ The name of the MBean is usually generated from the `id` of the endpoint. For ex ...@@ -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`. `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 names clash. To solve this problem, you can set the `spring.jmx.unique-names` property to
`management.endpoints.jmx.unique-names` property to `true` so that MBean names are always `true` so that MBean names are always unique.
unique.
You can also customize the JMX domain under which endpoints are exposed. The following You can also customize the JMX domain under which endpoints are exposed. The following
settings show an example of doing so in `application.properties`: settings show an example of doing so in `application.properties`:
[source,properties,indent=0] [source,properties,indent=0]
---- ----
spring.jmx.unique-names=true
management.endpoints.jmx.domain=com.example.myapp management.endpoints.jmx.domain=com.example.myapp
management.endpoints.jmx.unique-names=true
---- ----
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment