Skip to content

Commit 151f7ef

Browse files
committed
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 spring-projectsgh-10005
1 parent 35cf0c5 commit 151f7ef

File tree

7 files changed

+267
-31
lines changed

7 files changed

+267
-31
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
1818

19+
import java.util.Map;
20+
21+
import javax.management.MBeanServer;
1922
import javax.management.MalformedObjectNameException;
2023
import javax.management.ObjectName;
2124

2225
import org.springframework.boot.endpoint.jmx.EndpointMBean;
2326
import org.springframework.boot.endpoint.jmx.EndpointObjectNameFactory;
2427
import org.springframework.jmx.support.ObjectNameManager;
28+
import org.springframework.util.ObjectUtils;
2529
import org.springframework.util.StringUtils;
2630

2731
/**
@@ -32,15 +36,50 @@
3236
*/
3337
class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
3438

35-
private String domain = "org.springframework.boot";
39+
private final JmxEndpointExporterProperties properties;
40+
41+
private final MBeanServer mBeanServer;
42+
43+
private final String contextId;
44+
45+
DefaultEndpointObjectNameFactory(JmxEndpointExporterProperties properties,
46+
MBeanServer mBeanServer, String contextId) {
47+
this.properties = properties;
48+
this.mBeanServer = mBeanServer;
49+
this.contextId = contextId;
50+
}
3651

3752
@Override
3853
public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException {
39-
StringBuilder builder = new StringBuilder();
40-
builder.append(this.domain);
41-
builder.append(":type=Endpoint");
42-
builder.append(",name=" + StringUtils.capitalize(mBean.getEndpointId()));
54+
String baseObjectName = this.properties.getDomain() +
55+
":type=Endpoint" +
56+
",name=" + StringUtils.capitalize(mBean.getEndpointId());
57+
StringBuilder builder = new StringBuilder(baseObjectName);
58+
if (this.mBeanServer != null && hasMBean(baseObjectName)) {
59+
builder.append(",context=").append(this.contextId);
60+
}
61+
if (this.properties.isUniqueNames()) {
62+
builder.append(",identity=").append(ObjectUtils.getIdentityHexString(mBean));
63+
}
64+
builder.append(getStaticNames());
4365
return ObjectNameManager.getInstance(builder.toString());
4466
}
4567

68+
private boolean hasMBean(String baseObjectName) throws MalformedObjectNameException {
69+
ObjectName query = new ObjectName(baseObjectName + ",*");
70+
return this.mBeanServer.queryNames(query, null).size() > 0;
71+
}
72+
73+
private String getStaticNames() {
74+
if (this.properties.getStaticNames().isEmpty()) {
75+
return "";
76+
}
77+
StringBuilder builder = new StringBuilder();
78+
79+
for (Map.Entry<Object, Object> name : this.properties.getStaticNames().entrySet()) {
80+
builder.append(",").append(name.getKey()).append("=").append(name.getValue());
81+
}
82+
return builder.toString();
83+
}
84+
4685
}

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
3333
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3434
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
35+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3536
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
3637
import org.springframework.boot.endpoint.EndpointType;
3738
import org.springframework.boot.endpoint.OperationParameterMapper;
@@ -44,6 +45,7 @@
4445
import org.springframework.context.annotation.Bean;
4546
import org.springframework.context.annotation.Configuration;
4647
import org.springframework.core.convert.support.DefaultConversionService;
48+
import org.springframework.util.ObjectUtils;
4749
import org.springframework.util.StringUtils;
4850

4951
/**
@@ -56,6 +58,7 @@
5658
*/
5759
@Configuration
5860
@AutoConfigureAfter(JmxAutoConfiguration.class)
61+
@EnableConfigurationProperties(JmxEndpointExporterProperties.class)
5962
public class EndpointInfrastructureAutoConfiguration {
6063

6164
private final ApplicationContext applicationContext;
@@ -94,18 +97,20 @@ public JmxAnnotationEndpointDiscoverer jmxEndpointDiscoverer(
9497

9598
@ConditionalOnSingleCandidate(MBeanServer.class)
9699
@Bean
97-
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer,
98-
JmxAnnotationEndpointDiscoverer endpointDiscoverer,
100+
public JmxEndpointExporter jmxMBeanExporter(JmxEndpointExporterProperties properties,
101+
MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer,
99102
ObjectProvider<ObjectMapper> objectMapper) {
100103
EndpointProvider<JmxEndpointOperation> endpointProvider = new EndpointProvider<>(
101104
this.applicationContext.getEnvironment(), endpointDiscoverer,
102105
EndpointType.JMX);
103106
EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar(
104-
mBeanServer, new DefaultEndpointObjectNameFactory());
107+
mBeanServer, new DefaultEndpointObjectNameFactory(properties,
108+
mBeanServer, ObjectUtils.getIdentityHexString(this.applicationContext)));
105109
return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar,
106110
objectMapper.getIfAvailable(ObjectMapper::new));
107111
}
108112

