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 813bcf0aa3..8aef23614c 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. @@ -51,6 +51,7 @@ 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; @@ -67,6 +68,7 @@ import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; + /** * JMX exporter that allows for exposing any Spring-managed bean to a * JMX {@link javax.management.MBeanServer}, without the need to define any @@ -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, DisposableBean, 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 @@ -412,17 +422,6 @@ 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(); - } - catch (RuntimeException ex) { - // Unregister beans already registered by this exporter. - unregisterNotificationListeners(); - unregisterBeans(); - throw ex; - } } /** @@ -1054,6 +1053,78 @@ public class MBeanExporter extends MBeanRegistrationSupport } } + /** + * Set whether to automatically start the container 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; + } + + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + @Override + public void stop(Runnable callback) { + synchronized (this.lifecycleMonitor) { + stop(); + callback.run(); + } + } + + @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; + } + } + running = true; + } + + @Override + public void stop() { + logger.info("Unregistering JMX-exposed beans on stop"); + synchronized (this.lifecycleMonitor) { + unregisterNotificationListeners(); + unregisterBeans(); + running = false; + } + } + + @Override + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return this.running; + } + } + + /** + * Specify the phase in which this container should be started and + * stopped. The startup order proceeds from lowest to highest, and + * the shutdown order is the reverse of that. By default this value + * is Integer.MAX_VALUE meaning that this container starts as late + * as possible and stops as soon as possible. + */ + public void setPhase(int phase) { + this.phase = phase; + } + + @Override + public int getPhase() { + return this.phase; + } + //--------------------------------------------------------------------- // Inner classes for internal use 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 c08a9f73b2..808eedba81 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. @@ -38,6 +38,7 @@ 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.support.GenericApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.jmx.AbstractMBeanServerTests; import org.springframework.jmx.IJmxTestBean; @@ -116,6 +117,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setNotificationListenerMappings(listeners); try { exporter.afterPropertiesSet(); + exporter.start(); fail("Must have thrown an MBeanExportException when registering a NotificationListener on a non-existent MBean."); } catch (MBeanExportException expected) { @@ -129,6 +131,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(getBeanMap()); exporter.setServer(server); exporter.afterPropertiesSet(); + exporter.start(); assertIsRegistered("The bean was not registered with the MBeanServer", ObjectNameManager.getInstance(OBJECT_NAME)); } @@ -156,6 +159,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(map); exporter.setAssembler(asm); exporter.afterPropertiesSet(); + exporter.start(); Object name = server.getAttribute(ObjectNameManager.getInstance("spring:name=dynBean"), "Name"); assertEquals("The name attribute is incorrect", "Rob Harrop", name); @@ -164,11 +168,12 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { @Test public void testAutodetectMBeans() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); + GenericApplicationContext ctx = new GenericApplicationContext(); + new XmlBeanDefinitionReader(ctx).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); + ctx.refresh(); try { - bf.getBean("exporter"); - MBeanServer server = (MBeanServer) bf.getBean("server"); + ctx.getBean("exporter"); + MBeanServer server = (MBeanServer) ctx.getBean("server"); ObjectInstance instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean=true")); assertNotNull(instance); instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean2=true")); @@ -176,17 +181,18 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean3=true")); assertNotNull(instance); } finally { - bf.destroySingletons(); + ctx.destroy(); } } @Test public void testAutodetectWithExclude() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); + GenericApplicationContext ctx = new GenericApplicationContext(); + new XmlBeanDefinitionReader(ctx).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); + ctx.refresh(); try { - bf.getBean("exporter"); - MBeanServer server = (MBeanServer) bf.getBean("server"); + ctx.getBean("exporter"); + MBeanServer server = (MBeanServer) ctx.getBean("server"); ObjectInstance instance = server.getObjectInstance(ObjectNameManager.getInstance("spring:mbean=true")); assertNotNull(instance); @@ -196,17 +202,18 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } catch (InstanceNotFoundException expected) { } } finally { - bf.destroySingletons(); + ctx.destroy(); } } @Test public void testAutodetectLazyMBeans() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectLazyMBeans.xml", getClass())); + GenericApplicationContext ctx = new GenericApplicationContext(); + new XmlBeanDefinitionReader(ctx).loadBeanDefinitions(new ClassPathResource("autodetectLazyMBeans.xml", getClass())); + ctx.refresh(); try { - bf.getBean("exporter"); - MBeanServer server = (MBeanServer) bf.getBean("server"); + ctx.getBean("exporter"); + MBeanServer server = (MBeanServer) ctx.getBean("server"); ObjectName oname = ObjectNameManager.getInstance("spring:mbean=true"); assertNotNull(server.getObjectInstance(oname)); @@ -218,18 +225,19 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { name = (String) server.getAttribute(oname, "Name"); assertEquals("Invalid name returned", "Juergen Hoeller", name); } finally { - bf.destroySingletons(); + ctx.destroy(); } } @Test public void testAutodetectNoMBeans() throws Exception { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectNoMBeans.xml", getClass())); + GenericApplicationContext ctx = new GenericApplicationContext(); + new XmlBeanDefinitionReader(ctx).loadBeanDefinitions(new ClassPathResource("autodetectNoMBeans.xml", getClass())); + ctx.refresh(); try { - bf.getBean("exporter"); + ctx.getBean("exporter"); } finally { - bf.destroySingletons(); + ctx.destroy(); } } @@ -243,6 +251,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setServer(server); exporter.setListeners(new MBeanExporterListener[] { listener1, listener2 }); exporter.afterPropertiesSet(); + exporter.start(); exporter.destroy(); assertListener(listener1); @@ -289,6 +298,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.afterPropertiesSet(); + exporter.start(); ObjectInstance instance = server.getObjectInstance(objectName); assertNotNull(instance); @@ -318,6 +328,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setRegistrationBehavior(MBeanExporter.REGISTRATION_IGNORE_EXISTING); exporter.afterPropertiesSet(); + exporter.start(); ObjectInstance instance = server.getObjectInstance(objectName); assertNotNull(instance); @@ -349,6 +360,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setRegistrationPolicy(RegistrationPolicy.REPLACE_EXISTING); exporter.afterPropertiesSet(); + exporter.start(); ObjectInstance instance = server.getObjectInstance(objectName); assertNotNull(instance); @@ -374,6 +386,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(beans); exporter.setExposeManagedResourceClassLoader(true); exporter.afterPropertiesSet(); + exporter.start(); assertIsRegistered("Bean instance not registered", objectName); @@ -404,6 +417,8 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setAutodetectMode(MBeanExporter.AUTODETECT_NONE); // MBean has a bad ObjectName, so if said MBean is autodetected, an exception will be thrown... exporter.afterPropertiesSet(); + exporter.start(); + } @Test @@ -420,6 +435,8 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_MBEAN); exporter.afterPropertiesSet(); + exporter.start(); + assertIsRegistered("Bona fide MBean not autodetected in AUTODETECT_MBEAN mode", ObjectNameManager.getInstance(OBJECT_NAME)); assertIsNotRegistered("Bean autodetected and (only) AUTODETECT_MBEAN mode is on", @@ -442,6 +459,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.afterPropertiesSet(); + exporter.start(); assertIsRegistered("Bona fide MBean not autodetected in (AUTODETECT_ALL) mode", ObjectNameManager.getInstance(OBJECT_NAME)); assertIsRegistered("Bean not autodetected in (AUTODETECT_ALL) mode", @@ -464,6 +482,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ASSEMBLER); exporter.afterPropertiesSet(); + exporter.start(); 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", @@ -488,6 +507,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeanFactory(factory); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ASSEMBLER); exporter.afterPropertiesSet(); + exporter.start(); assertIsRegistered("Explicitly exported bona fide MBean obviously not exported.", ObjectNameManager.getInstance(OBJECT_NAME)); } @@ -566,6 +586,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { beans.put(OBJECT_NAME, "beanName"); exporter.setBeans(beans); exporter.afterPropertiesSet(); + exporter.start(); fail("Expecting exception because MBeanExporter is not running in a BeanFactory and was passed bean name to (lookup and then) export"); } catch (MBeanExportException expected) { @@ -578,6 +599,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { MBeanExporter exporter = new MBeanExporter(); exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.afterPropertiesSet(); + exporter.start(); fail("Expecting exception because MBeanExporter is not running in a BeanFactory and was configured to autodetect beans"); } catch (MBeanExportException expected) { @@ -595,6 +617,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { MockMBeanExporterListener listener = new MockMBeanExporterListener(); exporter.setListeners(new MBeanExporterListener[] { listener }); exporter.afterPropertiesSet(); + exporter.start(); assertIsRegistered("The bean was not registered with the MBeanServer", ObjectNameManager.getInstance(OBJECT_NAME)); @@ -677,6 +700,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { try { exporter.afterPropertiesSet(); + exporter.start(); fail("Must have failed during creation of RuntimeExceptionThrowingConstructorBean"); } catch (RuntimeException expected) {