Open Source Repository

Home /spring/spring-beans-3.0.5 | Repository Home



org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
/*
 * Copyright 2002-2010 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.support;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.core.SimpleAliasRegistry;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Generic registry for shared bean instances, implementing the
 {@link org.springframework.beans.factory.config.SingletonBeanRegistry}.
 * Allows for registering singleton instances that should be shared
 * for all callers of the registry, to be obtained via bean name.
 *
 <p>Also supports registration of
 {@link org.springframework.beans.factory.DisposableBean} instances,
 * (which might or might not correspond to registered singletons),
 * to be destroyed on shutdown of the registry. Dependencies between
 * beans can be registered to enforce an appropriate shutdown order.
 *
 <p>This class mainly serves as base class for
 {@link org.springframework.beans.factory.BeanFactory} implementations,
 * factoring out the common management of singleton bean instances. Note that
 * the {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}
 * interface extends the {@link SingletonBeanRegistry} interface.
 *
 <p>Note that this class assumes neither a bean definition concept
 * nor a specific creation process for bean instances, in contrast to
 {@link AbstractBeanFactory} and {@link DefaultListableBeanFactory}
 * (which inherit from it). Can alternatively also be used as a nested
 * helper to delegate to.
 *
 @author Juergen Hoeller
 @since 2.0
 @see #registerSingleton
 @see #registerDisposableBean
 @see org.springframework.beans.factory.DisposableBean
 @see org.springframework.beans.factory.config.ConfigurableBeanFactory
 */
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

  /**
   * Internal marker for a null singleton object:
   * used as marker value for concurrent Maps (which don't support null values).
   */
  protected static final Object NULL_OBJECT = new Object();


  /** Logger available to subclasses */
  protected final Log logger = LogFactory.getLog(getClass());

  /** Cache of singleton objects: bean name --> bean instance */
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>();

  /** Cache of singleton factories: bean name --> ObjectFactory */
  private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>();

  /** Cache of early singleton objects: bean name --> bean instance */
  private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>();

  /** Set of registered singletons, containing the bean names in registration order */
  private final Set<String> registeredSingletons = new LinkedHashSet<String>(16);

  /** Names of beans that are currently in creation */
  private final Set<String> singletonsCurrentlyInCreation = Collections.synchronizedSet(new HashSet<String>());

  /** List of suppressed Exceptions, available for associating related causes */
  private Set<Exception> suppressedExceptions;

  /** Flag that indicates whether we're currently within destroySingletons */
  private boolean singletonsCurrentlyInDestruction = false;

  /** Disposable bean instances: bean name --> disposable instance */
  private final Map<String, Object> disposableBeans = new LinkedHashMap<String, Object>();

  /** Map between containing bean names: bean name --> Set of bean names that the bean contains */
  private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<String, Set<String>>();

  /** Map between dependent bean names: bean name --> Set of dependent bean names */
  private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<String, Set<String>>();

  /** Map between depending bean names: bean name --> Set of bean names for the bean's dependencies */
  private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<String, Set<String>>();


  public void registerSingleton(String beanName, Object singletonObjectthrows IllegalStateException {
    Assert.notNull(beanName, "'beanName' must not be null");
    synchronized (this.singletonObjects) {
      Object oldObject = this.singletonObjects.get(beanName);
      if (oldObject != null) {
        throw new IllegalStateException("Could not register object [" + singletonObject +
            "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
      }
      addSingleton(beanName, singletonObject);
    }
  }

  /**
   * Add the given singleton object to the singleton cache of this factory.
   <p>To be called for eager registration of singletons.
   @param beanName the name of the bean
   @param singletonObject the singleton object
   */
  protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
    }
  }

  /**
   * Add the given singleton factory for building the specified singleton
   * if necessary.
   <p>To be called for eager registration of singletons, e.g. to be able to
   * resolve circular references.
   @param beanName the name of the bean
   @param singletonFactory the factory for the singleton object
   */
  protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
        this.singletonFactories.put(beanName, singletonFactory);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
      }
    }
  }

  public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
  }

  /**
   * Return the (raw) singleton object registered under the given name.
   <p>Checks already instantiated singletons and also allows for an early
   * reference to a currently created singleton (resolving a circular reference).
   @param beanName the name of the bean to look for
   @param allowEarlyReference whether early references should be created or not
   @return the registered singleton object, or <code>null</code> if none found
   */
  protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
      synchronized (this.singletonObjects) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
          ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
          if (singletonFactory != null) {
            singletonObject = singletonFactory.getObject();
            this.earlySingletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
          }
        }
      }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
  }

  /**
   * Return the (raw) singleton object registered under the given name,
   * creating and registering a new one if none registered yet.
   @param beanName the name of the bean
   @param singletonFactory the ObjectFactory to lazily create the singleton
   * with, if necessary
   @return the registered singleton object
   */
  public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
    Assert.notNull(beanName, "'beanName' must not be null");
    synchronized (this.singletonObjects) {
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
        if (this.singletonsCurrentlyInDestruction) {
          throw new BeanCreationNotAllowedException(beanName,
              "Singleton bean creation not allowed while the singletons of this factory are in destruction " +
              "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
        }
        if (logger.isDebugEnabled()) {
          logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
        }
        beforeSingletonCreation(beanName);
        boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
        if (recordSuppressedExceptions) {
          this.suppressedExceptions = new LinkedHashSet<Exception>();
        }
        try {
          singletonObject = singletonFactory.getObject();
        }
        catch (BeanCreationException ex) {
          if (recordSuppressedExceptions) {
            for (Exception suppressedException : this.suppressedExceptions) {
              ex.addRelatedCause(suppressedException);
            }
          }
          throw ex;
        }
        finally {
          if (recordSuppressedExceptions) {
            this.suppressedExceptions = null;
          }
          afterSingletonCreation(beanName);
        }
        addSingleton(beanName, singletonObject);
      }
      return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
  }

  /**
   * Register an Exception that happened to get suppressed during the creation of a
   * singleton bean instance, e.g. a temporary circular reference resolution problem.
   @param ex the Exception to register
   */
  protected void onSuppressedException(Exception ex) {
    synchronized (this.singletonObjects) {
      if (this.suppressedExceptions != null) {
        this.suppressedExceptions.add(ex);
      }
    }
  }

  /**
   * Remove the bean with the given name from the singleton cache of this factory,
   * to be able to clean up eager registration of a singleton if creation failed.
   @param beanName the name of the bean
   @see #getSingletonMutex()
   */
  protected void removeSingleton(String beanName) {
    synchronized (this.singletonObjects) {
      this.singletonObjects.remove(beanName);
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.remove(beanName);
    }
  }

  public boolean containsSingleton(String beanName) {
    return (this.singletonObjects.containsKey(beanName));
  }

  public String[] getSingletonNames() {
    synchronized (this.singletonObjects) {
      return StringUtils.toStringArray(this.registeredSingletons);
    }
  }

  public int getSingletonCount() {
    synchronized (this.singletonObjects) {
      return this.registeredSingletons.size();
    }
  }


  /**
   * Callback before singleton creation.
   <p>Default implementation register the singleton as currently in creation.
   @param beanName the name of the singleton about to be created
   @see #isSingletonCurrentlyInCreation
   */
  protected void beforeSingletonCreation(String beanName) {
    if (!this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
    }
  }

  /**
   * Callback after singleton creation.
   <p>Default implementation marks the singleton as not in creation anymore.
   @param beanName the name of the singleton that has been created
   @see #isSingletonCurrentlyInCreation
   */
  protected void afterSingletonCreation(String beanName) {
    if (!this.singletonsCurrentlyInCreation.remove(beanName)) {
      throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
  }

  /**
   * Return whether the specified singleton bean is currently in creation
   * (within the entire factory).
   @param beanName the name of the bean
   */
  public final boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
  }


  /**
   * Add the given bean to the list of disposable beans in this registry.
   * Disposable beans usually correspond to registered singletons,
   * matching the bean name but potentially being a different instance
   * (for example, a DisposableBean adapter for a singleton that does not
   * naturally implement Spring's DisposableBean interface).
   @param beanName the name of the bean
   @param bean the bean instance
   */
  public void registerDisposableBean(String beanName, DisposableBean bean) {
    synchronized (this.disposableBeans) {
      this.disposableBeans.put(beanName, bean);
    }
  }

  /**
   * Register a containment relationship between two beans,
   * e.g. between an inner bean and its containing outer bean.
   <p>Also registers the containing bean as dependent on the contained bean
   * in terms of destruction order.
   @param containedBeanName the name of the contained (inner) bean
   @param containingBeanName the name of the containing (outer) bean
   @see #registerDependentBean
   */
  public void registerContainedBean(String containedBeanName, String containingBeanName) {
    synchronized (this.containedBeanMap) {
      Set<String> containedBeans = this.containedBeanMap.get(containingBeanName);
      if (containedBeans == null) {
        containedBeans = new LinkedHashSet<String>(8);
        this.containedBeanMap.put(containingBeanName, containedBeans);
      }
      containedBeans.add(containedBeanName);
    }
    registerDependentBean(containedBeanName, containingBeanName);
  }

  /**
   * Register a dependent bean for the given bean,
   * to be destroyed before the given bean is destroyed.
   @param beanName the name of the bean
   @param dependentBeanName the name of the dependent bean
   */
  public void registerDependentBean(String beanName, String dependentBeanName) {
    String canonicalName = canonicalName(beanName);
    synchronized (this.dependentBeanMap) {
      Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
      if (dependentBeans == null) {
        dependentBeans = new LinkedHashSet<String>(8);
        this.dependentBeanMap.put(canonicalName, dependentBeans);
      }
      dependentBeans.add(dependentBeanName);
    }
    synchronized (this.dependenciesForBeanMap) {
      Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
      if (dependenciesForBean == null) {
        dependenciesForBean = new LinkedHashSet<String>(8);
        this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean);
      }
      dependenciesForBean.add(canonicalName);
    }
  }

  /**
   * Determine whether a dependent bean has been registered for the given name.
   @param beanName the name of the bean to check
   */
  protected boolean hasDependentBean(String beanName) {
    return this.dependentBeanMap.containsKey(beanName);
  }

  /**
   * Return the names of all beans which depend on the specified bean, if any.
   @param beanName the name of the bean
   @return the array of dependent bean names, or an empty array if none
   */
  public String[] getDependentBeans(String beanName) {
    Set<String> dependentBeans = this.dependentBeanMap.get(beanName);
    if (dependentBeans == null) {
      return new String[0];
    }
    return StringUtils.toStringArray(dependentBeans);
  }

  /**
   * Return the names of all beans that the specified bean depends on, if any.
   @param beanName the name of the bean
   @return the array of names of beans which the bean depends on,
   * or an empty array if none
   */
  public String[] getDependenciesForBean(String beanName) {
    Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(beanName);
    if (dependenciesForBean == null) {
      return new String[0];
    }
    return dependenciesForBean.toArray(new String[dependenciesForBean.size()]);
  }

  public void destroySingletons() {
    if (logger.isInfoEnabled()) {
      logger.info("Destroying singletons in " this);
    }
    synchronized (this.singletonObjects) {
      this.singletonsCurrentlyInDestruction = true;
    }

    synchronized (this.disposableBeans) {
      String[] disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
      for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
        destroySingleton(disposableBeanNames[i]);
      }
    }

    this.containedBeanMap.clear();
    this.dependentBeanMap.clear();
    this.dependenciesForBeanMap.clear();

    synchronized (this.singletonObjects) {
      this.singletonObjects.clear();
      this.singletonFactories.clear();
      this.earlySingletonObjects.clear();
      this.registeredSingletons.clear();
      this.singletonsCurrentlyInDestruction = false;
    }
  }

  /**
   * Destroy the given bean. Delegates to <code>destroyBean</code>
   * if a corresponding disposable bean instance is found.
   @param beanName the name of the bean
   @see #destroyBean
   */
  public void destroySingleton(String beanName) {
    // Remove a registered singleton of the given name, if any.
    removeSingleton(beanName);

    // Destroy the corresponding DisposableBean instance.
    DisposableBean disposableBean;
    synchronized (this.disposableBeans) {
      disposableBean = (DisposableBeanthis.disposableBeans.remove(beanName);
    }
    destroyBean(beanName, disposableBean);
  }

  /**
   * Destroy the given bean. Must destroy beans that depend on the given
   * bean before the bean itself. Should not throw any exceptions.
   @param beanName the name of the bean
   @param bean the bean instance to destroy
   */
  protected void destroyBean(String beanName, DisposableBean bean) {
    // Trigger destruction of dependent beans first...
    Set<String> dependencies = this.dependentBeanMap.remove(beanName);
    if (dependencies != null) {
      if (logger.isDebugEnabled()) {
        logger.debug("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);
      }
      for (String dependentBeanName : dependencies) {
        destroySingleton(dependentBeanName);
      }
    }

    // Actually destroy the bean now...
    if (bean != null) {
      try {
        bean.destroy();
      }
      catch (Throwable ex) {
        logger.error("Destroy method on bean with name '" + beanName + "' threw an exception", ex);
      }
    }

    // Trigger destruction of contained beans...
    Set<String> containedBeans = this.containedBeanMap.remove(beanName);
    if (containedBeans != null) {
      for (String containedBeanName : containedBeans) {
        destroySingleton(containedBeanName);
      }
    }

    // Remove destroyed bean from other beans' dependencies.
    synchronized (this.dependentBeanMap) {
      for (Iterator<Map.Entry<String, Set<String>>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) {
        Map.Entry<String, Set<String>> entry = it.next();
        Set<String> dependenciesToClean = entry.getValue();
        dependenciesToClean.remove(beanName);
        if (dependenciesToClean.isEmpty()) {
          it.remove();
        }
      }
    }

    // Remove destroyed bean's prepared dependency information.
    this.dependenciesForBeanMap.remove(beanName);
  }

  /**
   * Expose the singleton mutex to subclasses.
   <p>Subclasses should synchronize on the given Object if they perform
   * any sort of extended singleton creation phase. In particular, subclasses
   * should <i>not</i> have their own mutexes involved in singleton creation,
   * to avoid the potential for deadlocks in lazy-init situations.
   */
  protected final Object getSingletonMutex() {
    return this.singletonObjects;
  }

}