113+
@Configuration
109114
@ConditionalOnWebApplication
110115
static class WebInfrastructureConfiguration {
111116

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
18+
19+
import java.util.Properties;
20+
21+
import org.springframework.boot.context.properties.ConfigurationProperties;
22+
import org.springframework.core.env.Environment;
23+
import org.springframework.util.StringUtils;
24+
25+
/**
26+
* Configuration properties for JMX export of endpoints.
27+
*
28+
* @author Stephane Nicoll
29+
* @since 2.0.0
30+
*/
31+
@ConfigurationProperties("management.jmx")
32+
public class JmxEndpointExporterProperties {
33+
34+
/**
35+
* Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set.
36+
*/
37+
private String domain = "org.springframework.boot";
38+
39+
/**
40+
* Ensure that ObjectNames are modified in case of conflict.
41+
*/
42+
private boolean uniqueNames = false;
43+
44+
/**
45+
* Additional static properties to append to all ObjectNames of MBeans representing
46+
* Endpoints.
47+
*/
48+
private final Properties staticNames = new Properties();
49+
50+
public JmxEndpointExporterProperties(Environment environment) {
51+
String defaultDomain = environment.getProperty("spring.jmx.default-domain");
52+
if (StringUtils.hasText(defaultDomain)) {
53+
this.domain = defaultDomain;
54+
}
55+
}
56+
57+
public String getDomain() {
58+
return this.domain;
59+
}
60+
61+
public void setDomain(String domain) {
62+
this.domain = domain;
63+
}
64+
65+
public boolean isUniqueNames() {
66+
return this.uniqueNames;
67+
}
68+
69+
public void setUniqueNames(boolean uniqueNames) {
70+
this.uniqueNames = uniqueNames;
71+
}
72+
73+
public Properties getStaticNames() {
74+
return this.staticNames;
75+
}
76+
77+
}

spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,6 @@
5252
"type": "java.lang.String",
5353
"description": "Endpoint URL path."
5454
},
55-
{
56-
"name": "endpoints.jmx.enabled",
57-
"type": "java.lang.Boolean",
58-
"description": "Enable JMX export of all endpoints.",
59-
"defaultValue": true
60-
},
6155
{
6256
"name": "endpoints.mappings.path",
6357
"type": "java.lang.String",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
18+
19+
import java.util.Collections;
20+
21+
import javax.management.MBeanServer;
22+
import javax.management.MalformedObjectNameException;
23+
import javax.management.ObjectName;
24+
25+
import org.junit.Test;
26+
27+
import org.springframework.boot.endpoint.jmx.EndpointMBean;
28+
import org.springframework.mock.env.MockEnvironment;
29+
import org.springframework.util.ObjectUtils;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.mockito.BDDMockito.given;
33+
import static org.mockito.Mockito.mock;
34+
35+
/**
36+
* Tests for {@link DefaultEndpointObjectNameFactory}.
37+
*
38+
* @author Stephane Nicoll
39+
*/
40+
public class DefaultEndpointObjectNameFactoryTests {
41+
42+
private final MockEnvironment environment = new MockEnvironment();
43+
44+
private final JmxEndpointExporterProperties properties = new JmxEndpointExporterProperties(this.environment);
45+
46+
private final MBeanServer mBeanServer = mock(MBeanServer.class);
47+
48+
private String contextId;
49+
50+
@Test
51+
public void generateObjectName() {
52+
ObjectName objectName = generateObjectName(endpoint("Test"));
53+
assertThat(objectName.toString()).isEqualTo(
54+
"org.springframework.boot:type=Endpoint,name=Test");
55+
}
56+
57+
@Test
58+
public void generateObjectNameWithCapitalizedId() {
59+
ObjectName objectName = generateObjectName(endpoint("test"));
60+
assertThat(objectName.toString()).isEqualTo(
61+
"org.springframework.boot:type=Endpoint,name=Test");
62+
}
63+
64+
@Test
65+
public void generateObjectNameWithCustomDomain() {
66+
this.properties.setDomain("com.example.acme");
67+
ObjectName objectName = generateObjectName(endpoint("test"));
68+
assertThat(objectName.toString()).isEqualTo(
69+
"com.example.acme:type=Endpoint,name=Test");
70+
}
71+
72+
@Test
73+
public void generateObjectNameWithUniqueNames() {
74+
this.properties.setUniqueNames(true);
75+
EndpointMBean endpoint = endpoint("test");
76+
String id = ObjectUtils.getIdentityHexString(endpoint);
77+
ObjectName objectName = generateObjectName(endpoint);
78+
assertThat(objectName.toString()).isEqualTo(
79+
"org.springframework.boot:type=Endpoint,name=Test,identity=" + id);
80+
}
81+
82+
@Test
83+
public void generateObjectNameWithStaticNames() {
84+
this.properties.getStaticNames().setProperty("counter", "42");
85+
this.properties.getStaticNames().setProperty("foo", "bar");
86+
ObjectName objectName = generateObjectName(endpoint("test"));
87+
assertThat(objectName.getKeyProperty("counter")).isEqualTo("42");
88+
assertThat(objectName.getKeyProperty("foo")).isEqualTo("bar");
89+
assertThat(objectName.toString()).startsWith(
90+
"org.springframework.boot:type=Endpoint,name=Test,");
91+
}
92+
93+
@Test
94+
public void generateObjectNameWithDuplicate() throws MalformedObjectNameException {
95+
this.contextId = "testContext";
96+
given(this.mBeanServer.queryNames(new ObjectName(
97+
"org.springframework.boot:type=Endpoint,name=Test,*"), null))
98+
.willReturn(Collections.singleton(
99+
new ObjectName("org.springframework.boot:type=Endpoint,name=Test")));
100+
ObjectName objectName = generateObjectName(endpoint("test"));
101+
assertThat(objectName.toString()).isEqualTo(
102+
"org.springframework.boot:type=Endpoint,name=Test,context=testContext");
103+
104+
}
105+
106+
private ObjectName generateObjectName(EndpointMBean endpointMBean) {
107+
try {
108+
return new DefaultEndpointObjectNameFactory(this.properties, this.mBeanServer,
109+
this.contextId).generate(endpointMBean);
110+
}
111+
catch (MalformedObjectNameException ex) {
112+
throw new AssertionError("Invalid object name", ex);
113+
}
114+
}
115+
116+
private EndpointMBean endpoint(String id) {
117+
EndpointMBean endpointMBean = mock(EndpointMBean.class);
118+
given(endpointMBean.getEndpointId()).willReturn(id);
119+
return endpointMBean;
120+
}
121+
122+
}

spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,17 +1119,6 @@ content into your application; rather pick only the properties that you need.
11191119
endpoints.cors.exposed-headers= # Comma-separated list of headers to include in a response.
11201120
endpoints.cors.max-age=1800 # How long, in seconds, the response from a pre-flight request can be cached by clients.
11211121
1122-
# JMX ENDPOINT ({sc-spring-boot-actuator}/autoconfigure/EndpointMBeanExportProperties.{sc-ext}[EndpointMBeanExportProperties])
1123-
endpoints.jmx.domain= # JMX domain name. Initialized with the value of 'spring.jmx.default-domain' if set.
1124-
endpoints.jmx.enabled=true # Enable JMX export of all endpoints.
1125-
endpoints.jmx.static-names= # Additional static properties to append to all ObjectNames of MBeans representing Endpoints.
1126-
endpoints.jmx.unique-names=false # Ensure that ObjectNames are modified in case of conflict.
1127-
1128-
# JOLOKIA ({sc-spring-boot-actuator}/autoconfigure/jolokia/JolokiaProperties.{sc-ext}[JolokiaProperties])
1129-
management.jolokia.config.*= # Jolokia settings. See the Jolokia manual for details.
1130-
management.jolokia.enabled=true # Enable Jolokia.
1131-
management.jolokia.path=/jolokia # Path at which Jolokia will be available.
1132-
11331122
# MANAGEMENT HTTP SERVER ({sc-spring-boot-actuator}/autoconfigure/ManagementServerProperties.{sc-ext}[ManagementServerProperties])
11341123
management.add-application-context-header=false # Add the "X-Application-Context" HTTP header in each response.
11351124
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.
11841173
management.info.git.enabled=true # Enable git info.
11851174
management.info.git.mode=simple # Mode to use to expose git information.
11861175
1176+
# JMX ENDPOINT ({sc-spring-boot-actuator}/autoconfigure/endpoint.infrastructure/JmxEndpointExporterProperties.{sc-ext}[JmxEndpointExporterProperties])
1177+
management.jmx.domain=org.springframework.boot # Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set.
1178+
management.jmx.static-names=false # Additional static properties to append to all ObjectNames of MBeans representing Endpoints.
1179+
management.jmx.unique-names=false # Ensure that ObjectNames are modified in case of conflict.
1180+
1181+
# JOLOKIA ({sc-spring-boot-actuator}/autoconfigure/jolokia/JolokiaProperties.{sc-ext}[JolokiaProperties])
1182+
management.jolokia.config.*= # Jolokia settings. See the Jolokia manual for details.
1183+
management.jolokia.enabled=true # Enable Jolokia.
1184+
management.jolokia.path=/jolokia # Path at which Jolokia will be available.
1185+
11871186
# TRACING ({sc-spring-boot-actuator}/trace/TraceProperties.{sc-ext}[TraceProperties])
11881187
management.trace.include=request-headers,response-headers,cookies,errors # Items to be included in the trace.
11891188

0 commit comments

Comments
 (0)