From d1520682df4ccfa18733477525b77855366569b9 Mon Sep 17 00:00:00 2001 From: Steve Bohlen Date: Tue, 11 Dec 2012 16:50:57 -0500 Subject: [PATCH] SPRNET-1318 introduce local cache of merged ObjectDefinitions to reduce impact of AppContext.Dispose() calls --- .../AbstractAutowireCapableObjectFactory.cs | 383 ++++++++---------- .../Factory/Support/AbstractObjectFactory.cs | 212 ++++++++-- .../Support/DefaultListableObjectFactory.cs | 2 +- .../Support/DisposableObjectAdapter.cs | 296 ++++++++++++++ .../Spring.Core/Spring.Core.2010.csproj | 1 + .../Spring.Core/Util/ReflectionUtils.cs | 4 +- 6 files changed, 661 insertions(+), 237 deletions(-) create mode 100644 src/Spring/Spring.Core/Objects/Factory/Support/DisposableObjectAdapter.cs diff --git a/src/Spring/Spring.Core/Objects/Factory/Support/AbstractAutowireCapableObjectFactory.cs b/src/Spring/Spring.Core/Objects/Factory/Support/AbstractAutowireCapableObjectFactory.cs index e4492d32..402135f9 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Support/AbstractAutowireCapableObjectFactory.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Support/AbstractAutowireCapableObjectFactory.cs @@ -257,7 +257,7 @@ namespace Spring.Objects.Factory.Support ApplyPropertyValues(name, definition, new ObjectWrapper(instance), definition.PropertyValues); } } - + /// /// Apply the property values of the object definition with the supplied /// to the supplied . @@ -480,8 +480,8 @@ namespace Spring.Objects.Factory.Support //have to clone the collection before iterating it to avoid arbitrary code in the objects' Dispose() // that might permit re-entering the DisposableInnerObjects collections during the iteration to destroy them // see https://jira.springframework.org/browse/SPRNET-1334 - - ISet clone = (ISet) DisposableInnerObjects.Clone(); + + ISet clone = (ISet)DisposableInnerObjects.Clone(); foreach (object o in clone) { @@ -792,44 +792,44 @@ namespace Spring.Objects.Factory.Support ignoredDependencyInterfaces.Add(type); } -// /// -// /// Create an object instance for the given object definition. -// /// -// /// The name of the object. -// /// -// /// The object definition for the object that is to be instantiated. -// /// -// /// -// /// The arguments to use if creating a prototype using explicit arguments to -// /// a static factory method. It is invalid to use a non- arguments value -// /// in any other case. -// /// -// /// -// /// A new instance of the object. -// /// -// /// -// /// In case of errors. -// /// -// /// -// ///

-// /// Delegates to the -// /// -// /// method version with the allowEagerCaching parameter set to true. -// ///

-// ///

-// /// The object definition will already have been merged with the parent -// /// definition in case of a child definition. -// ///

-// ///

-// /// All the other methods in this class invoke this method, although objects -// /// may be cached after being instantiated by this method. All object -// /// instantiation within this class is performed by this method. -// ///

-// ///
-// protected internal override object CreateObject(string name, RootObjectDefinition definition, object[] arguments) -// { -// return CreateObject(name, definition, arguments, true, false); -// } + // /// + // /// Create an object instance for the given object definition. + // /// + // /// The name of the object. + // /// + // /// The object definition for the object that is to be instantiated. + // /// + // /// + // /// The arguments to use if creating a prototype using explicit arguments to + // /// a static factory method. It is invalid to use a non- arguments value + // /// in any other case. + // /// + // /// + // /// A new instance of the object. + // /// + // /// + // /// In case of errors. + // /// + // /// + // ///

+ // /// Delegates to the + // /// + // /// method version with the allowEagerCaching parameter set to true. + // ///

+ // ///

+ // /// The object definition will already have been merged with the parent + // /// definition in case of a child definition. + // ///

+ // ///

+ // /// All the other methods in this class invoke this method, although objects + // /// may be cached after being instantiated by this method. All object + // /// instantiation within this class is performed by this method. + // ///

