From 2bd6e24b653bd58ea025b87be041ca0b9d8312fb Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 10 Jul 2014 15:48:36 +0200 Subject: [PATCH] MBeanExporter implements newly introduced SmartInitializingSingleton callback interface Issue: SPR-8045 --- .../factory/SmartInitializingSingleton.java | 58 +++++++++++++++++++ .../support/DefaultListableBeanFactory.java | 24 ++++++++ .../jmx/export/MBeanExporter.java | 10 ++-- .../jmx/AbstractMBeanServerTests.java | 3 +- 4 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 spring-beans/src/main/java/org/springframework/beans/factory/SmartInitializingSingleton.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/SmartInitializingSingleton.java b/spring-beans/src/main/java/org/springframework/beans/factory/SmartInitializingSingleton.java new file mode 100644 index 0000000000..787080c789 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/SmartInitializingSingleton.java @@ -0,0 +1,58 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory; + +/** + * Callback interface triggered at the end of the singleton pre-instantiation phase + * during {@link BeanFactory} bootstrap. This interface can be implemented by + * singleton beans in order to perform some initialization after the regular + * singleton instantiation algorithm, avoiding side effects with accidental early + * initialization (e.g. from {@link ListableBeanFactory#getBeansOfType} calls). + * In that sense, it is an alternative to {@link InitializingBean} which gets + * triggered right at the end of a bean's local construction phase. + * + *

This callback variant is somewhat similar to + * {@link org.springframework.context.event.ContextRefreshedEvent} but doesn't + * require an implementation of {@link org.springframework.context.ApplicationListener}, + * with no need to filter context references across a context hierarchy etc. + * It also implies a more minimal dependency on just the {@code beans} package + * and is being honored by standalone {@link ListableBeanFactory} implementations, + * not just in an {@link org.springframework.context.ApplicationContext} environment. + * + *

NOTE: If you intend to start/manage asynchronous tasks, preferably + * implement {@link org.springframework.context.Lifecycle} instead which offers + * a richer model for runtime management and allows for phased startup/shutdown. + * + * @author Juergen Hoeller + * @since 4.1 + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons() + */ +public interface SmartInitializingSingleton { + + /** + * Invoked right at the end of the singleton pre-instantiation phase, + * with a guarantee that all regular singleton beans have been created + * already. {@link ListableBeanFactory#getBeansOfType} calls within + * this method won't trigger accidental side effects during bootstrap. + *

NOTE: This callback won't be triggered for singleton beans + * lazily initialized on demand after {@link BeanFactory} bootstrap, + * and not for any other bean scope either. Carefully use it for beans + * with the intended bootstrap semantics only. + */ + void afterSingletonsInstantiated(); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index eedad5dda1..a12d90fc4c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -56,6 +56,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -684,12 +685,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (this.logger.isDebugEnabled()) { this.logger.debug("Pre-instantiating singletons in " + this); } + List beanNames; synchronized (this.beanDefinitionMap) { // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. beanNames = new ArrayList(this.beanDefinitionNames); } + + // Trigger initialization of all non-lazy singleton beans... for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { @@ -717,6 +721,26 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } } + + // Trigger post-initialization callback for all applicable beans... + for (String beanName : beanNames) { + Object singletonInstance = getSingleton(beanName); + if (singletonInstance instanceof SmartInitializingSingleton) { + final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; + if (System.getSecurityManager() != null) { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + smartSingleton.afterSingletonsInstantiated(); + return null; + } + }, getAccessControlContext()); + } + else { + smartSingleton.afterSingletonsInstantiated(); + } + } + } } 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 0a25923772..7b613903ac 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 @@ -48,6 +48,7 @@ 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.SmartInitializingSingleton; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.Constants; @@ -87,7 +88,6 @@ 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 @@ -98,7 +98,7 @@ import org.springframework.util.ObjectUtils; * @see MBeanExporterListener */ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExportOperations, - BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { + BeanClassLoaderAware, BeanFactoryAware, InitializingBean, SmartInitializingSingleton, DisposableBean { /** * Autodetection mode indicating that no autodetection should be used. @@ -407,16 +407,14 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo if (this.server == null) { this.server = JmxUtils.locateMBeanServer(); } - - // TODO: to be replaced with some ContextRefreshedEvent-like callback - register(); } /** * Kick off bean registration automatically when deployed in an {@code ApplicationContext}. * @see #registerBeans() */ - public void register() { + @Override + public void afterSingletonsInstantiated() { try { logger.info("Registering beans for JMX exposure on startup"); registerBeans(); 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 87984ab737..aebfcd917a 100644 --- a/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java @@ -48,6 +48,7 @@ import static org.junit.Assert.*; * @author Juergen Hoeller * @author Sam Brannen * @author Chris Beams + * @author Stephane Nicoll */ public abstract class AbstractMBeanServerTests { @@ -100,7 +101,7 @@ public abstract class AbstractMBeanServerTests { */ protected void start(MBeanExporter exporter) { exporter.afterPropertiesSet(); - // exporter.register(); + exporter.afterSingletonsInstantiated(); } protected void assertIsRegistered(String message, ObjectName objectName) {