diff --git a/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java b/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java index 813bcf0aa38..2cca9ffc784 100644 --- a/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java +++ b/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,11 +46,11 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.CannotLoadBeanClassException; -import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.SmartLifecycle; import org.springframework.core.Constants; import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler; import org.springframework.jmx.export.assembler.MBeanInfoAssembler; @@ -89,6 +89,8 @@ import org.springframework.util.ObjectUtils; * @author Juergen Hoeller * @author Rick Evans * @author Mark Fisher + * @author Marten Deinum + * @author Stephane Nicoll * @since 1.2 * @see #setBeans * @see #setAutodetect @@ -98,7 +100,7 @@ import org.springframework.util.ObjectUtils; * @see MBeanExporterListener */ public class MBeanExporter extends MBeanRegistrationSupport - implements MBeanExportOperations, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { + implements MBeanExportOperations, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, SmartLifecycle { /** * Autodetection mode indicating that no autodetection should be used. @@ -178,6 +180,14 @@ public class MBeanExporter extends MBeanRegistrationSupport /** Stores the BeanFactory for use in autodetection process */ private ListableBeanFactory beanFactory; + private boolean autoStartup = true; + + private volatile boolean running = false; + + private int phase = Integer.MAX_VALUE; + + private final Object lifecycleMonitor = new Object(); + /** * Supply a {@code Map} of beans to be registered with the JMX @@ -395,16 +405,31 @@ public class MBeanExporter extends MBeanRegistrationSupport } } + /** + * Specify the phase in which the MBeans should be exported to the + * JMX domain. The startup order proceeds from lowest to highest, and + * the shutdown order is the reverse of that. By default this value + * is {@code Integer.MAX_VALUE} meaning that MBeans are exported + * as late as possible and removed from the domain as soon as possible. + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * Set whether to automatically export MBeans after initialization. + *