+ // ///
+ // protected internal override object CreateObject(string name, RootObjectDefinition definition, object[] arguments) + // { + // return CreateObject(name, definition, arguments, true, false); + // } /// /// Create an object instance for the given object definition. @@ -1221,7 +1221,7 @@ namespace Spring.Objects.Factory.Support if (unsatisfiedDependencies.Count > 0) { throw new UnsatisfiedDependencyException(definition.ResourceDescription, name, unsatisfiedDependencies[0].Name, - "Set this property value or disable dependency checking for this object."); + "Set this property value or disable dependency checking for this object."); } } @@ -1438,32 +1438,10 @@ namespace Spring.Objects.Factory.Support /// protected override void DestroyObject(string name, object target) { - log.Debug(m => m("Destroying dependant objects for object '{0}", name)); - - DestroyDependantObjects(name); - - ApplyObjectPostProcessBeforeDestruction(target, name); - - if (target is IDisposable) + using (new DisposableObjectAdapter(target, name, GetMergedObjectDefinition(name, true), ObjectPostProcessors)) { - log.Debug(m => m(string.Format(CultureInfo.InvariantCulture, "Calling Dispose() on object with name '{0}'.", name))); - - try - { - ((IDisposable)target).Dispose(); - } - catch (Exception ex) - { - log.Error("Destroy() on object with name '" + name + "' threw an exception.", ex); - } - } - - RootObjectDefinition rootDefinition = GetMergedObjectDefinition(name, false); - if (rootDefinition != null && StringUtils.HasText(rootDefinition.DestroyMethodName)) - { - log.Debug(m => m("Calling custom destroy method '{0}' on object with name '{1}'.", rootDefinition.DestroyMethodName, name)); - - InvokeCustomDestroyMethod(name, target, rootDefinition.DestroyMethodName); + log.Debug(m => m("Destroying dependant objects for object '{0}", name)); + DestroyDependantObjects(name); } } @@ -1533,123 +1511,123 @@ namespace Spring.Objects.Factory.Support ///// ///// The value of the property that is being resolved. ///// -// protected object ResolveValueIfNecessary(string name, RootObjectDefinition definition, string argumentName, object argumentValue) -// { -// object resolvedValue = null; -// -// AssertUtils.ArgumentNotNull(resolvedValue, "test"); -// -// // we must check the argument value to see whether it requires a runtime -// // reference to another object to be resolved. -// // if it does, we'll attempt to instantiate the object and set the reference. -// if (RemotingServices.IsTransparentProxy(argumentValue)) -// { -// resolvedValue = argumentValue; -// } -// else if (argumentValue is ObjectDefinitionHolder) -// { -// // contains an IObjectDefinition with name and aliases... -// ObjectDefinitionHolder holder = (ObjectDefinitionHolder)argumentValue; -// resolvedValue = ResolveInnerObjectDefinition(name, holder.ObjectName, argumentName, holder.ObjectDefinition, definition.IsSingleton); -// } -// else if (argumentValue is IObjectDefinition) -// { -// // resolve plain IObjectDefinition, without contained name: use dummy name... -// IObjectDefinition def = (IObjectDefinition)argumentValue; -// resolvedValue = ResolveInnerObjectDefinition(name, "(inner object)", argumentName, def, definition.IsSingleton); -// -// } -// else if (argumentValue is RuntimeObjectReference) -// { -// RuntimeObjectReference roref = (RuntimeObjectReference)argumentValue; -// resolvedValue = ResolveReference(definition, name, argumentName, roref); -// } -// else if (argumentValue is ExpressionHolder) -// { -// ExpressionHolder expHolder = (ExpressionHolder)argumentValue; -// object context = null; -// IDictionary variables = null; -// -// if (expHolder.Properties != null) -// { -// PropertyValue contextProperty = expHolder.Properties.GetPropertyValue("Context"); -// context = contextProperty == null -// ? null -// : ResolveValueIfNecessary2(name, definition, "Context", -// contextProperty.Value); -// PropertyValue variablesProperty = expHolder.Properties.GetPropertyValue("Variables"); -// object vars = (variablesProperty == null -// ? null -// : ResolveValueIfNecessary2(name, definition, "Variables", -// variablesProperty.Value)); -// if (vars is IDictionary) -// { -// variables = (IDictionary)vars; -// } -// else -// { -// if (vars != null) throw new ArgumentException("'Variables' must resolve to an IDictionary"); -// } -// } -// -// if (variables == null) variables = CollectionsUtil.CreateCaseInsensitiveHashtable(); -// // add 'this' objectfactory reference to variables -// variables.Add(Expression.ReservedVariableNames.CurrentObjectFactory, this); -// -// resolvedValue = expHolder.Expression.GetValue(context, variables); -// } -// else if (argumentValue is IManagedCollection) -// { -// resolvedValue = -// ((IManagedCollection)argumentValue).Resolve(name, definition, argumentName, -// new ManagedCollectionElementResolver(ResolveValueIfNecessary2)); -// } -// else if (argumentValue is TypedStringValue) -// { -// TypedStringValue tsv = (TypedStringValue)argumentValue; -// try -// { -// Type resolvedTargetType = ResolveTargetType(tsv); -// if (resolvedTargetType != null) -// { -// resolvedValue = TypeConversionUtils.ConvertValueIfNecessary(tsv.TargetType, tsv.Value, null); -// } -// else -// { -// resolvedValue = tsv.Value; -// } -// } -// catch (Exception ex) -// { -// throw new ObjectCreationException(definition.ResourceDescription, name, -// "Error converted typed String value for " + argumentName, ex); -// } -// -// } -// else -// { -// // no need to resolve value... -// resolvedValue = argumentValue; -// } -// return resolvedValue; -// } + // protected object ResolveValueIfNecessary(string name, RootObjectDefinition definition, string argumentName, object argumentValue) + // { + // object resolvedValue = null; + // + // AssertUtils.ArgumentNotNull(resolvedValue, "test"); + // + // // we must check the argument value to see whether it requires a runtime + // // reference to another object to be resolved. + // // if it does, we'll attempt to instantiate the object and set the reference. + // if (RemotingServices.IsTransparentProxy(argumentValue)) + // { + // resolvedValue = argumentValue; + // } + // else if (argumentValue is ObjectDefinitionHolder) + // { + // // contains an IObjectDefinition with name and aliases... + // ObjectDefinitionHolder holder = (ObjectDefinitionHolder)argumentValue; + // resolvedValue = ResolveInnerObjectDefinition(name, holder.ObjectName, argumentName, holder.ObjectDefinition, definition.IsSingleton); + // } + // else if (argumentValue is IObjectDefinition) + // { + // // resolve plain IObjectDefinition, without contained name: use dummy name... + // IObjectDefinition def = (IObjectDefinition)argumentValue; + // resolvedValue = ResolveInnerObjectDefinition(name, "(inner object)", argumentName, def, definition.IsSingleton); + // + // } + // else if (argumentValue is RuntimeObjectReference) + // { + // RuntimeObjectReference roref = (RuntimeObjectReference)argumentValue; + // resolvedValue = ResolveReference(definition, name, argumentName, roref); + // } + // else if (argumentValue is ExpressionHolder) + // { + // ExpressionHolder expHolder = (ExpressionHolder)argumentValue; + // object context = null; + // IDictionary variables = null; + // + // if (expHolder.Properties != null) + // { + // PropertyValue contextProperty = expHolder.Properties.GetPropertyValue("Context"); + // context = contextProperty == null + // ? null + // : ResolveValueIfNecessary2(name, definition, "Context", + // contextProperty.Value); + // PropertyValue variablesProperty = expHolder.Properties.GetPropertyValue("Variables"); + // object vars = (variablesProperty == null + // ? null + // : ResolveValueIfNecessary2(name, definition, "Variables", + // variablesProperty.Value)); + // if (vars is IDictionary) + // { + // variables = (IDictionary)vars; + // } + // else + // { + // if (vars != null) throw new ArgumentException("'Variables' must resolve to an IDictionary"); + // } + // } + // + // if (variables == null) variables = CollectionsUtil.CreateCaseInsensitiveHashtable(); + // // add 'this' objectfactory reference to variables + // variables.Add(Expression.ReservedVariableNames.CurrentObjectFactory, this); + // + // resolvedValue = expHolder.Expression.GetValue(context, variables); + // } + // else if (argumentValue is IManagedCollection) + // { + // resolvedValue = + // ((IManagedCollection)argumentValue).Resolve(name, definition, argumentName, + // new ManagedCollectionElementResolver(ResolveValueIfNecessary2)); + // } + // else if (argumentValue is TypedStringValue) + // { + // TypedStringValue tsv = (TypedStringValue)argumentValue; + // try + // { + // Type resolvedTargetType = ResolveTargetType(tsv); + // if (resolvedTargetType != null) + // { + // resolvedValue = TypeConversionUtils.ConvertValueIfNecessary(tsv.TargetType, tsv.Value, null); + // } + // else + // { + // resolvedValue = tsv.Value; + // } + // } + // catch (Exception ex) + // { + // throw new ObjectCreationException(definition.ResourceDescription, name, + // "Error converted typed String value for " + argumentName, ex); + // } + // + // } + // else + // { + // // no need to resolve value... + // resolvedValue = argumentValue; + // } + // return resolvedValue; + // } ///// ///// Resolve the target type of the passed . ///// ///// The who's target type is to be resolved ///// The resolved target type, if any. otherwise. -// protected virtual Type ResolveTargetType(TypedStringValue value) -// { -// if (value.HasTargetType) -// { -// return value.TargetType; -// } -// else -// { -// return null; -// } -// } + // protected virtual Type ResolveTargetType(TypedStringValue value) + // { + // if (value.HasTargetType) + // { + // return value.TargetType; + // } + // else + // { + // return null; + // } + // } ///// ///// Resolves an inner object definition. @@ -1674,30 +1652,30 @@ namespace Spring.Objects.Factory.Support ///// ///// The resolved object as defined by the inner object definition. ///// -// protected object ResolveInnerObjectDefinition(string name, string innerObjectName, string argumentName, IObjectDefinition definition, -// bool singletonOwner) -// { -// RootObjectDefinition mod = GetMergedObjectDefinition(innerObjectName, definition); -// mod.IsSingleton = singletonOwner; -// object instance; -// object result; -// try -// { -// instance = InstantiateObject(innerObjectName, mod, ObjectUtils.EmptyObjects, false, false); -// result = GetObjectForInstance(innerObjectName, instance); -// } -// catch (ObjectsException ex) -// { -// throw ObjectCreationException.GetObjectCreationException(ex, name, argumentName, definition.ResourceDescription, innerObjectName); -// } -// if (singletonOwner && instance is IDisposable) -// { -// // keep a reference to the inner object instance, to be able to destroy -// // it on factory shutdown... -// DisposableInnerObjects.Add(instance); -// } -// return result; -// } + // protected object ResolveInnerObjectDefinition(string name, string innerObjectName, string argumentName, IObjectDefinition definition, + // bool singletonOwner) + // { + // RootObjectDefinition mod = GetMergedObjectDefinition(innerObjectName, definition); + // mod.IsSingleton = singletonOwner; + // object instance; + // object result; + // try + // { + // instance = InstantiateObject(innerObjectName, mod, ObjectUtils.EmptyObjects, false, false); + // result = GetObjectForInstance(innerObjectName, instance); + // } + // catch (ObjectsException ex) + // { + // throw ObjectCreationException.GetObjectCreationException(ex, name, argumentName, definition.ResourceDescription, innerObjectName); + // } + // if (singletonOwner && instance is IDisposable) + // { + // // keep a reference to the inner object instance, to be able to destroy + // // it on factory shutdown... + // DisposableInnerObjects.Add(instance); + // } + // return result; + // } /// /// Resolve a reference to another object in the factory. @@ -1897,7 +1875,6 @@ namespace Spring.Objects.Factory.Support return instance; } - /// /// Applies the PostProcessAfterInitialization callback of all /// registered IObjectPostProcessors, giving them a chance to post-process diff --git a/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs b/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs index 0233369b..882bc14a 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs @@ -29,6 +29,7 @@ using System.ComponentModel; using Common.Logging; using Spring.Collections; +using Spring.Collections.Generic; using Spring.Core; using Spring.Core.TypeConversion; using Spring.Objects.Factory.Config; @@ -144,6 +145,27 @@ namespace Spring.Objects.Factory.Support /// private readonly Dictionary factoryObjectProductCache = new Dictionary(); + /// + /// Disposable object instances: object name --> disposable instance + /// + private readonly Dictionary disposableObjects = new Dictionary(); + + /// + /// root object definitons: object name --> Root Object Definition + /// + protected SynchronizedHashtable mergedObjectDefinitions = new Spring.Collections.SynchronizedHashtable(); + + /// + /// Whether to cache object metadata or rather reobtain it for every access + /// + protected bool cacheObjectMetadata = true; + + + /// + /// Names of object that have already been created at least once + /// + private Spring.Collections.Generic.ISet alreadyCreated = new SynchronizedSet(new HashedSet()); + #region Constructor (s) / Destructor /// @@ -623,11 +645,26 @@ namespace Spring.Objects.Factory.Support /// Return a , /// even by traversing parent if the parameter is a child definition. /// + /// The name. + /// The od. /// /// A merged /// with overridden properties. /// protected internal virtual RootObjectDefinition GetMergedObjectDefinition(string name, IObjectDefinition od) + { + return GetMergedLocalObjectDefinition(name) ?? GetMergedObjectDefinitionInternal(name, od); + } + + /// + /// Return a , + /// even by traversing parent if the parameter is a child definition. + /// + /// + /// A merged + /// with overridden properties. + /// + protected internal virtual RootObjectDefinition GetMergedObjectDefinitionInternal(string name, IObjectDefinition od) { if (od == null) { @@ -636,40 +673,99 @@ namespace Spring.Objects.Factory.Support RootObjectDefinition mod; - if (od.ParentName == null) + // Check with full lock now in order to enforce the same merged instance. + lock (this.mergedObjectDefinitions.SyncRoot) { - mod = CreateRootObjectDefinition(od); - } - else - { - // IObjectDefinition childDefinition = definition; - IObjectDefinition pod = null; - if (!name.Equals(od.ParentName)) + mod = this.mergedObjectDefinitions[name] as RootObjectDefinition; + + if (null != mod) { - pod = GetMergedObjectDefinition(TransformedObjectName(od.ParentName), true); + return mod; + } + + + if (od.ParentName == null) + { + mod = CreateRootObjectDefinition(od); } else { - if (ParentObjectFactory is AbstractObjectFactory) + IObjectDefinition pod = null; + if (!name.Equals(od.ParentName)) { - pod = ((AbstractObjectFactory)ParentObjectFactory).GetMergedObjectDefinition(od.ParentName, true); + pod = GetMergedObjectDefinition(TransformedObjectName(od.ParentName), true); } + else + { + if (ParentObjectFactory is AbstractObjectFactory) + { + pod = ((AbstractObjectFactory)ParentObjectFactory).GetMergedObjectDefinition( + od.ParentName, true); + } + } + + if (pod == null) + { + throw new NoSuchObjectDefinitionException(od.ParentName, + string.Format( + "Parent name '{0}' is equal to object name '{1}' - " + + + "cannot be resolved without an AbstractObjectFactory parent.", + od.ParentName, name)); + } + + mod = CreateRootObjectDefinition(pod); + mod.OverrideFrom(od); } - if (pod == null) + // Only cache the merged bean definition if we're already about to create an + // instance of the object, or at least have already created an instance before. + if (CacheObjectMetadata && IsObjectEligibleForMetadataCaching(name)) { - throw new NoSuchObjectDefinitionException(od.ParentName, - string.Format( - "Parent name '{0}' is equal to object name '{1}' - " - + - "cannot be resolved without an AbstractObjectFactory parent.", - od.ParentName, name)); + this.mergedObjectDefinitions.Remove(name); + this.mergedObjectDefinitions.Add(name, mod); } - mod = CreateRootObjectDefinition(pod); - mod.OverrideFrom(od); + return mod; + } //release the lock scope + } + + /// + /// Gets the merged local object definition. + /// + /// Name of the object. + /// + /// Merged RootBeanDefinition, traversing the parent bean definition + /// if the specified bean corresponds to a child bean definition. + /// + protected RootObjectDefinition GetMergedLocalObjectDefinition(string objectName) + { + // Quick check on the concurrent map first, with minimal locking. + RootObjectDefinition mbd = null; + + if (this.mergedObjectDefinitions.ContainsKey(objectName)) + { + mbd = this.mergedObjectDefinitions[objectName] as RootObjectDefinition; } - return mod; + + return mbd; // ?? GetMergedObjectDefinition(objectName, GetObjectDefinition(objectName)); + } + + /// + /// Determines whether the metadata for the specified object name is eligible for caching. + /// + /// Name of the bean. + /// + /// true if [is object eligible for metadata caching] [the specified bean name]; otherwise, false. + /// + protected bool IsObjectEligibleForMetadataCaching(String beanName) + { + return this.alreadyCreated.Contains(beanName); + } + + protected bool CacheObjectMetadata + { + get { return this.cacheObjectMetadata; } } /// @@ -1233,8 +1329,8 @@ namespace Spring.Objects.Factory.Support /// /// More specifically, check whether a GetObject call for the given name /// would return an object that is assignable to the specified target type. - /// Translates aliases back to the corresponding canonical bean name. - /// Will ask the parent factory if the bean cannot be found in this factory instance. + /// Translates aliases back to the corresponding canonical instance name. + /// Will ask the parent factory if the instance cannot be found in this factory instance. /// /// The name of the object to query. /// Type of the target to match against. @@ -1314,8 +1410,8 @@ namespace Spring.Objects.Factory.Support /// /// More specifically, check whether a GetObject call for the given name /// would return an object that is assignable to the specified target type. - /// Translates aliases back to the corresponding canonical bean name. - /// Will ask the parent factory if the bean cannot be found in this factory instance. + /// Translates aliases back to the corresponding canonical instance name. + /// Will ask the parent factory if the instance cannot be found in this factory instance. /// /// The name of the object to query. /// Type of the target to match against. @@ -1624,7 +1720,7 @@ namespace Spring.Objects.Factory.Support private OrderedDictionary singletonLocks; /// - /// Set of registered singletons, containing the bean names in registration order + /// Set of registered singletons, containing the instance names in registration order /// private HashSet registeredSingletons = new HashSet(); @@ -1664,7 +1760,7 @@ namespace Spring.Objects.Factory.Support } /// - /// Determines whether the local object factory contains a bean of the given name, + /// Determines whether the local object factory contains a instance of the given name, /// ignoring object defined in ancestor contexts. /// This is an alternative to ContainsObject, ignoring an object /// of the given name from an ancestor object factory. @@ -2194,6 +2290,15 @@ namespace Spring.Objects.Factory.Support } } + try + { + RegisterDisposableObjectIfNecessary(name, instance, mergedObjectDefinition); + } + catch (ObjectDefinitionValidationException ex) + { + throw new ObjectCreationException(mergedObjectDefinition.ResourceDescription, name, "Invalid destruction signature", ex); + } + return EnsureObjectIsOfRequiredType(name, instance, requiredType); } catch @@ -2237,6 +2342,34 @@ namespace Spring.Objects.Factory.Support } } + protected void RegisterDisposableObjectIfNecessary(string name, object instance, RootObjectDefinition od) + { + if (od.IsSingleton && RequiresDestruction(instance, od)) + { + // Register a DisposableObject implementation that performs all destruction + // work for the given object: DestructionAwareObjectPostProcessors, + // DisposableObject interface, custom destroy method. + RegisterDisposableObject(name, + new DisposableObjectAdapter(instance, name, od, objectPostProcessors)); + + } + } + + /// + /// Requireses the destruction. + /// + /// The instance to check. + /// The corresponding instance definition. + /// + /// Boolean indicating whether destruction is required. + /// + + protected bool RequiresDestruction(object instance, RootObjectDefinition od) + { + return (instance != null && + (instance is IDisposable || od.DestroyMethodName != null || HasDestructionAwareBeanPostProcessors)); + } + private int nestingCount; /// @@ -2507,17 +2640,17 @@ namespace Spring.Objects.Factory.Support if (name == alias) { #region Instrumentation - + if (log.IsDebugEnabled) { log.Debug(string.Format("Ignoring attempt to Register alias '{0}' for object with name '{1}' because name and alias would be the same value.", alias, name)); } - + #endregion return; } - + #region Instrumentation if (log.IsDebugEnabled) @@ -2597,7 +2730,7 @@ namespace Spring.Objects.Factory.Support /// /// /// Only checks already instantiated singletons; does not return names - /// for singleton bean definitions which have not been instantiated yet. + /// for singleton instance definitions which have not been instantiated yet. /// /// /// The main purpose of this method is to check manually registered singletons @@ -2666,6 +2799,23 @@ namespace Spring.Objects.Factory.Support #endregion + /// + /// Registers the disposable object. + /// + /// Name of the instance. + /// The instance. + /// Add the given instance to the list of disposable beans in this registry. + /// Disposable beans usually correspond to registered singletons, + /// matching the instance name but potentially being a different instance + /// (for example, a DisposableBean adapter for a singleton that does not + /// naturally implement ). + public void RegisterDisposableObject(String objectName, IDisposable instance) + { + disposableObjects.Add(objectName, instance); + } + + + /// /// Determines whether the given object name is already in use within this factory, /// i.e. whether there is a local object or alias registered under this name or diff --git a/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs b/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs index 81419945..f1504089 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs @@ -1287,7 +1287,7 @@ namespace Spring.Objects.Factory.Support fallbackObjectName = candidateBeanName; } } - return (primaryObjectName != null ? primaryObjectName : fallbackObjectName); + return (primaryObjectName ?? fallbackObjectName); } /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Support/DisposableObjectAdapter.cs b/src/Spring/Spring.Core/Objects/Factory/Support/DisposableObjectAdapter.cs new file mode 100644 index 00000000..e1ec4c39 --- /dev/null +++ b/src/Spring/Spring.Core/Objects/Factory/Support/DisposableObjectAdapter.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Common.Logging; +using Spring.Collections; +using Spring.Objects.Factory.Config; +using Spring.Util; + +namespace Spring.Objects.Factory.Support +{ + public class DisposableObjectAdapter : IDisposable + { + private readonly ILog logger = LogManager.GetLogger(typeof(DisposableObjectAdapter)); + + private object instance; + + private string objectName; + + private bool invokeDisposableObject; + + private string destroyMethodName; + + private MethodInfo destroyMethod; + + private List objectPostProcessors; + + /// + /// Disposables the bean adapter. + /// + /// The bean. + /// Name of the bean. + /// The bean definition. + /// The post processors. + /// The acc. + /// Create a new DisposableBeanAdapter for the given bean. + /// @param bean the bean instance (never + /// null + /// ) + /// @param beanName the name of the bean + /// @param beanDefinition the merged bean definition + /// @param postProcessors the List of BeanPostProcessors + /// (potentially DestructionAwareBeanPostProcessor), if any + + public DisposableObjectAdapter(object instance, string objectName, RootObjectDefinition objectDefinition, ISet postProcessors) + { + AssertUtils.ArgumentNotNull(instance, "Disposable object must not be null"); + + this.instance = instance; + this.objectName = objectName; + this.invokeDisposableObject = (this.instance is IDisposable); // && !beanDefinition.IsExternallyManagedDestroyMethod("destroy")); + + if (null == objectDefinition) + { + return; + } + + InferDestroyMethodIfNecessary(objectDefinition); + + string definedDestroyMethodName = objectDefinition.DestroyMethodName; + + if (definedDestroyMethodName != null && !(this.invokeDisposableObject && "Destroy".Equals(definedDestroyMethodName))) // && !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) + { + this.destroyMethodName = definedDestroyMethodName; + this.destroyMethod = DetermineDestroyMethod(); + if (this.destroyMethod == null) + { + //TODO: add support for Enforcing Destroy Method + //if (beanDefinition.IsEnforceDestroyMethod()) { + // throw new BeanDefinitionValidationException("Couldn't find a destroy method named '" + + // destroyMethodName + "' on bean with name '" + beanName + "'"); + //} + } + else + { + Type[] paramTypes = ReflectionUtils.GetParameterTypes(this.destroyMethod); + if (paramTypes.Length > 1) + { + throw new ObjectDefinitionValidationException("Method '" + definedDestroyMethodName + "' of object '" + + objectName + "' has more than one parameter - not supported as Destroy Method"); + } + else if (paramTypes.Length == 1 && !(paramTypes[0] == typeof(bool))) + { + throw new ObjectDefinitionValidationException("Method '" + definedDestroyMethodName + "' of object '" + + objectName + "' has a non-boolean parameter - not supported as Destroy Method"); + } + } + } + this.objectPostProcessors = FilterPostProcessors(postProcessors); + } + + /** + * If the current value of the given beanDefinition's destroyMethodName property is + * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method. + * Candidate methods are currently limited to public, no-arg methods named 'close' + * (whether declared locally or inherited). The given beanDefinition's + * destroyMethodName is updated to be null if no such method is found, otherwise set + * to the name of the inferred method. This constant serves as the default for the + * {@code @Bean#customDestroyMethod} attribute and the value of the constant may also be + * used in XML within the {@code } or {@code + * } attributes. + */ + private void InferDestroyMethodIfNecessary(RootObjectDefinition beanDefinition) + { + if ("(Inferred)".Equals(beanDefinition.DestroyMethodName)) + { + try + { + MethodInfo candidate = ReflectionUtils.GetMethod(instance.GetType(), "Close", null); + if (candidate.IsPublic) + { + beanDefinition.DestroyMethodName = candidate.Name; + } + } + catch (MissingMethodException ex) + { + // no candidate destroy method found + beanDefinition.DestroyMethodName = null; + } + } + } + + /// + /// Filters the post processors. + /// + /// The post processors. + /// + /// Search for all DestructionAwareBeanPostProcessors in the List. + /// @param postProcessors the List to search + /// @return the filtered List of DestructionAwareBeanPostProcessors + private List FilterPostProcessors(ISet postProcessors) + { + List filteredPostProcessors = null; + if (postProcessors != null && postProcessors.Count != 0) + { + filteredPostProcessors = new List(postProcessors.Count); + filteredPostProcessors.AddRange(postProcessors.OfType()); + } + return filteredPostProcessors; + } + + + + public void Dispose() + { + if (this.objectPostProcessors != null && this.objectPostProcessors.Count != 0) + { + foreach (IDestructionAwareObjectPostProcessor processor in this.objectPostProcessors) + { + try + { + processor.PostProcessBeforeDestruction(this.instance, this.objectName); + } + + catch (Exception ex) + { + logger.ErrorFormat( + string.Format("Error during execution of {0}.PostProcessBeforeDestruction for object {1}", + processor.GetType().Name, this.objectName), ex); + } + } + } + + if (this.invokeDisposableObject) + { + if (logger.IsDebugEnabled) + { + logger.Debug("Invoking Dispose() on object with name '" + this.objectName + "'"); + } + try + { + ((IDisposable)instance).Dispose(); + + } + + catch (Exception ex) + { + string msg = "Invocation of Dispose method failed on object with name '" + this.objectName + "'"; + if (logger.IsDebugEnabled) + { + logger.Warn(msg, ex); + } + else + { + logger.Warn(msg + ": " + ex); + } + } + } + + if (this.destroyMethod != null) + { + InvokeCustomDestroyMethod(this.destroyMethod); + } + else if (this.destroyMethodName != null) + { + MethodInfo methodToCall = DetermineDestroyMethod(); + if (methodToCall != null) + { + InvokeCustomDestroyMethod(methodToCall); + } + } + } + + + private MethodInfo DetermineDestroyMethod() + { + try + { + return FindDestroyMethod(); + } + + catch (ArgumentException ex) + { + throw new ObjectDefinitionValidationException("Couldn't find a unique Destroy Method on object with name '" + + this.objectName + ": " + ex.Message); + } + } + + private MethodInfo FindDestroyMethod() + { + try + { + return ReflectionUtils.GetMethod(instance.GetType(), this.destroyMethodName, null); + } + catch (Exception) + { + return null; + } + } + + /// + /// Invokes the custom destroy method. + /// + /// The custom destroy method. + /// Invoke the specified custom destroy method on the given bean. + /// This implementation invokes a no-arg method if found, else checking + /// for a method with a single boolean argument (passing in "true", + /// assuming a "force" parameter), else logging an error. + private void InvokeCustomDestroyMethod(MethodInfo customDestroyMethod) + { + Type[] paramTypes = ReflectionUtils.GetParameterTypes(customDestroyMethod); + object[] args = new object[paramTypes.Length]; + if (paramTypes.Length == 1) + { + args[0] = true; + } + if (logger.IsDebugEnabled) + { + logger.Debug("Invoking destroy method '" + this.destroyMethodName + + "' on object with name '" + this.objectName + "'"); + } + try + { + + customDestroyMethod.Invoke(instance, args); + } + catch (TargetInvocationException ex) + { + string msg = "Invocation of destroy method '" + this.destroyMethodName + + "' failed on object with name '" + this.objectName + "'"; + if (logger.IsDebugEnabled) + { + logger.Warn(msg, ex.InnerException); + } + else + { + logger.Warn(msg + ": " + ex.InnerException); + } + } + catch (Exception ex) + { + logger.Error("Couldn't invoke destroy method '" + this.destroyMethodName + + "' on object with name '" + this.objectName + "'", ex); + } + } + + + /** + * Serializes a copy of the state of this class, + * filtering out non-serializable BeanPostProcessors. + */ + //protected Object writeReplace() { + // List serializablePostProcessors = null; + // if (this.objectPostProcessors != null) { + // serializablePostProcessors = new ArrayList(); + // for (DestructionAwareBeanPostProcessor postProcessor : this.objectPostProcessors) { + // if (postProcessor instanceof Serializable) { + // serializablePostProcessors.add(postProcessor); + // } + // } + // } + // return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableObject, + // this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors); + //} + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Core/Spring.Core.2010.csproj b/src/Spring/Spring.Core/Spring.Core.2010.csproj index 6daba345..ac306901 100644 --- a/src/Spring/Spring.Core/Spring.Core.2010.csproj +++ b/src/Spring/Spring.Core/Spring.Core.2010.csproj @@ -723,6 +723,7 @@ + diff --git a/src/Spring/Spring.Core/Util/ReflectionUtils.cs b/src/Spring/Spring.Core/Util/ReflectionUtils.cs index 9027f7d3..240ea2c1 100644 --- a/src/Spring/Spring.Core/Util/ReflectionUtils.cs +++ b/src/Spring/Spring.Core/Util/ReflectionUtils.cs @@ -160,9 +160,9 @@ namespace Spring.Util Type[] parameterTypes = Array.ConvertAll(candidate.GetParameters(), delegate(ParameterInfo i) { return i.ParameterType; }); bool typesMatch = false; - bool zeroTypeArguments = argumentTypes.Length == 0; + bool zeroTypeArguments = null == argumentTypes || argumentTypes.Length == 0; - if (parameterTypes.Length == argumentTypes.Length && !zeroTypeArguments) + if (!zeroTypeArguments && parameterTypes.Length == argumentTypes.Length) { for (int i = 0; i < parameterTypes.Length; i++) {