Skip to content

Commit 4e94f26

Browse files
committed
Support JMX endpoints when only Jackson 2 is present
Closes gh-47688
1 parent 8292954 commit 4e94f26

File tree

4 files changed

+119
-1
lines changed

4 files changed

+119
-1
lines changed

module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import javax.management.MBeanServer;
2020

21+
import com.fasterxml.jackson.databind.ObjectMapper;
2122
import tools.jackson.databind.json.JsonMapper;
2223

2324
import org.springframework.beans.factory.ObjectProvider;
@@ -44,6 +45,7 @@
4445
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
4546
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
4647
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
48+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
4749
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
4850
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
4951
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
@@ -133,4 +135,24 @@ JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer,
133135

134136
}
135137

138+
@Configuration(proxyBeanMethods = false)
139+
@ConditionalOnClass(ObjectMapper.class)
140+
@ConditionalOnMissingClass("tools.jackson.databind.json.JsonMapper")
141+
@Deprecated(since = "4.2.0", forRemoval = true)
142+
@SuppressWarnings("removal")
143+
static class JmxJackson2EndpointConfiguration {
144+
145+
@Bean
146+
@ConditionalOnSingleCandidate(MBeanServer.class)
147+
JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer,
148+
EndpointObjectNameFactory endpointObjectNameFactory, ObjectProvider<ObjectMapper> objectMapper,
149+
JmxEndpointsSupplier jmxEndpointsSupplier) {
150+
JmxOperationResponseMapper responseMapper = new org.springframework.boot.actuate.endpoint.jmx.Jackson2JmxOperationResponseMapper(
151+
objectMapper.getIfAvailable());
152+
return new JmxEndpointExporter(mBeanServer, endpointObjectNameFactory, responseMapper,
153+
jmxEndpointsSupplier.getEndpoints());
154+
}
155+
156+
}
157+
136158
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2012-present 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+
* https://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.endpoint.jmx;
18+
19+
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import com.fasterxml.jackson.databind.JavaType;
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import org.jspecify.annotations.Nullable;
26+
27+
import org.springframework.lang.Contract;
28+
29+
/**
30+
* {@link JmxOperationResponseMapper} that delegates to a Jackson 2 {@link ObjectMapper}
31+
* to return a JSON response.
32+
*
33+
* @author Stephane Nicoll
34+
* @since 4.0.0
35+
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of
36+
* {@link JacksonJmxOperationResponseMapper}.
37+
*/
38+
@Deprecated(since = "4.0.0", forRemoval = true)
39+
public class Jackson2JmxOperationResponseMapper implements JmxOperationResponseMapper {
40+
41+
private final ObjectMapper objectMapper;
42+
43+
private final JavaType listType;
44+
45+
private final JavaType mapType;
46+
47+
public Jackson2JmxOperationResponseMapper(@Nullable ObjectMapper objectMapper) {
48+
this.objectMapper = (objectMapper != null) ? objectMapper : new ObjectMapper();
49+
this.listType = this.objectMapper.getTypeFactory().constructParametricType(List.class, Object.class);
50+
this.mapType = this.objectMapper.getTypeFactory()
51+
.constructParametricType(Map.class, String.class, Object.class);
52+
}
53+
54+
@Override
55+
public Class<?> mapResponseType(Class<?> responseType) {
56+
if (CharSequence.class.isAssignableFrom(responseType)) {
57+
return String.class;
58+
}
59+
if (responseType.isArray() || Collection.class.isAssignableFrom(responseType)) {
60+
return List.class;
61+
}
62+
return Map.class;
63+
}
64+
65+
@Override
66+
@Contract("!null -> !null")
67+
public @Nullable Object mapResponse(@Nullable Object response) {
68+
if (response == null) {
69+
return null;
70+
}
71+
if (response instanceof CharSequence) {
72+
return response.toString();
73+
}
74+
if (response.getClass().isArray() || response instanceof Collection) {
75+
return this.objectMapper.convertValue(response, this.listType);
76+
}
77+
return this.objectMapper.convertValue(response, this.mapType);
78+
}
79+
80+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
management.endpoints.jmx.exposure.include=*
12
management.endpoints.web.exposure.include=*
3+

smoke-test/spring-boot-smoke-test-jackson2-only/src/test/java/smoketest/jackson2/only/SampleJackson2OnlyApplicationTests.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616

1717
package smoketest.jackson2.only;
1818

19+
import java.lang.management.ManagementFactory;
1920
import java.util.Map;
2021

22+
import javax.management.MBeanServer;
23+
import javax.management.ObjectName;
24+
2125
import org.junit.jupiter.api.Test;
2226

2327
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,7 +37,7 @@
3337
*
3438
* @author Andy Wilkinson
3539
*/
36-
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
40+
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.jmx.enabled=true")
3741
@AutoConfigureRestTestClient
3842
class SampleJackson2OnlyApplicationTests {
3943

@@ -64,4 +68,14 @@ void actuatorLinksShouldReturnOk() {
6468
.value((body) -> assertThat(body).containsOnlyKeys("_links"));
6569
}
6670

71+
@Test
72+
@SuppressWarnings("unchecked")
73+
void jmxEndpointsShouldWork() throws Exception {
74+
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
75+
Map<String, Object> result = (Map<String, Object>) mbeanServer.invoke(
76+
ObjectName.getInstance("org.springframework.boot:type=Endpoint,name=Configprops"),
77+
"configurationProperties", new Object[0], null);
78+
assertThat(result).containsOnlyKeys("contexts");
79+
}
80+
6781
}

0 commit comments

Comments
 (0)