Default is "true"; set this to "false" to allow for manual startup + * through the {@link #start()} method. + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + //--------------------------------------------------------------------- // Lifecycle in bean factory: automatically register/unregister beans //--------------------------------------------------------------------- - /** - * Start bean registration automatically when deployed in an - * {@code ApplicationContext}. - * @see #registerBeans() - */ @Override public void afterPropertiesSet() { // If no server was provided then try to find one. This is useful in an environment @@ -412,28 +437,58 @@ public class MBeanExporter extends MBeanRegistrationSupport if (this.server == null) { this.server = JmxUtils.locateMBeanServer(); } - try { - logger.info("Registering beans for JMX exposure on startup"); - registerBeans(); - registerNotificationListeners(); + } + + @Override + public void start() { + logger.info("Registering beans for JMX exposure"); + synchronized (this.lifecycleMonitor) { + try { + registerBeans(); + registerNotificationListeners(); + } catch (RuntimeException ex) { + // Unregister beans already registered by this exporter. + unregisterNotificationListeners(); + unregisterBeans(); + throw ex; + } } - catch (RuntimeException ex) { - // Unregister beans already registered by this exporter. + running = true; + } + + @Override + public void stop() { + logger.info("Unregistering JMX-exposed beans on stop"); + synchronized (this.lifecycleMonitor) { unregisterNotificationListeners(); unregisterBeans(); - throw ex; + running = false; + } + } + + @Override + public void stop(Runnable callback) { + synchronized (this.lifecycleMonitor) { + stop(); + callback.run(); } } - /** - * Unregisters all beans that this exported has exposed via JMX - * when the enclosing {@code ApplicationContext} is destroyed. - */ @Override - public void destroy() { - logger.info("Unregistering JMX-exposed beans on shutdown"); - unregisterNotificationListeners(); - unregisterBeans(); + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return this.running; + } + } + + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + @Override + public int getPhase() { + return this.phase; } @@ -1054,7 +1109,6 @@ public class MBeanExporter extends MBeanRegistrationSupport } } - //--------------------------------------------------------------------- // Inner classes for internal use //--------------------------------------------------------------------- diff --git a/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java b/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java index ab05a3ea6ed..9676a69adf0 100644 --- a/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java @@ -25,6 +25,7 @@ import org.junit.Before; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.jmx.export.MBeanExporter; import org.springframework.tests.TestGroup; import org.springframework.util.MBeanTestUtils; @@ -91,6 +92,17 @@ public abstract class AbstractMBeanServerTests { return this.server; } + /** + * Start the specified {@link MBeanExporter}. + * + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + * @see org.springframework.context.Lifecycle#start() + */ + protected void start(MBeanExporter exporter) { + exporter.afterPropertiesSet(); + exporter.start(); + } + protected void assertIsRegistered(String message, ObjectName objectName) { assertTrue(message, getServer().isRegistered(objectName)); } diff --git a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java index e1bd521a1df..a249f6a315f 100644 --- a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java @@ -67,7 +67,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { adapter.setServer(getServer()); adapter.setBeans(beans); adapter.setAssembler(new ProxyTestAssembler()); - adapter.afterPropertiesSet(); + start(adapter); } protected MBeanServerConnection getServerConnection() throws Exception { diff --git a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java index c08a9f73b2f..532034b64c4 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,11 +33,17 @@ import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.jmx.AbstractMBeanServerTests; import org.springframework.jmx.IJmxTestBean; @@ -62,10 +68,14 @@ import static org.junit.Assert.*; * @author Mark Fisher * @author Chris Beams * @author Sam Brannen + * @author Stephane Nicoll */ @SuppressWarnings("deprecation") public final class MBeanExporterTests extends AbstractMBeanServerTests { + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private static final String OBJECT_NAME = "spring:test=jmxMBeanAdaptor"; @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -75,12 +85,9 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { // put a non-NotificationListener instance in as a value... listeners.put("*", this); MBeanExporter exporter = new MBeanExporter(); - try { - exporter.setNotificationListenerMappings(listeners); - fail("Must have thrown a ClassCastException when registering a non-NotificationListener instance as a NotificationListener."); - } - catch (ClassCastException expected) { - } + + thrown.expect(ClassCastException.class); + exporter.setNotificationListenerMappings(listeners); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -90,12 +97,9 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { // put null in as a value... listeners.put("*", null); MBeanExporter exporter = new MBeanExporter(); - try { - exporter.setNotificationListenerMappings(listeners); - fail("Must have thrown an IllegalArgumentException when registering a null instance as a NotificationListener."); - } - catch (IllegalArgumentException expected) { - } + + thrown.expect(IllegalArgumentException.class); + exporter.setNotificationListenerMappings(listeners); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -115,8 +119,9 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setNotificationListenerMappings(listeners); try { - exporter.afterPropertiesSet(); - fail("Must have thrown an MBeanExportException when registering a NotificationListener on a non-existent MBean."); + start(exporter); + fail("Must have thrown an MBeanExportException when registering a " + + "NotificationListener on a non-existent MBean."); } catch (MBeanExportException expected) { assertTrue(expected.contains(InstanceNotFoundException.class)); @@ -128,21 +133,15 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { MBeanExporter exporter = new MBeanExporter(); exporter.setBeans(getBeanMap()); exporter.setServer(server); - exporter.afterPropertiesSet(); - assertIsRegistered("The bean was not registered with the MBeanServer", - ObjectNameManager.getInstance(OBJECT_NAME)); - } - - /** Fails if JVM platform MBean server has been started already - @Test - public void testWithLocatedMBeanServer() throws Exception { - MBeanExporter adaptor = new MBeanExporter(); - adaptor.setBeans(getBeanMap()); - adaptor.afterPropertiesSet(); - assertIsRegistered("The bean was not registered with the MBeanServer", ObjectNameManager.getInstance(OBJECT_NAME)); - server.unregisterMBean(new ObjectName(OBJECT_NAME)); + try { + start(exporter); + assertIsRegistered("The bean was not registered with the MBeanServer", + ObjectNameManager.getInstance(OBJECT_NAME)); + } + finally { + exporter.stop(); + } } - */ @Test public void testUserCreatedMBeanRegWithDynamicMBean() throws Exception { @@ -155,20 +154,24 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setBeans(map); exporter.setAssembler(asm); - exporter.afterPropertiesSet(); - Object name = server.getAttribute(ObjectNameManager.getInstance("spring:name=dynBean"), "Name"); - assertEquals("The name attribute is incorrect", "Rob Harrop", name); - assertFalse("Assembler should not have been invoked", asm.invoked); + try { + start(exporter); + Object name = server.getAttribute(ObjectNameManager.getInstance("spring:name=dynBean"), "Name"); + assertEquals("The name attribute is incorrect", "Rob Harrop", name); + assertFalse("Assembler should not have been invoked", asm.invoked); + } + finally { + exporter.stop(); + } } @Test public void testAutodetectMBeans() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); + ConfigurableApplicationContext ctx = load("autodetectMBeans.xml"); try { - bf.getBean("exporter"); - MBeanServer server = (MBeanServer) bf.getBean("server"); + ctx.getBean("exporter"); + MBeanServer server = ctx.getBean("server", MBeanServer.class); ObjectInstance instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean=true")); assertNotNull(instance); instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean2=true")); @@ -176,37 +179,32 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean3=true")); assertNotNull(instance); } finally { - bf.destroySingletons(); + ctx.close(); } } @Test public void testAutodetectWithExclude() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); + ConfigurableApplicationContext ctx = load("autodetectMBeans.xml"); try { - bf.getBean("exporter"); - MBeanServer server = (MBeanServer) bf.getBean("server"); + ctx.getBean("exporter"); + MBeanServer server = ctx.getBean("server", MBeanServer.class); ObjectInstance instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean=true")); assertNotNull(instance); - try { - server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean=false")); - fail("MBean with name spring:mbean=false should have been excluded"); - } catch (InstanceNotFoundException expected) { - } + thrown.expect(InstanceNotFoundException.class); + server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean=false")); } finally { - bf.destroySingletons(); + ctx.close(); } } @Test public void testAutodetectLazyMBeans() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectLazyMBeans.xml", getClass())); + ConfigurableApplicationContext ctx = load("autodetectLazyMBeans.xml"); try { - bf.getBean("exporter"); - MBeanServer server = (MBeanServer) bf.getBean("server"); + ctx.getBean("exporter"); + MBeanServer server = ctx.getBean("server", MBeanServer.class); ObjectName oname = ObjectNameManager.getInstance("spring:mbean=true"); assertNotNull(server.getObjectInstance(oname)); @@ -218,18 +216,41 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { name = (String) server.getAttribute(oname, "Name"); assertEquals("Invalid name returned", "Juergen Hoeller", name); } finally { - bf.destroySingletons(); + ctx.close(); } } @Test public void testAutodetectNoMBeans() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectNoMBeans.xml", getClass())); + ConfigurableApplicationContext ctx = load("autodetectNoMBeans.xml"); try { - bf.getBean("exporter"); + ctx.getBean("exporter"); } finally { - bf.destroySingletons(); + ctx.close(); + } + } + + @Test + public void testAutoStartupToFalse() throws Exception { + ConfigurableApplicationContext ctx = load("autodetectNoAutoStartup.xml"); + try { + MBeanExporter exporter = ctx.getBean("exporter", MBeanExporter.class); + MBeanServer server = ctx.getBean("server", MBeanServer.class); + + ObjectName on = ObjectNameManager.getInstance("spring:mbean=true"); + try { + server.getObjectInstance(on); + fail("MBeans should not have been exported with autoStartup set to false"); + } + catch (InstanceNotFoundException e) { + // expected + } + + // Export manually + exporter.start(); + assertNotNull(server.getObjectInstance(on)); // Should be exposed now. + } finally { + ctx.close(); } } @@ -242,13 +263,14 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(getBeanMap()); exporter.setServer(server); exporter.setListeners(new MBeanExporterListener[] { listener1, listener2 }); - exporter.afterPropertiesSet(); - exporter.destroy(); + start(exporter); + exporter.stop(); assertListener(listener1); assertListener(listener2); } + @Test public void testExportJdkProxy() throws Exception { JmxTestBean bean = new JmxTestBean(); @@ -288,7 +310,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setBeans(beans); - exporter.afterPropertiesSet(); + start(exporter); ObjectInstance instance = server.getObjectInstance(objectName); assertNotNull(instance); @@ -317,7 +339,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setRegistrationBehavior(MBeanExporter.REGISTRATION_IGNORE_EXISTING); - exporter.afterPropertiesSet(); + start(exporter); ObjectInstance instance = server.getObjectInstance(objectName); assertNotNull(instance); @@ -348,7 +370,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setRegistrationPolicy(RegistrationPolicy.REPLACE_EXISTING); - exporter.afterPropertiesSet(); + start(exporter); ObjectInstance instance = server.getObjectInstance(objectName); assertNotNull(instance); @@ -373,12 +395,12 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setServer(getServer()); exporter.setBeans(beans); exporter.setExposeManagedResourceClassLoader(true); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Bean instance not registered", objectName); - Object result = server.invoke(objectName, "add", new Object[] { new Integer(2), new Integer(3) }, new String[] { - int.class.getName(), int.class.getName() }); + Object result = server.invoke(objectName, "add", new Object[] {new Integer(2), new Integer(3)}, new String[] { + int.class.getName(), int.class.getName()}); assertEquals("Incorrect result return from add", result, new Integer(5)); assertEquals("Incorrect attribute value", name, server.getAttribute(objectName, "Name")); @@ -403,7 +425,8 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_NONE); // MBean has a bad ObjectName, so if said MBean is autodetected, an exception will be thrown... - exporter.afterPropertiesSet(); + start(exporter); + } @Test @@ -419,7 +442,8 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName)); exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_MBEAN); - exporter.afterPropertiesSet(); + start(exporter); + assertIsRegistered("Bona fide MBean not autodetected in AUTODETECT_MBEAN mode", ObjectNameManager.getInstance(OBJECT_NAME)); assertIsNotRegistered("Bean autodetected and (only) AUTODETECT_MBEAN mode is on", @@ -441,7 +465,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName)); exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Bona fide MBean not autodetected in (AUTODETECT_ALL) mode", ObjectNameManager.getInstance(OBJECT_NAME)); assertIsRegistered("Bean not autodetected in (AUTODETECT_ALL) mode", @@ -463,7 +487,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(exportedBeanName)); exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ASSEMBLER); - exporter.afterPropertiesSet(); + start(exporter); assertIsNotRegistered("Bona fide MBean was autodetected in AUTODETECT_ASSEMBLER mode - must not have been", ObjectNameManager.getInstance(OBJECT_NAME)); assertIsRegistered("Bean not autodetected in AUTODETECT_ASSEMBLER mode", @@ -487,101 +511,69 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(OBJECT_NAME)); exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ASSEMBLER); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Explicitly exported bona fide MBean obviously not exported.", ObjectNameManager.getInstance(OBJECT_NAME)); } @Test - public void testSetAutodetectModeToOutOfRangeNegativeValue() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - exporter.setAutodetectMode(-1); - fail("Must have failed when supplying an invalid negative out-of-range autodetect mode"); - } - catch (IllegalArgumentException expected) { - } + public void testSetAutodetectModeToOutOfRangeNegativeValue() { + MBeanExporter exporter = new MBeanExporter(); + thrown.expect(IllegalArgumentException.class); + exporter.setAutodetectMode(-1); } @Test - public void testSetAutodetectModeToOutOfRangePositiveValue() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - exporter.setAutodetectMode(5); - fail("Must have failed when supplying an invalid positive out-of-range autodetect mode"); - } - catch (IllegalArgumentException expected) { - } + public void testSetAutodetectModeToOutOfRangePositiveValue() { + MBeanExporter exporter = new MBeanExporter(); + thrown.expect(IllegalArgumentException.class); + exporter.setAutodetectMode(5); } @Test - public void testSetAutodetectModeNameToNull() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - exporter.setAutodetectModeName(null); - fail("Must have failed when supplying a null autodetect mode name"); - } - catch (IllegalArgumentException expected) { - } + public void testSetAutodetectModeNameToNull() { + MBeanExporter exporter = new MBeanExporter(); + thrown.expect(IllegalArgumentException.class); + exporter.setAutodetectModeName(null); } @Test - public void testSetAutodetectModeNameToAnEmptyString() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - exporter.setAutodetectModeName(""); - fail("Must have failed when supplying an empty autodetect mode name"); - } - catch (IllegalArgumentException expected) { - } + public void testSetAutodetectModeNameToAnEmptyString() { + MBeanExporter exporter = new MBeanExporter(); + thrown.expect(IllegalArgumentException.class); + exporter.setAutodetectModeName(""); } @Test - public void testSetAutodetectModeNameToAWhitespacedString() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - exporter.setAutodetectModeName(" \t"); - fail("Must have failed when supplying a whitespace-only autodetect mode name"); - } - catch (IllegalArgumentException expected) { - } + public void testSetAutodetectModeNameToAWhitespacedString() { + MBeanExporter exporter = new MBeanExporter(); + thrown.expect(IllegalArgumentException.class); + exporter.setAutodetectModeName(" \t"); } @Test - public void testSetAutodetectModeNameToARubbishValue() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - exporter.setAutodetectModeName("That Hansel is... *sssooo* hot right now!"); - fail("Must have failed when supplying a whitespace-only autodetect mode name"); - } - catch (IllegalArgumentException expected) { - } + public void testSetAutodetectModeNameToARubbishValue() { + MBeanExporter exporter = new MBeanExporter(); + thrown.expect(IllegalArgumentException.class); + exporter.setAutodetectModeName("That Hansel is... *sssooo* hot right now!"); } @Test public void testNotRunningInBeanFactoryAndPassedBeanNameToExport() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - Map beans = new HashMap(); - beans.put(OBJECT_NAME, "beanName"); - exporter.setBeans(beans); - exporter.afterPropertiesSet(); - fail("Expecting exception because MBeanExporter is not running in a BeanFactory and was passed bean name to (lookup and then) export"); - } - catch (MBeanExportException expected) { - } + MBeanExporter exporter = new MBeanExporter(); + Map beans = new HashMap(); + beans.put(OBJECT_NAME, "beanName"); + exporter.setBeans(beans); + thrown.expect(MBeanExportException.class); + start(exporter); } @Test public void testNotRunningInBeanFactoryAndAutodetectionIsOn() throws Exception { - try { - MBeanExporter exporter = new MBeanExporter(); - exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); - exporter.afterPropertiesSet(); - fail("Expecting exception because MBeanExporter is not running in a BeanFactory and was configured to autodetect beans"); - } - catch (MBeanExportException expected) { - } + MBeanExporter exporter = new MBeanExporter(); + exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); + thrown.expect(MBeanExportException.class); + start(exporter); } /** @@ -594,12 +586,12 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setServer(this.server); MockMBeanExporterListener listener = new MockMBeanExporterListener(); exporter.setListeners(new MBeanExporterListener[] { listener }); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("The bean was not registered with the MBeanServer", ObjectNameManager.getInstance(OBJECT_NAME)); this.server.unregisterMBean(new ObjectName(OBJECT_NAME)); - exporter.destroy(); + exporter.stop(); assertEquals("Listener should not have been invoked (MBean previously unregistered by external agent)", 0, listener.getUnregistered().size()); } @@ -626,7 +618,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { StubNotificationListener listener = new StubNotificationListener(); exporter.setNotificationListenerMappings(Collections.singletonMap(beanName, listener)); - exporter.afterPropertiesSet(); + start(exporter); } @Test @@ -648,7 +640,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { StubNotificationListener listener = new StubNotificationListener(); exporter.setNotificationListenerMappings(Collections.singletonMap("*", listener)); - exporter.afterPropertiesSet(); + start(exporter); } /* @@ -676,7 +668,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeanFactory(factory); try { - exporter.afterPropertiesSet(); + start(exporter); fail("Must have failed during creation of RuntimeExceptionThrowingConstructorBean"); } catch (RuntimeException expected) { @@ -688,6 +680,10 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { ObjectNameManager.getInstance(objectName2)); } + private ConfigurableApplicationContext load(String context) { + return new ClassPathXmlApplicationContext(context, getClass()); + } + private Map getBeanMap() { Map map = new HashMap(); map.put(OBJECT_NAME, new JmxTestBean()); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java index a6423dcee8d..86edb16bf06 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java @@ -61,7 +61,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setBeans(beans); exporter.setNotificationListenerMappings(notificationListeners); - exporter.afterPropertiesSet(); + start(exporter); // update the attribute String attributeName = "Name"; @@ -87,7 +87,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setBeans(beans); exporter.setNotificationListenerMappings(notificationListeners); - exporter.afterPropertiesSet(); + start(exporter); // update the attribute String attributeName = "Name"; @@ -115,7 +115,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setBeans(beans); exporter.setNotificationListeners(new NotificationListenerBean[] { listenerBean }); - exporter.afterPropertiesSet(); + start(exporter); // update the attribute String attributeName = "Name"; @@ -143,7 +143,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setBeans(beans); exporter.setNotificationListeners(new NotificationListenerBean[] { listenerBean }); - exporter.afterPropertiesSet(); + start(exporter); // update the attribute String attributeName = "Name"; @@ -181,7 +181,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setBeans(beans); exporter.setNotificationListeners(new NotificationListenerBean[] { listenerBean }); - exporter.afterPropertiesSet(); + start(exporter); // update the attributes String nameAttribute = "Name"; @@ -227,7 +227,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setNotificationListenerMappings(listenerMappings); exporter.setBeanFactory(factory); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Should have registered MBean", objectName); server.setAttribute(objectName, new Attribute("Age", new Integer(77))); @@ -258,7 +258,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setNotificationListenerMappings(listenerMappings); exporter.setBeanFactory(factory); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Should have registered MBean", objectName); server.setAttribute(objectName, new Attribute("Age", new Integer(77))); @@ -290,7 +290,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setNotificationListenerMappings(listenerMappings); exporter.setBeanFactory(factory); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Should have registered MBean", objectName); server.setAttribute(objectName, new Attribute("Age", new Integer(77))); @@ -322,7 +322,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setNotificationListenerMappings(listenerMappings); exporter.setBeanFactory(factory); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Should have registered MBean", objectName); server.setAttribute(objectName, new Attribute("Age", new Integer(77))); @@ -362,7 +362,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setNotificationListenerMappings(listenerMappings); exporter.setBeanFactory(factory); - exporter.afterPropertiesSet(); + start(exporter); assertIsRegistered("Should have registered MBean", objectName1); assertIsRegistered("Should have registered MBean", objectName2); @@ -384,7 +384,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { MBeanExporter exporter = new MBeanExporter(); exporter.setServer(server); exporter.setBeans(beans); - exporter.afterPropertiesSet(); + start(exporter); CountingAttributeChangeNotificationListener listener = new CountingAttributeChangeNotificationListener(); @@ -420,7 +420,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { MBeanExporter exporter = new MBeanExporter(); exporter.setServer(server); exporter.setBeans(beans); - exporter.afterPropertiesSet(); + start(exporter); CountingAttributeChangeNotificationListener listener = new CountingAttributeChangeNotificationListener(); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java index 0c3ece78395..06424ac7b34 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java @@ -175,7 +175,7 @@ public abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble Map beans = new HashMap(); beans.put(objectName, proxy); exporter.setBeans(beans); - exporter.afterPropertiesSet(); + start(exporter); MBeanInfo inf = getServer().getMBeanInfo(ObjectNameManager.getInstance(objectName)); assertEquals("Incorrect number of operations", getExpectedOperationCount(), inf.getOperations().length); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/autodetectLazyMBeans.xml b/spring-context/src/test/java/org/springframework/jmx/export/autodetectLazyMBeans.xml index 960a57b12cb..009109490dd 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/autodetectLazyMBeans.xml +++ b/spring-context/src/test/java/org/springframework/jmx/export/autodetectLazyMBeans.xml @@ -17,10 +17,4 @@ - - - - - - diff --git a/spring-context/src/test/java/org/springframework/jmx/export/autodetectMBeans.xml b/spring-context/src/test/java/org/springframework/jmx/export/autodetectMBeans.xml index d028b5274c0..ccda368e1b1 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/autodetectMBeans.xml +++ b/spring-context/src/test/java/org/springframework/jmx/export/autodetectMBeans.xml @@ -17,12 +17,6 @@ - - - - - - diff --git a/spring-context/src/test/resources/org/springframework/jmx/export/autodetectNoAutoStartup.xml b/spring-context/src/test/resources/org/springframework/jmx/export/autodetectNoAutoStartup.xml new file mode 100644 index 00000000000..d385c07c485 --- /dev/null +++ b/spring-context/src/test/resources/org/springframework/jmx/export/autodetectNoAutoStartup.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 84eeb27e889..afcdf5eced2 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -41809,6 +41809,13 @@ With this configuration the `testBean` bean is exposed as an MBean under the are exposed as attributes and all __public__ methods (bar those inherited from the `Object` class) are exposed as operations. +[NOTE] +==== +`MBeanExporter` is a `Lifecycle` bean (see <>) +and MBeans are exported as late as possible during the application lifecycle by default. It +is possible to configure the `phase` at which the export happens or disable automatic +registration by setting the `autoStartup` flag. +==== [[jmx-exporting-mbeanserver]]