001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.beans.factory.support;
018:
019: import java.util.Collections;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.LinkedHashSet;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Set;
027: import java.util.LinkedHashMap;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import org.springframework.beans.factory.BeanCreationException;
033: import org.springframework.beans.factory.BeanCreationNotAllowedException;
034: import org.springframework.beans.factory.BeanCurrentlyInCreationException;
035: import org.springframework.beans.factory.DisposableBean;
036: import org.springframework.beans.factory.ObjectFactory;
037: import org.springframework.beans.factory.config.SingletonBeanRegistry;
038: import org.springframework.core.CollectionFactory;
039: import org.springframework.util.Assert;
040: import org.springframework.util.StringUtils;
041:
042: /**
043: * Generic registry for shared bean instances, implementing the
044: * {@link org.springframework.beans.factory.config.SingletonBeanRegistry}.
045: * Allows for registering singleton instances that should be shared
046: * for all callers of the registry, to be obtained via bean name.
047: *
048: * <p>Also supports registration of
049: * {@link org.springframework.beans.factory.DisposableBean} instances,
050: * (which might or might not correspond to registered singletons),
051: * to be destroyed on shutdown of the registry. Dependencies between
052: * beans can be registered to enforce an appropriate shutdown order.
053: *
054: * <p>This class mainly serves as base class for
055: * {@link org.springframework.beans.factory.BeanFactory} implementations,
056: * factoring out the common management of singleton bean instances. Note that
057: * the {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}
058: * interface extends the {@link SingletonBeanRegistry} interface.
059: *
060: * <p>Note that this class assumes neither a bean definition concept
061: * nor a specific creation process for bean instances, in contrast to
062: * {@link AbstractBeanFactory} and {@link DefaultListableBeanFactory}
063: * (which inherit from it). Can alternatively also be used as a nested
064: * helper to delegate to.
065: *
066: * @author Juergen Hoeller
067: * @since 2.0
068: * @see #registerSingleton
069: * @see #registerDisposableBean
070: * @see org.springframework.beans.factory.DisposableBean
071: * @see org.springframework.beans.factory.config.ConfigurableBeanFactory
072: */
073: public class DefaultSingletonBeanRegistry implements
074: SingletonBeanRegistry {
075:
076: /**
077: * Internal marker for a null singleton object:
078: * used as marker value for concurrent Maps (which don't support null values).
079: */
080: private static final Object NULL_OBJECT = new Object();
081:
082: /** Logger available to subclasses */
083: protected final Log logger = LogFactory.getLog(getClass());
084:
085: /** Cache of singleton objects: bean name --> bean instance */
086: private final Map singletonObjects = CollectionFactory
087: .createConcurrentMapIfPossible(16);
088:
089: /** Set of registered singletons, containing the bean names in registration order */
090: private final Set registeredSingletons = new LinkedHashSet(16);
091:
092: /** Names of beans that are currently in creation */
093: private final Set singletonsCurrentlyInCreation = Collections
094: .synchronizedSet(new HashSet());
095:
096: /** List of suppressed Exceptions, available for associating related causes */
097: private List suppressedExceptions;
098:
099: /** Flag that indicates whether we're currently within destroySingletons */
100: private boolean singletonsCurrentlyInDestruction = false;
101:
102: /** Disposable bean instances: bean name --> disposable instance */
103: private final Map disposableBeans = new LinkedHashMap(16);
104:
105: /** Map between dependent bean names: bean name --> Set of dependent bean names */
106: private final Map dependentBeanMap = CollectionFactory
107: .createConcurrentMapIfPossible(16);
108:
109: /** Map between depending bean names: bean name --> Set of bean names for the bean's dependencies */
110: private final Map dependenciesForBeanMap = CollectionFactory
111: .createConcurrentMapIfPossible(16);
112:
113: public void registerSingleton(String beanName,
114: Object singletonObject) throws IllegalStateException {
115: Assert.notNull(beanName, "'beanName' must not be null");
116: synchronized (this .singletonObjects) {
117: Object oldObject = this .singletonObjects.get(beanName);
118: if (oldObject != null) {
119: throw new IllegalStateException(
120: "Could not register object [" + singletonObject
121: + "] under bean name '" + beanName
122: + "': there is already object ["
123: + oldObject + "] bound");
124: }
125: addSingleton(beanName, singletonObject);
126: }
127: }
128:
129: /**
130: * Add the given singleton object to the singleton cache of this factory.
131: * <p>To be called for eager registration of singletons, e.g. to be able to
132: * resolve circular references.
133: * @param beanName the name of the bean
134: * @param singletonObject the singleton object
135: */
136: protected void addSingleton(String beanName, Object singletonObject) {
137: synchronized (this .singletonObjects) {
138: this .singletonObjects.put(beanName,
139: (singletonObject != null ? singletonObject
140: : NULL_OBJECT));
141: this .registeredSingletons.add(beanName);
142: }
143: }
144:
145: public Object getSingleton(String beanName) {
146: Object singletonObject = this .singletonObjects.get(beanName);
147: return (singletonObject != NULL_OBJECT ? singletonObject : null);
148: }
149:
150: /**
151: * Return the (raw) singleton object registered under the given name,
152: * creating and registering a new one if none registered yet.
153: * @param beanName the name of the bean
154: * @param singletonFactory the ObjectFactory to lazily create the singleton
155: * with, if necessary
156: * @return the registered singleton object
157: */
158: public Object getSingleton(String beanName,
159: ObjectFactory singletonFactory) {
160: Assert.notNull(beanName, "'beanName' must not be null");
161: synchronized (this .singletonObjects) {
162: // Re-check singleton cache within synchronized block.
163: Object singletonObject = this .singletonObjects
164: .get(beanName);
165: if (singletonObject == null) {
166: if (this .singletonsCurrentlyInDestruction) {
167: throw new BeanCreationNotAllowedException(
168: beanName,
169: "Singleton bean creation not allowed while the singletons of this factory are in destruction "
170: + "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
171: }
172: if (logger.isDebugEnabled()) {
173: logger
174: .debug("Creating shared instance of singleton bean '"
175: + beanName + "'");
176: }
177: beforeSingletonCreation(beanName);
178: boolean recordSuppressedExceptions = (this .suppressedExceptions == null);
179: if (recordSuppressedExceptions) {
180: this .suppressedExceptions = new LinkedList();
181: }
182: try {
183: singletonObject = singletonFactory.getObject();
184: } catch (BeanCreationException ex) {
185: for (Iterator it = this .suppressedExceptions
186: .iterator(); it.hasNext();) {
187: ex.addRelatedCause((Exception) it.next());
188: }
189: throw ex;
190: } finally {
191: if (recordSuppressedExceptions) {
192: this .suppressedExceptions = null;
193: }
194: afterSingletonCreation(beanName);
195: }
196: addSingleton(beanName, singletonObject);
197: }
198: return (singletonObject != NULL_OBJECT ? singletonObject
199: : null);
200: }
201: }
202:
203: /**
204: * Register an Exception that happened to get suppressed during the creation of a
205: * singleton bean instance, e.g. a temporary circular reference resolution problem.
206: * @param ex the Exception to register
207: */
208: protected void onSuppressedException(Exception ex) {
209: synchronized (this .singletonObjects) {
210: if (this .suppressedExceptions != null) {
211: this .suppressedExceptions.add(ex);
212: }
213: }
214: }
215:
216: /**
217: * Remove the bean with the given name from the singleton cache of this factory.
218: * <p>To be able to clean up eager registration of a singleton if creation failed.
219: * @param beanName the name of the bean
220: */
221: protected void removeSingleton(String beanName) {
222: this .singletonObjects.remove(beanName);
223: this .registeredSingletons.remove(beanName);
224: }
225:
226: public boolean containsSingleton(String beanName) {
227: return (this .singletonObjects.containsKey(beanName));
228: }
229:
230: public String[] getSingletonNames() {
231: synchronized (this .singletonObjects) {
232: return StringUtils.toStringArray(this .registeredSingletons);
233: }
234: }
235:
236: public int getSingletonCount() {
237: synchronized (this .singletonObjects) {
238: return this .registeredSingletons.size();
239: }
240: }
241:
242: /**
243: * Callback before singleton creation.
244: * <p>Default implementation register the singleton as currently in creation.
245: * @param beanName the name of the singleton about to be created
246: * @see #isSingletonCurrentlyInCreation
247: */
248: protected void beforeSingletonCreation(String beanName) {
249: if (!this .singletonsCurrentlyInCreation.add(beanName)) {
250: throw new BeanCurrentlyInCreationException(beanName);
251: }
252: }
253:
254: /**
255: * Callback after singleton creation.
256: * <p>Default implementation marks the singleton as not in creation anymore.
257: * @param beanName the name of the singleton that has been created
258: * @see #isSingletonCurrentlyInCreation
259: */
260: protected void afterSingletonCreation(String beanName) {
261: if (!this .singletonsCurrentlyInCreation.remove(beanName)) {
262: throw new IllegalStateException("Singleton '" + beanName
263: + "' isn't currently in creation");
264: }
265: }
266:
267: /**
268: * Return whether the specified singleton bean is currently in creation
269: * (within the entire factory).
270: * @param beanName the name of the bean
271: */
272: public final boolean isSingletonCurrentlyInCreation(String beanName) {
273: return this .singletonsCurrentlyInCreation.contains(beanName);
274: }
275:
276: /**
277: * Add the given bean to the list of disposable beans in this registry.
278: * Disposable beans usually correspond to registered singletons,
279: * matching the bean name but potentially being a different instance
280: * (for example, a DisposableBean adapter for a singleton that does not
281: * naturally implement Spring's DisposableBean interface).
282: * @param beanName the name of the bean
283: * @param bean the bean instance
284: */
285: public void registerDisposableBean(String beanName,
286: DisposableBean bean) {
287: synchronized (this .disposableBeans) {
288: this .disposableBeans.put(beanName, bean);
289: }
290: }
291:
292: /**
293: * Register a dependent bean for the given bean,
294: * to be destroyed before the given bean is destroyed.
295: * @param beanName the name of the bean
296: * @param dependentBeanName the name of the dependent bean
297: */
298: public void registerDependentBean(String beanName,
299: String dependentBeanName) {
300: synchronized (this .dependentBeanMap) {
301: Set dependentBeans = (Set) this .dependentBeanMap
302: .get(beanName);
303: if (dependentBeans == null) {
304: dependentBeans = new LinkedHashSet(8);
305: this .dependentBeanMap.put(beanName, dependentBeans);
306: }
307: dependentBeans.add(dependentBeanName);
308: }
309: synchronized (this .dependenciesForBeanMap) {
310: Set dependenciesForBean = (Set) this .dependenciesForBeanMap
311: .get(dependentBeanName);
312: if (dependenciesForBean == null) {
313: dependenciesForBean = new LinkedHashSet(8);
314: this .dependenciesForBeanMap.put(dependentBeanName,
315: dependenciesForBean);
316: }
317: dependenciesForBean.add(beanName);
318: }
319: }
320:
321: /**
322: * Determine whether a dependent bean has been registered under the given name.
323: * @param beanName the name of the bean
324: */
325: protected boolean hasDependentBean(String beanName) {
326: return this .dependentBeanMap.containsKey(beanName);
327: }
328:
329: /**
330: * Return the names of all beans which depend on the specified bean, if any.
331: * @param beanName the name of the bean
332: * @return the array of dependent bean names, or an empty array if none
333: */
334: public String[] getDependentBeans(String beanName) {
335: Set dependentBeans = (Set) this .dependentBeanMap.get(beanName);
336: if (dependentBeans == null) {
337: return new String[0];
338: }
339: return (String[]) dependentBeans
340: .toArray(new String[dependentBeans.size()]);
341: }
342:
343: /**
344: * Return the names of all beans that the specified bean depends on, if any.
345: * @param beanName the name of the bean
346: * @return the array of names of beans which the bean depends on,
347: * or an empty array if none
348: */
349: public String[] getDependenciesForBean(String beanName) {
350: Set dependenciesForBean = (Set) this .dependenciesForBeanMap
351: .get(beanName);
352: if (dependenciesForBean == null) {
353: return new String[0];
354: }
355: return (String[]) dependenciesForBean
356: .toArray(new String[dependenciesForBean.size()]);
357: }
358:
359: public void destroySingletons() {
360: if (logger.isInfoEnabled()) {
361: logger.info("Destroying singletons in " + this );
362: }
363: synchronized (this .singletonObjects) {
364: this .singletonsCurrentlyInDestruction = true;
365: }
366: synchronized (this .disposableBeans) {
367: String[] disposableBeanNames = StringUtils
368: .toStringArray(this .disposableBeans.keySet());
369: for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
370: destroySingleton(disposableBeanNames[i]);
371: }
372: }
373: synchronized (this .singletonObjects) {
374: this .singletonObjects.clear();
375: this .registeredSingletons.clear();
376: this .singletonsCurrentlyInDestruction = false;
377: }
378: }
379:
380: /**
381: * Destroy the given bean. Delegates to <code>destroyBean</code>
382: * if a corresponding disposable bean instance is found.
383: * @param beanName the name of the bean
384: * @see #destroyBean
385: */
386: public void destroySingleton(String beanName) {
387: synchronized (this .singletonObjects) {
388: // Remove a registered singleton of the given name, if any.
389: removeSingleton(beanName);
390: }
391:
392: // Destroy the corresponding DisposableBean instance.
393: DisposableBean disposableBean = null;
394: synchronized (this .disposableBeans) {
395: disposableBean = (DisposableBean) this .disposableBeans
396: .remove(beanName);
397: }
398: destroyBean(beanName, disposableBean);
399: }
400:
401: /**
402: * Destroy the given bean. Must destroy beans that depend on the given
403: * bean before the bean itself. Should not throw any exceptions.
404: * @param beanName the name of the bean
405: * @param bean the bean instance to destroy
406: */
407: protected void destroyBean(String beanName, DisposableBean bean) {
408: Set dependencies = (Set) this .dependentBeanMap.remove(beanName);
409: if (dependencies != null) {
410: if (logger.isDebugEnabled()) {
411: logger.debug("Retrieved dependent beans for bean '"
412: + beanName + "': " + dependencies);
413: }
414: for (Iterator it = dependencies.iterator(); it.hasNext();) {
415: String dependentBeanName = (String) it.next();
416: destroySingleton(dependentBeanName);
417: }
418: }
419: if (bean != null) {
420: try {
421: bean.destroy();
422: } catch (Throwable ex) {
423: logger.error("Destroy method on bean with name '"
424: + beanName + "' threw an exception", ex);
425: }
426: }
427: }
428:
429: /**
430: * Expose the singleton mutex to subclasses.
431: * <p>Subclasses should synchronize on the given Object if they perform
432: * any sort of extended singleton creation phase. In particular, subclasses
433: * should <i>not</i> have their own mutexes involved in singleton creation,
434: * to avoid the potential for deadlocks in lazy-init situations.
435: */
436: protected final Object getSingletonMutex() {
437: return this.singletonObjects;
438: }
439:
440: }
|