Skip to content

Commit d1a5d4a

Browse files
committed
Set classloader for JMX endpoints to application classloader, #12088.
1 parent 0fcffae commit d1a5d4a

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@
2929
import javax.management.MBeanInfo;
3030
import javax.management.ReflectionException;
3131

32+
import org.apache.commons.logging.Log;
33+
import org.apache.commons.logging.LogFactory;
3234
import reactor.core.publisher.Mono;
3335

36+
import org.springframework.beans.factory.BeanClassLoaderAware;
3437
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
3538
import org.springframework.boot.actuate.endpoint.InvocationContext;
3639
import org.springframework.boot.actuate.endpoint.SecurityContext;
@@ -46,11 +49,15 @@
4649
* @author Phillip Webb
4750
* @since 2.0.0
4851
*/
49-
public class EndpointMBean implements DynamicMBean {
52+
public class EndpointMBean implements DynamicMBean, BeanClassLoaderAware {
5053

5154
private static final boolean REACTOR_PRESENT = ClassUtils.isPresent(
5255
"reactor.core.publisher.Mono", EndpointMBean.class.getClassLoader());
5356

57+
private static final Log logger = LogFactory.getLog(EndpointMBean.class);
58+
59+
private ClassLoader classLoader;
60+
5461
private final JmxOperationResponseMapper responseMapper;
5562

5663
private final ExposableJmxEndpoint endpoint;
@@ -69,6 +76,11 @@ public class EndpointMBean implements DynamicMBean {
6976
this.operations = getOperations(endpoint);
7077
}
7178

79+
@Override
80+
public void setBeanClassLoader(ClassLoader classLoader) {
81+
this.classLoader = classLoader;
82+
}
83+
7284
private Map<String, JmxOperation> getOperations(ExposableJmxEndpoint endpoint) {
7385
Map<String, JmxOperation> operations = new HashMap<>();
7486
endpoint.getOperations()
@@ -90,7 +102,28 @@ public Object invoke(String actionName, Object[] params, String[] signature)
90102
+ "' has no operation named " + actionName;
91103
throw new ReflectionException(new IllegalArgumentException(message), message);
92104
}
93-
return invoke(operation, params);
105+
ClassLoader previousClassLoader = overrideThreadContextClassLoaderSafe(this.classLoader);
106+
try {
107+
return invoke(operation, params);
108+
}
109+
finally {
110+
overrideThreadContextClassLoaderSafe(previousClassLoader);
111+
}
112+
}
113+
114+
private static ClassLoader overrideThreadContextClassLoaderSafe(ClassLoader classLoader) {
115+
if (classLoader == null) {
116+
return null;
117+
}
118+
119+
try {
120+
return ClassUtils.overrideThreadContextClassLoader(classLoader);
121+
}
122+
catch (SecurityException exc) {
123+
// can't set class loader, ignore it and proceed
124+
logger.warn("Unable to override class loader for JMX endpoint.");
125+
}
126+
return null;
94127
}
95128

96129
private Object invoke(JmxOperation operation, Object[] params)

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.commons.logging.Log;
3030
import org.apache.commons.logging.LogFactory;
3131

32+
import org.springframework.beans.factory.BeanClassLoaderAware;
3233
import org.springframework.beans.factory.DisposableBean;
3334
import org.springframework.beans.factory.InitializingBean;
3435
import org.springframework.jmx.JmxException;
@@ -42,10 +43,12 @@
4243
* @author Phillip Webb
4344
* @since 2.0.0
4445
*/
45-
public class JmxEndpointExporter implements InitializingBean, DisposableBean {
46+
public class JmxEndpointExporter implements InitializingBean, DisposableBean, BeanClassLoaderAware {
4647

4748
private static final Log logger = LogFactory.getLog(JmxEndpointExporter.class);
4849

50+
private ClassLoader classLoader;
51+
4952
private final MBeanServer mBeanServer;
5053

5154
private final EndpointObjectNameFactory objectNameFactory;
@@ -70,6 +73,11 @@ public JmxEndpointExporter(MBeanServer mBeanServer,
7073
this.endpoints = Collections.unmodifiableCollection(endpoints);
7174
}
7275

76+
@Override
77+
public void setBeanClassLoader(ClassLoader classLoader) {
78+
this.classLoader = classLoader;
79+
}
80+
7381
@Override
7482
public void afterPropertiesSet() {
7583
this.registered = register();
@@ -89,6 +97,7 @@ private ObjectName register(ExposableJmxEndpoint endpoint) {
8997
try {
9098
ObjectName name = this.objectNameFactory.getObjectName(endpoint);
9199
EndpointMBean mbean = new EndpointMBean(this.responseMapper, endpoint);
100+
mbean.setBeanClassLoader(this.classLoader);
92101
this.mBeanServer.registerMBean(mbean, name);
93102
return name;
94103
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot.actuate.endpoint.jmx;
1818

19+
import java.net.URL;
20+
import java.net.URLClassLoader;
21+
1922
import javax.management.Attribute;
2023
import javax.management.AttributeList;
2124
import javax.management.AttributeNotFoundException;
@@ -32,6 +35,7 @@
3235
import org.springframework.beans.FatalBeanException;
3336
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
3437
import org.springframework.boot.actuate.endpoint.InvocationContext;
38+
import org.springframework.util.ClassUtils;
3539

3640
import static org.assertj.core.api.Assertions.assertThat;
3741
import static org.hamcrest.CoreMatchers.instanceOf;
@@ -126,6 +130,18 @@ public void invokeWhenActionNameIsNotAnOperationShouldThrowException()
126130
bean.invoke("missingOperation", NO_PARAMS, NO_SIGNATURE);
127131
}
128132

133+
@Test
134+
public void invokeShouldInvokeJmxOperationWithBeanClassLoader()
135+
throws ReflectionException, MBeanException {
136+
TestExposableJmxEndpoint endpoint = new TestExposableJmxEndpoint(
137+
new TestJmxOperation((arguments) -> ClassUtils.getDefaultClassLoader()));
138+
URLClassLoader beanClassLoader = new URLClassLoader(new URL[]{}, getClass().getClassLoader());
139+
EndpointMBean bean = new EndpointMBean(this.responseMapper, endpoint);
140+
bean.setBeanClassLoader(beanClassLoader);
141+
Object result = bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE);
142+
assertThat(result).isEqualTo(beanClassLoader);
143+
}
144+
129145
@Test
130146
public void invokeWhenOperationIsInvalidShouldThrowException()
131147
throws MBeanException, ReflectionException {

0 commit comments

Comments
 (0)