#region License
/*
* Copyright 2002-2005 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.
*/
#endregion
#region Imports
using System;
using System.Globalization;
using System.Threading;
using AopAlliance.Intercept;
using Spring.Aop.Support;
using Spring.Collections;
using Spring.Util;
#endregion
namespace Spring.Aop.Target{
/// <summary>
/// <see cref="Spring.Aop.ITargetSource"/> implementation that uses a
/// threading model in which every thread has its own copy of the target.
/// </summary>
/// <remarks>
/// <p>
/// Alternative to an object pool.
/// </p>
/// <p>
/// Application code is written as to a normal pool; callers can't assume
/// they will be dealing with the same instance in invocations in different
/// threads. However, state can be relied on during the operations of a
/// single thread: for example, if one caller makes repeated calls on the
/// AOP proxy.
/// </p>
/// <p>
/// This class act both as an introduction and as an interceptor, so it
/// should be added twice, once as an introduction and once as an
/// interceptor.
/// </p>
/// </remarks>
/// <author>Rod Johnson</author>
/// <author>Federico Spinazzi (.NET)</author>
public sealed class ThreadLocalTargetSource : AbstractPrototypeTargetSource,
IThreadLocalTargetSourceStats, IDisposable, IMethodInterceptor
{
#region Fields
/// <summary>
/// ThreadLocal holding the target associated with the current thread.
/// </summary>
/// <remarks>
/// <p>
/// Unlike most thread local storage which is static, this variable is
/// meant to be per thread per instance of this class.
/// </p>
/// </remarks>
private LocalDataStoreSlot _targetInThread = Thread.AllocateDataSlot();
/// <summary>
/// The set of managed targets, enabling us to keep track of the
/// targets we've created.
/// </summary>
private ISet _targetSet = new ListSet();
private int _invocations;
private int _hits;
#endregion
#region Properties
/// <summary>
/// Gets the number of invocations of the <see cref="GetTarget()"/> and
/// <see cref="Invoke(IMethodInvocation)"/> methods.
/// </summary>
/// <value>
/// The number of invocations of the <see cref="GetTarget()"/> and
/// <see cref="Invoke(IMethodInvocation)"/> methods.
/// </value>
public int Invocations
{
get { return _invocations; }
}
/// <summary>
/// Gets the number of hits that were satisfied by a thread bound object.
/// </summary>
/// <value>
/// The number of hits that were satisfied by a thread bound object.
/// </value>
public int Hits
{
get { return _hits; }
}
/// <summary>
/// Gets the number of thread bound objects created.
/// </summary>
/// <value>The number of thread bound objects created.</value>
public int Objects
{
get { return _targetSet.Count; }
}
private object ThreadBoundTarget
{
get { return Thread.GetData(_targetInThread); }
}
#endregion
#region Methods
/// <summary>
/// Returns the target object.
/// </summary>
/// <remarks>
/// <p>
/// Tries to locate the target from thread local storage. If no target
/// is found, a target will be obtained and bound to the thread.
/// </p>
/// </remarks>
/// <returns>The target object.</returns>
/// <exception cref="System.Exception">
/// If unable to obtain the target object.
/// </exception>
/// <seealso cref="Spring.Aop.Target.AbstractPrototypeTargetSource.NewPrototypeInstance"/>
public override Object GetTarget()
{
++_invocations;
object target = ThreadBoundTarget;
if (target == null)
{
#region Instrumentation
if (logger.IsDebugEnabled)
{
logger.Debug(string.Format(
"No target for apartment prototype '{0}' " +
"found in thread: creating one and binding it to thread '#{1}'",
TargetObjectName, Thread.CurrentThread.GetHashCode()));
}
#endregion
target = NewPrototypeInstance();
Thread.SetData(_targetInThread, target);
_targetSet.Add(target);
}
else
{
++_hits;
}
return ThreadBoundTarget;
}
/// <summary>
/// Return an introduction advisor mixin that allows the AOP proxy to be
/// cast to an <see cref="IThreadLocalTargetSourceStats"/> reference.
/// </summary>
public IIntroductionAdvisor GetStatsMixin()
{
return new DefaultIntroductionAdvisor(this, typeof (IThreadLocalTargetSourceStats));
}
/// <summary>
/// Cleans up this instance's thread storage, and disposes of any
/// <see cref="System.IDisposable"/> targets as necessary.
/// </summary>
/// <seealso cref="System.IDisposable.Dispose"/>
public void Dispose()
{
#region Instrumentation
if (logger.IsDebugEnabled)
{
logger.Debug("Destroying ThreadLocal bindings");
}
#endregion
foreach (object target in _targetSet)
{
if (target is IDisposable)
{
try
{
((IDisposable) target).Dispose();
}
catch (Exception ex)
{
#region Instrumentation
if (logger.IsWarnEnabled)
{
logger.Warn(string.Format(
"Thread-bound target of class '{0}' " +
"threw exception from it's IDisposable.Dispose() method.",
target.GetType()), ex);
}
#endregion
}
}
}
_targetSet.Clear();
_targetInThread = null;
}
/// <summary>
/// Increments Invocations and Hits statistics
/// </summary>
/// <param name="invocation">The method invocation joinpoint</param>
/// <returns>The result of the call to IJoinpoint.Proceed(), might be intercepted by the interceptor.</returns>
/// <exception cref="System.Exception">if the interceptors or the target-object throws an exception.</exception>
public object Invoke(IMethodInvocation invocation)
{
if (ReflectionUtils.MethodIsOnOneOfTheseInterfaces(
invocation.Method, new Type[] {typeof (IThreadLocalTargetSourceStats)}))
{
return invocation.Method.Invoke(this, invocation.Arguments);
}
return invocation.Method.Invoke(GetTarget(), invocation.Arguments);
}
#endregion
}
}
|