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
This commit is contained in:
Stephane Nicoll
2018-08-06 14:32:44 +02:00
parent 1a3f08d772
commit e6b44189e0
8 changed files with 68 additions and 19 deletions

View File

@@ -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);
}

View File

@@ -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> objectMapper,
Environment environment, ObjectProvider<ObjectMapper> 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,

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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
},
{

View File

@@ -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.

View File

@@ -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
----