Merge branch '6.0.x'

This commit is contained in:
Sam Brannen 2023-07-10 19:07:09 +02:00
commit 7bc731dfa6
2 changed files with 191 additions and 139 deletions

View File

@ -53,7 +53,6 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Constants;
import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler; import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler;
import org.springframework.jmx.export.assembler.MBeanInfoAssembler; import org.springframework.jmx.export.assembler.MBeanInfoAssembler;
import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler; import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler;
@ -92,6 +91,7 @@ import org.springframework.util.ObjectUtils;
* @author Rick Evans * @author Rick Evans
* @author Mark Fisher * @author Mark Fisher
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
* @since 1.2 * @since 1.2
* @see #setBeans * @see #setBeans
* @see #setAutodetect * @see #setAutodetect
@ -134,13 +134,19 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
/** Constant for the JMX {@code mr_type} "ObjectReference". */ /** Constant for the JMX {@code mr_type} "ObjectReference". */
private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference"; private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference";
/** Prefix for the autodetect constants defined in this class. */ /**
private static final String CONSTANT_PREFIX_AUTODETECT = "AUTODETECT_"; * Map of constant names to constant values for the autodetect constants defined
* in this class.
* @since 6.0.11
*/
private static final Map<String, Integer> constants = Map.of(
"AUTODETECT_NONE", AUTODETECT_NONE,
"AUTODETECT_MBEAN", AUTODETECT_MBEAN,
"AUTODETECT_ASSEMBLER", AUTODETECT_ASSEMBLER,
"AUTODETECT_ALL", AUTODETECT_ALL
);
/** Constants instance for this class. */
private static final Constants constants = new Constants(MBeanExporter.class);
/** The beans to be exposed as JMX managed resources, with JMX names as keys. */ /** The beans to be exposed as JMX managed resources, with JMX names as keys. */
@Nullable @Nullable
private Map<String, Object> beans; private Map<String, Object> beans;
@ -222,38 +228,52 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
this.autodetectMode = (autodetect ? AUTODETECT_ALL : AUTODETECT_NONE); this.autodetectMode = (autodetect ? AUTODETECT_ALL : AUTODETECT_NONE);
} }
/**
* Set the autodetection mode to use.
* @throws IllegalArgumentException if the supplied value is not
* one of the {@code AUTODETECT_} constants
* @see #setAutodetectModeName(String)
* @see #AUTODETECT_ALL
* @see #AUTODETECT_ASSEMBLER
* @see #AUTODETECT_MBEAN
* @see #AUTODETECT_NONE
*/
public void setAutodetectMode(int autodetectMode) {
if (!constants.getValues(CONSTANT_PREFIX_AUTODETECT).contains(autodetectMode)) {
throw new IllegalArgumentException("Only values of autodetect constants allowed");
}
this.autodetectMode = autodetectMode;
}
/** /**
* Set the autodetection mode to use by name. * Set the autodetection mode to use by name.
* @throws IllegalArgumentException if the supplied value is not resolvable * @throws IllegalArgumentException if the supplied value is not resolvable
* to one of the {@code AUTODETECT_} constants or is {@code null} * to one of the {@code AUTODETECT_} constants or is {@code null}
* @see #setAutodetectMode(int) * @see #setAutodetectMode(int)
* @see #getAutodetectMode()
* @see #AUTODETECT_ALL * @see #AUTODETECT_ALL
* @see #AUTODETECT_ASSEMBLER * @see #AUTODETECT_ASSEMBLER
* @see #AUTODETECT_MBEAN * @see #AUTODETECT_MBEAN
* @see #AUTODETECT_NONE * @see #AUTODETECT_NONE
*/ */
public void setAutodetectModeName(String constantName) { public void setAutodetectModeName(String constantName) {
if (!constantName.startsWith(CONSTANT_PREFIX_AUTODETECT)) { Assert.hasText(constantName, "'constantName' must not be null or blank");
throw new IllegalArgumentException("Only autodetect constants allowed"); Integer mode = constants.get(constantName);
} Assert.notNull(mode, "Only autodetect constants allowed");
this.autodetectMode = (Integer) constants.asNumber(constantName); this.autodetectMode = mode;
}
/**
* Set the autodetection mode to use.
* @throws IllegalArgumentException if the supplied value is not
* one of the {@code AUTODETECT_} constants
* @see #setAutodetectModeName(String)
* @see #getAutodetectMode()
* @see #AUTODETECT_ALL
* @see #AUTODETECT_ASSEMBLER
* @see #AUTODETECT_MBEAN
* @see #AUTODETECT_NONE
*/
public void setAutodetectMode(int autodetectMode) {
Assert.isTrue(constants.containsValue(autodetectMode),
"Only values of autodetect constants allowed");
this.autodetectMode = autodetectMode;
}
/**
* Get the autodetect mode to use for this {@code MBeanExporter}.
* @return the configured autodetect mode, or {@code null} if not explicitly
* configured
* @since 6.0.11
* @see #setAutodetectModeName(String)
* @see #setAutodetectMode(int)
*/
@Nullable
public Integer getAutodetectMode() {
return this.autodetectMode;
} }
/** /**

View File

@ -16,6 +16,7 @@
package org.springframework.jmx.export; package org.springframework.jmx.export;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -23,6 +24,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream;
import javax.management.Attribute; import javax.management.Attribute;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
@ -55,14 +57,16 @@ import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembl
import org.springframework.jmx.export.naming.SelfNaming; import org.springframework.jmx.export.naming.SelfNaming;
import org.springframework.jmx.support.ObjectNameManager; import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.jmx.support.RegistrationPolicy; import org.springframework.jmx.support.RegistrationPolicy;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatRuntimeException; import static org.assertj.core.api.Assertions.assertThatRuntimeException;
/** /**
* Integration tests for the {@link MBeanExporter} class. * Integration tests for {@link MBeanExporter}.
* *
* @author Rob Harrop * @author Rob Harrop
* @author Juergen Hoeller * @author Juergen Hoeller
@ -76,9 +80,11 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
private static final String OBJECT_NAME = "spring:test=jmxMBeanAdaptor"; private static final String OBJECT_NAME = "spring:test=jmxMBeanAdaptor";
private final MBeanExporter exporter = new MBeanExporter();
@Test @Test
void testRegisterNullNotificationListenerType() throws Exception { void registerNullNotificationListenerType() throws Exception {
Map<String, NotificationListener> listeners = new HashMap<>(); Map<String, NotificationListener> listeners = new HashMap<>();
// put null in as a value... // put null in as a value...
listeners.put("*", null); listeners.put("*", null);
@ -89,14 +95,12 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testRegisterNotificationListenerForNonExistentMBean() throws Exception { void registerNotificationListenerForNonExistentMBean() throws Exception {
Map<String, NotificationListener> listeners = new HashMap<>();
NotificationListener dummyListener = (notification, handback) -> { NotificationListener dummyListener = (notification, handback) -> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
}; };
// the MBean with the supplied object name does not exist... // the MBean with the supplied object name does not exist...
listeners.put("spring:type=Test", dummyListener); Map<String, NotificationListener> listeners = Map.of("spring:type=Test", dummyListener);
MBeanExporter exporter = new MBeanExporter();
exporter.setBeans(getBeanMap()); exporter.setBeans(getBeanMap());
exporter.setServer(server); exporter.setServer(server);
exporter.setNotificationListenerMappings(listeners); exporter.setNotificationListenerMappings(listeners);
@ -107,8 +111,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testWithSuppliedMBeanServer() throws Exception { void withSuppliedMBeanServer() throws Exception {
MBeanExporter exporter = new MBeanExporter();
exporter.setBeans(getBeanMap()); exporter.setBeans(getBeanMap());
exporter.setServer(server); exporter.setServer(server);
try { try {
@ -122,13 +125,11 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testUserCreatedMBeanRegWithDynamicMBean() throws Exception { void userCreatedMBeanRegWithDynamicMBean() throws Exception {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = Map.of("spring:name=dynBean", new TestDynamicMBean());
map.put("spring:name=dynBean", new TestDynamicMBean());
InvokeDetectAssembler asm = new InvokeDetectAssembler(); InvokeDetectAssembler asm = new InvokeDetectAssembler();
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(server); exporter.setServer(server);
exporter.setBeans(map); exporter.setBeans(map);
exporter.setAssembler(asm); exporter.setAssembler(asm);
@ -145,7 +146,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testAutodetectMBeans() throws Exception { void autodetectMBeans() throws Exception {
try (ConfigurableApplicationContext ctx = load("autodetectMBeans.xml")) { try (ConfigurableApplicationContext ctx = load("autodetectMBeans.xml")) {
ctx.getBean("exporter"); ctx.getBean("exporter");
MBeanServer server = ctx.getBean("server", MBeanServer.class); MBeanServer server = ctx.getBean("server", MBeanServer.class);
@ -159,7 +160,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testAutodetectWithExclude() throws Exception { void autodetectWithExclude() throws Exception {
try (ConfigurableApplicationContext ctx = load("autodetectMBeans.xml")) { try (ConfigurableApplicationContext ctx = load("autodetectMBeans.xml")) {
ctx.getBean("exporter"); ctx.getBean("exporter");
MBeanServer server = ctx.getBean("server", MBeanServer.class); MBeanServer server = ctx.getBean("server", MBeanServer.class);
@ -172,7 +173,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testAutodetectLazyMBeans() throws Exception { void autodetectLazyMBeans() throws Exception {
try (ConfigurableApplicationContext ctx = load("autodetectLazyMBeans.xml")) { try (ConfigurableApplicationContext ctx = load("autodetectLazyMBeans.xml")) {
ctx.getBean("exporter"); ctx.getBean("exporter");
MBeanServer server = ctx.getBean("server", MBeanServer.class); MBeanServer server = ctx.getBean("server", MBeanServer.class);
@ -190,18 +191,17 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testAutodetectNoMBeans() throws Exception { void autodetectNoMBeans() throws Exception {
try (ConfigurableApplicationContext ctx = load("autodetectNoMBeans.xml")) { try (ConfigurableApplicationContext ctx = load("autodetectNoMBeans.xml")) {
ctx.getBean("exporter"); ctx.getBean("exporter");
} }
} }
@Test @Test
void testWithMBeanExporterListeners() throws Exception { void withMBeanExporterListeners() throws Exception {
MockMBeanExporterListener listener1 = new MockMBeanExporterListener(); MockMBeanExporterListener listener1 = new MockMBeanExporterListener();
MockMBeanExporterListener listener2 = new MockMBeanExporterListener(); MockMBeanExporterListener listener2 = new MockMBeanExporterListener();
MBeanExporter exporter = new MBeanExporter();
exporter.setBeans(getBeanMap()); exporter.setBeans(getBeanMap());
exporter.setServer(server); exporter.setServer(server);
exporter.setListeners(listener1, listener2); exporter.setListeners(listener1, listener2);
@ -213,7 +213,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testExportJdkProxy() throws Exception { void exportJdkProxy() throws Exception {
JmxTestBean bean = new JmxTestBean(); JmxTestBean bean = new JmxTestBean();
bean.setName("Rob Harrop"); bean.setName("Rob Harrop");
@ -225,10 +225,8 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
IJmxTestBean proxy = (IJmxTestBean) factory.getProxy(); IJmxTestBean proxy = (IJmxTestBean) factory.getProxy();
String name = "bean:mmm=whatever"; String name = "bean:mmm=whatever";
Map<String, Object> beans = new HashMap<>(); Map<String, Object> beans = Map.of(name, proxy);
beans.put(name, proxy);
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(server); exporter.setServer(server);
exporter.setBeans(beans); exporter.setBeans(beans);
exporter.registerBeans(); exporter.registerBeans();
@ -239,15 +237,13 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testSelfNaming() throws Exception { void selfNaming() throws Exception {
ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME);
SelfNamingTestBean testBean = new SelfNamingTestBean(); SelfNamingTestBean testBean = new SelfNamingTestBean();
testBean.setObjectName(objectName); testBean.setObjectName(objectName);
Map<String, Object> beans = new HashMap<>(); Map<String, Object> beans = Map.of("foo", testBean);
beans.put("foo", testBean);
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(server); exporter.setServer(server);
exporter.setBeans(beans); exporter.setBeans(beans);
@ -258,7 +254,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testRegisterIgnoreExisting() throws Exception { void registerIgnoreExisting() throws Exception {
ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME);
Person preRegistered = new Person(); Person preRegistered = new Person();
@ -271,11 +267,11 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
String objectName2 = "spring:test=equalBean"; String objectName2 = "spring:test=equalBean";
Map<String, Object> beans = new HashMap<>(); Map<String, Object> beans = Map.of(
beans.put(objectName.toString(), springRegistered); objectName.toString(), springRegistered,
beans.put(objectName2, springRegistered); objectName2, springRegistered
);
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(server); exporter.setServer(server);
exporter.setBeans(beans); exporter.setBeans(beans);
exporter.setRegistrationPolicy(RegistrationPolicy.IGNORE_EXISTING); exporter.setRegistrationPolicy(RegistrationPolicy.IGNORE_EXISTING);
@ -292,7 +288,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testRegisterReplaceExisting() throws Exception { void registerReplaceExisting() throws Exception {
ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME);
Person preRegistered = new Person(); Person preRegistered = new Person();
@ -303,10 +299,8 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
Person springRegistered = new Person(); Person springRegistered = new Person();
springRegistered.setName("Sally Greenwood"); springRegistered.setName("Sally Greenwood");
Map<String, Object> beans = new HashMap<>(); Map<String, Object> beans = Map.of(objectName.toString(), springRegistered);
beans.put(objectName.toString(), springRegistered);
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(server); exporter.setServer(server);
exporter.setBeans(beans); exporter.setBeans(beans);
exporter.setRegistrationPolicy(RegistrationPolicy.REPLACE_EXISTING); exporter.setRegistrationPolicy(RegistrationPolicy.REPLACE_EXISTING);
@ -321,7 +315,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testWithExposeClassLoader() throws Exception { void withExposeClassLoader() throws Exception {
String name = "Rob Harrop"; String name = "Rob Harrop";
String otherName = "Juergen Hoeller"; String otherName = "Juergen Hoeller";
@ -329,10 +323,8 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
bean.setName(name); bean.setName(name);
ObjectName objectName = ObjectNameManager.getInstance("spring:type=Test"); ObjectName objectName = ObjectNameManager.getInstance("spring:type=Test");
Map<String, Object> beans = new HashMap<>(); Map<String, Object> beans = Map.of(objectName.toString(), bean);
beans.put(objectName.toString(), bean);
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setBeans(beans); exporter.setBeans(beans);
exporter.setExposeManagedResourceClassLoader(true); exporter.setExposeManagedResourceClassLoader(true);
@ -351,34 +343,30 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testBonaFideMBeanIsNotExportedWhenAutodetectIsTotallyTurnedOff() { void bonaFideMBeanIsNotExportedWhenAutodetectIsTotallyTurnedOff() {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("^&_invalidObjectName_(*", builder.getBeanDefinition()); factory.registerBeanDefinition("^&_invalidObjectName_(*", builder.getBeanDefinition());
String exportedBeanName = "export.me.please"; String exportedBeanName = "export.me.please";
factory.registerSingleton(exportedBeanName, new TestBean()); factory.registerSingleton(exportedBeanName, new TestBean());
MBeanExporter exporter = new MBeanExporter(); Map<String, Object> beansToExport = Map.of(OBJECT_NAME, exportedBeanName);
Map<String, Object> beansToExport = new HashMap<>();
beansToExport.put(OBJECT_NAME, exportedBeanName);
exporter.setBeans(beansToExport); exporter.setBeans(beansToExport);
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_NONE); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_NONE);
// MBean has a bad ObjectName, so if said MBean is autodetected, an exception will be thrown... // MBean has a bad ObjectName, so if said MBean is autodetected, an exception will be thrown...
start(exporter); start(exporter);
} }
@Test @Test
void testOnlyBonaFideMBeanIsExportedWhenAutodetectIsMBeanOnly() throws Exception { void onlyBonaFideMBeanIsExportedWhenAutodetectIsMBeanOnly() throws Exception {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition()); factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition());
String exportedBeanName = "spring:type=TestBean"; String exportedBeanName = "spring:type=TestBean";
factory.registerSingleton(exportedBeanName, new TestBean()); factory.registerSingleton(exportedBeanName, new TestBean());
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName)); exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName));
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
@ -392,7 +380,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testBonaFideMBeanAndRegularBeanExporterWithAutodetectAll() throws Exception { void bonaFideMBeanAndRegularBeanExporterWithAutodetectAll() throws Exception {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition()); factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition());
@ -401,7 +389,6 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
String notToBeExportedBeanName = "spring:type=NotToBeExported"; String notToBeExportedBeanName = "spring:type=NotToBeExported";
factory.registerSingleton(notToBeExportedBeanName, new TestBean()); factory.registerSingleton(notToBeExportedBeanName, new TestBean());
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName)); exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName));
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
@ -416,14 +403,13 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testBonaFideMBeanIsNotExportedWithAutodetectAssembler() throws Exception { void bonaFideMBeanIsNotExportedWithAutodetectAssembler() throws Exception {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition()); factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition());
String exportedBeanName = "spring:type=TestBean"; String exportedBeanName = "spring:type=TestBean";
factory.registerSingleton(exportedBeanName, new TestBean()); factory.registerSingleton(exportedBeanName, new TestBean());
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName)); exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName));
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
@ -439,15 +425,13 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
* Want to ensure that said MBean is not exported twice. * Want to ensure that said MBean is not exported twice.
*/ */
@Test @Test
void testBonaFideMBeanExplicitlyExportedAndAutodetectionIsOn() throws Exception { void bonaFideMBeanExplicitlyExportedAndAutodetectionIsOn() throws Exception {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition()); factory.registerBeanDefinition(OBJECT_NAME, builder.getBeanDefinition());
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
Map<String, Object> beansToExport = new HashMap<>(); Map<String, Object> beansToExport = Map.of(OBJECT_NAME, OBJECT_NAME);
beansToExport.put(OBJECT_NAME, OBJECT_NAME);
exporter.setBeans(beansToExport); exporter.setBeans(beansToExport);
exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(OBJECT_NAME)); exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(OBJECT_NAME));
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
@ -458,61 +442,104 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testSetAutodetectModeToOutOfRangeNegativeValue() { void setAutodetectModeToOutOfRangeNegativeValue() {
MBeanExporter exporter = new MBeanExporter(); assertThatIllegalArgumentException()
assertThatIllegalArgumentException().isThrownBy(() -> .isThrownBy(() -> exporter.setAutodetectMode(-1))
exporter.setAutodetectMode(-1)); .withMessage("Only values of autodetect constants allowed");
assertThat(exporter.getAutodetectMode()).isNull();
} }
@Test @Test
void testSetAutodetectModeToOutOfRangePositiveValue() { void setAutodetectModeToOutOfRangePositiveValue() {
MBeanExporter exporter = new MBeanExporter(); assertThatIllegalArgumentException()
assertThatIllegalArgumentException().isThrownBy(() -> .isThrownBy(() -> exporter.setAutodetectMode(5))
exporter.setAutodetectMode(5)); .withMessage("Only values of autodetect constants allowed");
assertThat(exporter.getAutodetectMode()).isNull();
}
/**
* This test effectively verifies that the internal 'constants' map is properly
* configured for all autodetect constants defined in {@link MBeanExporter}.
*/
@Test
void setAutodetectModeToAllSupportedValues() {
streamConstants(MBeanExporter.class)
.map(MBeanExporterTests::getFieldValue)
.forEach(mode -> assertThatNoException().isThrownBy(() -> exporter.setAutodetectMode(mode)));
} }
@Test @Test
void testSetAutodetectModeNameToAnEmptyString() { void setAutodetectModeToSupportedValue() {
MBeanExporter exporter = new MBeanExporter(); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ASSEMBLER);
assertThatIllegalArgumentException().isThrownBy(() -> assertThat(exporter.getAutodetectMode()).isEqualTo(MBeanExporter.AUTODETECT_ASSEMBLER);
exporter.setAutodetectModeName(""));
} }
@Test @Test
void testSetAutodetectModeNameToAWhitespacedString() { void setAutodetectModeNameToNull() {
MBeanExporter exporter = new MBeanExporter(); assertThatIllegalArgumentException()
assertThatIllegalArgumentException().isThrownBy(() -> .isThrownBy(() -> exporter.setAutodetectModeName(null))
exporter.setAutodetectModeName(" \t")); .withMessage("'constantName' must not be null or blank");
assertThat(exporter.getAutodetectMode()).isNull();
} }
@Test @Test
void testSetAutodetectModeNameToARubbishValue() { void setAutodetectModeNameToAnEmptyString() {
MBeanExporter exporter = new MBeanExporter(); assertThatIllegalArgumentException()
assertThatIllegalArgumentException().isThrownBy(() -> .isThrownBy(() -> exporter.setAutodetectModeName(""))
exporter.setAutodetectModeName("That Hansel is... *sssooo* hot right now!")); .withMessage("'constantName' must not be null or blank");
assertThat(exporter.getAutodetectMode()).isNull();
} }
@Test @Test
void testNotRunningInBeanFactoryAndPassedBeanNameToExport() throws Exception { void setAutodetectModeNameToWhitespace() {
MBeanExporter exporter = new MBeanExporter(); assertThatIllegalArgumentException()
Map<String, Object> beans = new HashMap<>(); .isThrownBy(() -> exporter.setAutodetectModeName(" \t"))
beans.put(OBJECT_NAME, "beanName"); .withMessage("'constantName' must not be null or blank");
assertThat(exporter.getAutodetectMode()).isNull();
}
@Test
void setAutodetectModeNameToBogusValue() {
assertThatIllegalArgumentException()
.isThrownBy(() -> exporter.setAutodetectModeName("Bogus"))
.withMessage("Only autodetect constants allowed");
assertThat(exporter.getAutodetectMode()).isNull();
}
/**
* This test effectively verifies that the internal 'constants' map is properly
* configured for all autodetect constants defined in {@link MBeanExporter}.
*/
@Test
void setAutodetectModeNameToAllSupportedValues() {
streamConstants(MBeanExporter.class)
.map(Field::getName)
.forEach(name -> assertThatNoException().isThrownBy(() -> exporter.setAutodetectModeName(name)));
}
@Test
void setAutodetectModeNameToSupportedValue() {
exporter.setAutodetectModeName("AUTODETECT_ASSEMBLER");
assertThat(exporter.getAutodetectMode()).isEqualTo(MBeanExporter.AUTODETECT_ASSEMBLER);
}
@Test
void notRunningInBeanFactoryAndPassedBeanNameToExport() throws Exception {
Map<String, Object> beans = Map.of(OBJECT_NAME, "beanName");
exporter.setBeans(beans); exporter.setBeans(beans);
assertThatExceptionOfType(MBeanExportException.class).isThrownBy(() -> assertThatExceptionOfType(MBeanExportException.class)
start(exporter)); .isThrownBy(() -> start(exporter));
} }
@Test @Test
void testNotRunningInBeanFactoryAndAutodetectionIsOn() throws Exception { void notRunningInBeanFactoryAndAutodetectionIsOn() throws Exception {
MBeanExporter exporter = new MBeanExporter();
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL);
assertThatExceptionOfType(MBeanExportException.class).isThrownBy(() -> assertThatExceptionOfType(MBeanExportException.class)
start(exporter)); .isThrownBy(() -> start(exporter));
} }
@Test // SPR-2158 @Test // SPR-2158
void testMBeanIsNotUnregisteredSpuriouslyIfSomeExternalProcessHasUnregisteredMBean() throws Exception { void mbeanIsNotUnregisteredSpuriouslyIfSomeExternalProcessHasUnregisteredMBean() throws Exception {
MBeanExporter exporter = new MBeanExporter();
exporter.setBeans(getBeanMap()); exporter.setBeans(getBeanMap());
exporter.setServer(this.server); exporter.setServer(this.server);
MockMBeanExporterListener listener = new MockMBeanExporterListener(); MockMBeanExporterListener listener = new MockMBeanExporterListener();
@ -523,12 +550,13 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
this.server.unregisterMBean(new ObjectName(OBJECT_NAME)); this.server.unregisterMBean(new ObjectName(OBJECT_NAME));
exporter.destroy(); exporter.destroy();
assertThat(listener.getUnregistered()).as("Listener should not have been invoked (MBean previously unregistered by external agent)") assertThat(listener.getUnregistered())
.as("Listener should not have been invoked (MBean previously unregistered by external agent)")
.isEmpty(); .isEmpty();
} }
@Test // SPR-3302 @Test // SPR-3302
void testBeanNameCanBeUsedInNotificationListenersMap() throws Exception { void beanNameCanBeUsedInNotificationListenersMap() throws Exception {
String beanName = "charlesDexterWard"; String beanName = "charlesDexterWard";
BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class);
@ -537,10 +565,8 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
factory.preInstantiateSingletons(); factory.preInstantiateSingletons();
Object testBeanInstance = factory.getBean(beanName); Object testBeanInstance = factory.getBean(beanName);
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
Map<String, Object> beansToExport = new HashMap<>(); Map<String, Object> beansToExport = Map.of("test:what=ever", testBeanInstance);
beansToExport.put("test:what=ever", testBeanInstance);
exporter.setBeans(beansToExport); exporter.setBeans(beansToExport);
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
StubNotificationListener listener = new StubNotificationListener(); StubNotificationListener listener = new StubNotificationListener();
@ -550,7 +576,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testWildcardCanBeUsedInNotificationListenersMap() throws Exception { void wildcardCanBeUsedInNotificationListenersMap() throws Exception {
String beanName = "charlesDexterWard"; String beanName = "charlesDexterWard";
BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class);
@ -559,10 +585,8 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
factory.preInstantiateSingletons(); factory.preInstantiateSingletons();
Object testBeanInstance = factory.getBean(beanName); Object testBeanInstance = factory.getBean(beanName);
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
Map<String, Object> beansToExport = new HashMap<>(); Map<String, Object> beansToExport = Map.of("test:what=ever", testBeanInstance);
beansToExport.put("test:what=ever", testBeanInstance);
exporter.setBeans(beansToExport); exporter.setBeans(beansToExport);
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
StubNotificationListener listener = new StubNotificationListener(); StubNotificationListener listener = new StubNotificationListener();
@ -572,7 +596,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test // SPR-3625 @Test // SPR-3625
void testMBeanIsUnregisteredForRuntimeExceptionDuringInitialization() throws Exception { void mbeanIsUnregisteredForRuntimeExceptionDuringInitialization() throws Exception {
BeanDefinitionBuilder builder1 = BeanDefinitionBuilder.rootBeanDefinition(Person.class); BeanDefinitionBuilder builder1 = BeanDefinitionBuilder.rootBeanDefinition(Person.class);
BeanDefinitionBuilder builder2 = BeanDefinitionBuilder BeanDefinitionBuilder builder2 = BeanDefinitionBuilder
.rootBeanDefinition(RuntimeExceptionThrowingConstructorBean.class); .rootBeanDefinition(RuntimeExceptionThrowingConstructorBean.class);
@ -584,16 +608,16 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
factory.registerBeanDefinition(objectName1, builder1.getBeanDefinition()); factory.registerBeanDefinition(objectName1, builder1.getBeanDefinition());
factory.registerBeanDefinition(objectName2, builder2.getBeanDefinition()); factory.registerBeanDefinition(objectName2, builder2.getBeanDefinition());
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
Map<String, Object> beansToExport = new HashMap<>(); Map<String, Object> beansToExport = Map.of(
beansToExport.put(objectName1, objectName1); objectName1, objectName1,
beansToExport.put(objectName2, objectName2); objectName2, objectName2
);
exporter.setBeans(beansToExport); exporter.setBeans(beansToExport);
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
assertThatRuntimeException().as("failed during creation of RuntimeExceptionThrowingConstructorBean") assertThatRuntimeException().as("failed during creation of RuntimeExceptionThrowingConstructorBean")
.isThrownBy(() -> start(exporter)); .isThrownBy(() -> start(exporter));
assertIsNotRegistered("Must have unregistered all previously registered MBeans due to RuntimeException", assertIsNotRegistered("Must have unregistered all previously registered MBeans due to RuntimeException",
ObjectNameManager.getInstance(objectName1)); ObjectNameManager.getInstance(objectName1));
@ -602,14 +626,13 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testIgnoreBeanName() throws MalformedObjectNameException { void ignoreBeanName() throws MalformedObjectNameException {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
String firstBeanName = "spring:type=TestBean"; String firstBeanName = "spring:type=TestBean";
factory.registerSingleton(firstBeanName, new TestBean("test")); factory.registerSingleton(firstBeanName, new TestBean("test"));
String secondBeanName = "spring:type=TestBean2"; String secondBeanName = "spring:type=TestBean2";
factory.registerSingleton(secondBeanName, new TestBean("test2")); factory.registerSingleton(secondBeanName, new TestBean("test2"));
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(firstBeanName, secondBeanName)); exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(firstBeanName, secondBeanName));
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
@ -624,11 +647,10 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testRegisterFactoryBean() throws MalformedObjectNameException { void registerFactoryBean() throws MalformedObjectNameException {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("spring:type=FactoryBean", new RootBeanDefinition(ProperSomethingFactoryBean.class)); factory.registerBeanDefinition("spring:type=FactoryBean", new RootBeanDefinition(ProperSomethingFactoryBean.class));
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL);
@ -639,11 +661,10 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void testIgnoreNullObjectFromFactoryBean() throws MalformedObjectNameException { void ignoreNullObjectFromFactoryBean() throws MalformedObjectNameException {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("spring:type=FactoryBean", new RootBeanDefinition(NullSomethingFactoryBean.class)); factory.registerBeanDefinition("spring:type=FactoryBean", new RootBeanDefinition(NullSomethingFactoryBean.class));
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL);
@ -658,13 +679,11 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
return new ClassPathXmlApplicationContext(context, getClass()); return new ClassPathXmlApplicationContext(context, getClass());
} }
private Map<String, Object> getBeanMap() { private static Map<String, Object> getBeanMap() {
Map<String, Object> map = new HashMap<>(); return Map.of(OBJECT_NAME, new JmxTestBean());
map.put(OBJECT_NAME, new JmxTestBean());
return map;
} }
private void assertListener(MockMBeanExporterListener listener) throws MalformedObjectNameException { private static void assertListener(MockMBeanExporterListener listener) throws MalformedObjectNameException {
ObjectName desired = ObjectNameManager.getInstance(OBJECT_NAME); ObjectName desired = ObjectNameManager.getInstance(OBJECT_NAME);
assertThat(listener.getRegistered()).as("Incorrect number of registrations").hasSize(1); assertThat(listener.getRegistered()).as("Incorrect number of registrations").hasSize(1);
assertThat(listener.getUnregistered()).as("Incorrect number of unregistrations").hasSize(1); assertThat(listener.getUnregistered()).as("Incorrect number of unregistrations").hasSize(1);
@ -684,6 +703,19 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
} }
private static Stream<Field> streamConstants(Class<?> clazz) {
return Arrays.stream(clazz.getFields()).filter(ReflectionUtils::isPublicStaticFinal);
}
private static Integer getFieldValue(Field field) {
try {
return (Integer) field.get(null);
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static class MockMBeanExporterListener implements MBeanExporterListener { private static class MockMBeanExporterListener implements MBeanExporterListener {