Merge pull request #26 from thomast74/InitDestroyAttribute

SPRNET-1518 Add InitDestroyPostProcessor to allow init and destroy via attributes
This commit is contained in:
Steve Bohlen
2012-09-13 06:50:51 -07:00
9 changed files with 798 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
#region License
/*
* Copyright © 2010-2011 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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Common.Logging;
using Spring.Objects;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Config;
using Spring.Core;
namespace Spring.Objects.Factory.Attributes
{
/// <summary>
/// <see cref="InitDestroyAttributeObjectPostProcessor"/> implementation
/// that invokes attributed init and destroy methods. Allows for an attributation
/// alternative to Spring's <see cref="IInitializingObject"/> and
/// <see cref="IDisposable"/> callback interfaces.
///
/// Invoke and destroy annotations may be applied to methods of any visibility:
/// public, protected, or private. Multiple such methods
/// may be annotated, but it is recommended to only annotate one single
/// init method and destroy method, respectively.
/// </summary>
public class InitDestroyAttributeObjectPostProcessor : IDestructionAwareObjectPostProcessor, IObjectFactoryAware, IOrdered
{
private static readonly ILog Logger = LogManager.GetLogger<InitDestroyAttributeObjectPostProcessor>();
private IConfigurableListableObjectFactory _objectFactory;
private readonly IDictionary<string, LifecycleLifecycleMetadata> _lifecycleMetadataCache;
private int _order = int.MaxValue;
private Type _initAttributeType;
private Type _destroyAttributeType;
/// <summary>
/// Return the order value of this object, where a higher value means greater in
/// terms of sorting.
/// </summary>
/// <remarks>
/// <p>Normally starting with 0 or 1, with <see cref="F:System.Int32.MaxValue"/> indicating
/// greatest. Same order values will result in arbitrary positions for the affected
/// objects.
/// </p><p>Higher value can be interpreted as lower priority, consequently the first object
/// has highest priority.
/// </p>
/// </remarks>
/// <returns>
/// The order value.
/// </returns>
public int Order { get { return _order; } private set { _order = value; } }
/// <summary>
/// Specify the init attribute to check for, indicating initialization
/// methods to call after configuration of an object.
/// </summary>
public Type InitAttributeType { get { return _initAttributeType; } set { _initAttributeType = value; } }
/// <summary>
/// Specify the destroy attribute to check for, indicating disposal
/// methods to call before object is destroyed
/// </summary>
public Type DestroyAttributeType { get { return _destroyAttributeType; } set { _destroyAttributeType = value; } }
/// <summary>
///
/// </summary>
public IObjectFactory ObjectFactory
{
set { _objectFactory = value as IConfigurableListableObjectFactory; }
}
/// <summary>
/// Creates InitDestroy Post Processor with default attribute types of
/// <see cref="PostConstructAttribute"/>
/// </summary>
public InitDestroyAttributeObjectPostProcessor()
{
_initAttributeType = typeof (PostConstructAttribute);
_destroyAttributeType = typeof (PreDestroyAttribute);
_lifecycleMetadataCache = new Dictionary<string, LifecycleLifecycleMetadata>();
}
/// <summary>
/// Applies PostConstruct init method initialisation if instance is attributed
/// </summary>
/// <param name="instance">The new object instance.
/// </param><param name="name">The name of the object.
/// </param>
/// <returns>
/// The object instance to use, either the original or a wrapped one.
/// </returns>
/// <exception cref="T:Spring.Objects.ObjectsException">In case of errors.
/// </exception>
public object PostProcessBeforeInitialization(object instance, string name)
{
try
{
var metadata = FindLifecycleMetadata(instance.GetType(), name);
metadata.InvokeInitMethods(instance, name);
}
catch (Exception e)
{
throw new ObjectCreationException(name, "Couldn't invoke init method", e);
}
return instance;
}
/// <summary>
/// No special post processing after initialization
/// </summary>
public object PostProcessAfterInitialization(object instance, string objectName)
{
return instance;
}
/// <summary>
/// Executed PreDestroy methods in given order for provided instance
/// </summary>
/// <param name="instance">The new object instance.</param><param name="name">The name of the object.</param><exception cref="T:Spring.Objects.ObjectsException">In case of errors.
/// </exception>
public void PostProcessBeforeDestruction(object instance, string name)
{
try
{
var metadata = FindLifecycleMetadata(instance.GetType(), name);
metadata.InvokeDestroyMethods(instance, name);
}
catch (Exception e)
{
throw new ObjectsException("Couldn't invoke destroy method", e);
}
}
private LifecycleLifecycleMetadata FindLifecycleMetadata(Type instanceType, string name)
{
if (_lifecycleMetadataCache.ContainsKey(name))
return _lifecycleMetadataCache[name];
lock (_lifecycleMetadataCache)
{
var metadata = BuildLifecycleMetadata(instanceType, name);
_lifecycleMetadataCache.Add(name, metadata);
return metadata;
}
}
private LifecycleLifecycleMetadata BuildLifecycleMetadata(Type instanceType, string name)
{
var initMethods = new List<LifecycleElement>();
var destroyMethods = new List<LifecycleElement>();
do
{
var curInitMethods = new List<LifecycleElement>();
var curDestroyMethods = new List<LifecycleElement>();
var methods = instanceType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
foreach (var methodInfo in methods)
{
var initAttribute =
Attribute.GetCustomAttribute(methodInfo, _initAttributeType) as PostConstructAttribute;
if (initAttribute != null && methodInfo.DeclaringType == instanceType)
{
Logger.Debug(m => m("Found init method on class [{0}]: {1}", instanceType.Name, methodInfo.Name));
curInitMethods.Add(new LifecycleElement(methodInfo, initAttribute.Order));
}
var destroyAttribute =
Attribute.GetCustomAttribute(methodInfo, _destroyAttributeType) as PreDestroyAttribute;
if (destroyAttribute != null && methodInfo.DeclaringType == instanceType)
{
Logger.Debug(m => m("Found destroy method on class [{0}]: {1}", instanceType.Name, methodInfo.Name));
curDestroyMethods.Add(new LifecycleElement(methodInfo, destroyAttribute.Order));
}
}
initMethods.InsertRange(0, curInitMethods.OrderBy(e => e.Order));
destroyMethods.InsertRange(0, curDestroyMethods.OrderBy(e => e.Order));
instanceType = instanceType.BaseType;
}
while (instanceType != null && instanceType != typeof(Object));
var objectDef = _objectFactory.GetObjectDefinition(name);
var metadata = new LifecycleLifecycleMetadata(initMethods, destroyMethods);
metadata.CheckConfigMembers(objectDef);
return metadata;
}
private class LifecycleLifecycleMetadata
{
private readonly IList<LifecycleElement> _initMethods;
private readonly IList<LifecycleElement> _destroyMethods;
public LifecycleLifecycleMetadata(IList<LifecycleElement> initMethods, IList<LifecycleElement> destroyMethods)
{
_initMethods = initMethods;
_destroyMethods = destroyMethods;
}
public void CheckConfigMembers(IObjectDefinition objectDef)
{
lock (_initMethods)
{
for (int i = 0; i < _initMethods.Count; i++)
{
if (!_initMethods[i].CheckConfig(objectDef))
_initMethods.Remove(_initMethods[i]);
}
}
lock (_destroyMethods)
{
for (int i = 0; i < _destroyMethods.Count; i++)
{
if (!_destroyMethods[i].CheckConfig(objectDef))
_destroyMethods.Remove(_destroyMethods[i]);
}
}
}
public void InvokeInitMethods(object instance, string objectName)
{
foreach (var lifecycleElement in _initMethods)
{
lifecycleElement.Invoke(instance, objectName);
}
}
public void InvokeDestroyMethods(object instance, string objectName)
{
foreach (var lifecycleElement in _destroyMethods)
{
lifecycleElement.Invoke(instance, objectName);
}
}
}
private class LifecycleElement
{
private readonly MethodInfo _method;
private readonly int _order;
public int Order { get { return _order; } }
public LifecycleElement(MethodInfo method, int order)
{
_method = method;
_order = order;
}
public bool CheckConfig(IObjectDefinition objectDef)
{
if (_method.Name == objectDef.InitMethodName || _method.Name == objectDef.DestroyMethodName)
return false;
return true;
}
public void Invoke(object instance, string objectName)
{
Logger.Debug(m => m("Invoking init method on object '" + objectName + "': " + _method.Name));
_method.Invoke(instance, new object[] {});
}
}
}
}

View File

@@ -0,0 +1,62 @@
#region License
/*
* Copyright © 2010-2011 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
using System;
namespace Spring.Objects.Factory.Attributes
{
/// <summary>
/// Defines a method that will be called during the intantiation of an instance
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class PostConstructAttribute : Attribute
{
private int _order;
/// <summary>
/// Initializes a new instance of the PostConstruct class with order = 1
/// </summary>
public PostConstructAttribute()
{
_order = int.MaxValue;
}
/// <summary>
/// Initializes a new instance of the PostConstruct class with defined order
/// </summary>
/// <param name="order">Order in which the PostContruct method is called</param>
public PostConstructAttribute(int order)
{
_order = order;
}
/// <summary>
/// Defined the order in which the PostContruct methods are called
/// </summary>
public int Order
{
get { return _order; }
set { _order = value; }
}
}
}

View File

@@ -0,0 +1,61 @@
#region License
/*
* Copyright © 2010-2011 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
using System;
namespace Spring.Objects.Factory.Attributes
{
/// <summary>
/// Defines a method that will be called prior to the destruction of the object instance
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class PreDestroyAttribute : Attribute
{
private int _order;
/// <summary>
/// Initializes a new instance of the PreDestroy attribute with order = 1
/// </summary>
public PreDestroyAttribute()
{
_order = int.MaxValue;
}
/// <summary>
/// Initializes a new instance of the PreDestroy attribute with defined order
/// </summary>
/// <param name="order">Order in which the PostContruct method is called</param>
public PreDestroyAttribute(int order)
{
_order = order;
}
/// <summary>
/// Defined the order in which the PreDestroy methods are called
/// </summary>
public int Order
{
get { return _order; }
set { _order = value; }
}
}
}

View File

@@ -673,6 +673,9 @@
<Compile Include="Globalization\Resource.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Objects\Factory\Attributes\InitDestroyAttributeObjectPostProcessor.cs" />
<Compile Include="Objects\Factory\Attributes\PostConstructAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\PreDestroyAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredAttributeObjectPostProcessor.cs" />
<Compile Include="Objects\Factory\Config\AbstractConfigurer.cs" />

View File

@@ -675,6 +675,9 @@
<Compile Include="Globalization\Resource.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Objects\Factory\Attributes\InitDestroyAttributeObjectPostProcessor.cs" />
<Compile Include="Objects\Factory\Attributes\PostConstructAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\PreDestroyAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredAttributeObjectPostProcessor.cs" />
<Compile Include="Objects\Factory\Config\AbstractConfigurer.cs" />

View File

@@ -0,0 +1,178 @@
#region License
/*
* Copyright © 2002-2011 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
using NUnit.Framework;
using Spring.Context.Support;
using Spring.Objects.Factory.Config;
using Spring.Objects.Factory.Support;
namespace Spring.Objects.Factory.Attributes
{
[TestFixture]
public class PostConstructAttributeTests
{
private GenericApplicationContext _applicationContext;
[SetUp]
public void Setup()
{
_applicationContext = new GenericApplicationContext();
var objDef = new RootObjectDefinition(typeof (InitDestroyAttributeObjectPostProcessor));
objDef.Role = ObjectRole.ROLE_INFRASTRUCTURE;
_applicationContext.ObjectFactory.RegisterObjectDefinition("InitDestroyAttributeObjectPostProcessor", objDef);
objDef = new RootObjectDefinition(typeof(PostContructTestObject1));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PostContructTestObject1", objDef);
objDef = new RootObjectDefinition(typeof(PostContructTestObject2));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PostContructTestObject2", objDef);
objDef = new RootObjectDefinition(typeof(PostContructTestObject3));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PostContructTestObject3", objDef);
objDef = new RootObjectDefinition(typeof(PostContructTestObject4));
objDef.Scope = "prototype";
_applicationContext.ObjectFactory.RegisterObjectDefinition("PostContructTestObject4", objDef);
objDef = new RootObjectDefinition(typeof(PostContructTestObject5));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PostContructTestObject5", objDef);
objDef = new RootObjectDefinition(typeof(PostContructTestObject1));
objDef.InitMethodName = "Init1";
_applicationContext.ObjectFactory.RegisterObjectDefinition("PostContructTestObject6", objDef);
_applicationContext.Refresh();
}
[Test]
public void PostContructMethodExecuted()
{
var testObj = (PostContructTestObject1)_applicationContext.GetObject("PostContructTestObject1");
Assert.That(testObj.InitCalled, Is.EqualTo(1));
}
[Test]
public void ExecutedInCorrectOrder()
{
var testObj = (PostContructTestObject2)_applicationContext.GetObject("PostContructTestObject2");
Assert.That(testObj.InitCalled, Is.EqualTo(2), "Two PostContruct methods defined, need to have two method calls.");
Assert.That(testObj.CalledAfter, Is.True, "Order of PostConstruct not followed.");
}
[Test]
public void NoExceptionIfMethodHasReturnValue()
{
var testObj = (PostContructTestObject3)_applicationContext.GetObject("PostContructTestObject3");
Assert.That(testObj.InitCalled, Is.EqualTo(1), "A PostContruct method with return value should run the init method.");
}
[Test]
public void WithArgumentMustThrowException()
{
Assert.That(delegate { _applicationContext.GetObject("PostContructTestObject4"); }, Throws.Exception.TypeOf<ObjectCreationException>());
}
[Test]
public void AttributeOnbaseTypeMethod()
{
var testObj = (PostContructTestObject5)_applicationContext.GetObject("PostContructTestObject5");
Assert.That(testObj.InitCalled, Is.EqualTo(1));
}
[Test]
public void SameMethodDefinedInXml()
{
var testObj = (PostContructTestObject1)_applicationContext.GetObject("PostContructTestObject6");
Assert.That(testObj.InitCalled, Is.EqualTo(1));
}
}
public class PostContructTestObject1
{
public int InitCalled { get; set; }
[PostConstruct]
public void Init1()
{
InitCalled++;
}
}
public class PostContructTestObject2
{
public int InitCalled { get; set; }
public bool Init1Called { get; set; }
public bool CalledAfter { get; set; }
[PostConstruct(Order = 1)]
public void Init1()
{
InitCalled++;
Init1Called = true;
}
[PostConstruct(Order = 2)]
public void Init2()
{
InitCalled++;
if (Init1Called)
CalledAfter = true;
}
}
public class PostContructTestObject3
{
public int InitCalled { get; set; }
[PostConstruct]
private bool Init1()
{
InitCalled++;
return true;
}
}
public class PostContructTestObject4
{
public int InitCalled { get; set; }
[PostConstruct]
private void Init1(bool enabled)
{
InitCalled++;
}
}
public class PostContructTestObject5 : PostContructTestObject1
{
}
}

View File

@@ -0,0 +1,191 @@
#region License
/*
* Copyright © 2002-2011 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
using NUnit.Framework;
using Spring.Context.Support;
using Spring.Objects.Factory.Config;
using Spring.Objects.Factory.Support;
namespace Spring.Objects.Factory.Attributes
{
[TestFixture]
public class PreDestroyAttributeTests
{
private GenericApplicationContext _applicationContext;
[SetUp]
public void Setup()
{
_applicationContext = new GenericApplicationContext();
var objDef = new RootObjectDefinition(typeof(InitDestroyAttributeObjectPostProcessor));
objDef.Role = ObjectRole.ROLE_INFRASTRUCTURE;
_applicationContext.ObjectFactory.RegisterObjectDefinition("InitDestroyAttributeObjectPostProcessor", objDef);
objDef = new RootObjectDefinition(typeof(PreDestroyTestObject1));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PreDestroyTestObject1", objDef);
objDef = new RootObjectDefinition(typeof(PreDestroyTestObject2));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PreDestroyTestObject2", objDef);
objDef = new RootObjectDefinition(typeof(PreDestroyTestObject3));
objDef.DestroyMethodName = "Destroy";
_applicationContext.ObjectFactory.RegisterObjectDefinition("PreDestroyTestObject3", objDef);
objDef = new RootObjectDefinition(typeof(PreDestroyTestObject4));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PreDestroyTestObject4", objDef);
objDef = new RootObjectDefinition(typeof(PreDestroyTestObject5));
_applicationContext.ObjectFactory.RegisterObjectDefinition("PreDestroyTestObject5", objDef);
_applicationContext.Refresh();
}
[Test]
public void PreDestroyMethodExecution()
{
DestroyTester.ExecutionCount1 = 0;
var testObj = _applicationContext.GetObject("PreDestroyTestObject1");
_applicationContext.Dispose();
Assert.That(DestroyTester.ExecutionCount1, Is.EqualTo(1));
}
[Test]
public void PreDestroyInBaseType()
{
DestroyTester.ExecutionCount2 = 0;
var testObj = _applicationContext.GetObject("PreDestroyTestObject2");
_applicationContext.Dispose();
Assert.That(DestroyTester.ExecutionCount2, Is.EqualTo(1));
}
[Test]
public void SameMethodDefinedInXml()
{
DestroyTester.ExecutionCount3 = 0;
var testObj = _applicationContext.GetObject("PreDestroyTestObject3");
_applicationContext.Dispose();
Assert.That(DestroyTester.ExecutionCount3, Is.EqualTo(1));
}
[Test]
public void InCorrectOrder()
{
DestroyTester.ExecutionCount4 = 0;
var testObj = (PreDestroyTestObject4)_applicationContext.GetObject("PreDestroyTestObject4");
_applicationContext.Dispose();
Assert.That(DestroyTester.ExecutionCount4, Is.EqualTo(2));
Assert.That(DestroyTester.CorrectOrder, Is.True);
}
[Test]
public void WithArgumentMustThrowException()
{
Assert.That(delegate
{
DestroyTester.ExecutionCount5 = 0;
var testObj = (PreDestroyTestObject5)_applicationContext.GetObject("PreDestroyTestObject5");
_applicationContext.Dispose();
},
Throws.Nothing);
}
}
public class PreDestroyTestObject1
{
[PreDestroy]
public void Destroy()
{
DestroyTester.ExecutionCount1++;
}
}
public class PreDestroyBase
{
[PreDestroy]
public void Destroy()
{
DestroyTester.ExecutionCount2++;
}
}
public class PreDestroyTestObject2 : PreDestroyBase
{
}
public class PreDestroyTestObject3
{
[PreDestroy]
public void Destroy()
{
DestroyTester.ExecutionCount3++;
}
}
public class PreDestroyTestObject4
{
public PreDestroyTestObject4()
{
DestroyTester.Destroy1Called = false;
DestroyTester.CorrectOrder = false;
}
[PreDestroy(Order = 1)]
public void Destroy1()
{
DestroyTester.ExecutionCount4++;
DestroyTester.Destroy1Called = true;
}
[PreDestroy(Order = 2)]
public void Destroy2()
{
DestroyTester.ExecutionCount4++;
if (DestroyTester.Destroy1Called)
DestroyTester.CorrectOrder = true;
}
}
public class PreDestroyTestObject5
{
[PreDestroy]
public void Destroy(bool arugment)
{
DestroyTester.ExecutionCount5++;
}
}
public static class DestroyTester
{
public static int ExecutionCount1 { get; set; }
public static int ExecutionCount2 { get; set; }
public static int ExecutionCount3 { get; set; }
public static int ExecutionCount4 { get; set; }
public static int ExecutionCount5 { get; set; }
public static bool Destroy1Called { get; set; }
public static bool CorrectOrder { get; set; }
}
}

View File

@@ -316,7 +316,9 @@
</Compile>
<Compile Include="HookableContextHandler.cs" />
<Compile Include="Objects\ExpressionTestObject.cs" />
<Compile Include="Objects\Factory\Attributes\PostConstructAttributeTests.cs" />
<Compile Include="Objects\Factory\Attributes\MyRequiredAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\PreDestroyAttributeTests.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredAttributeObjectPostProcessorTests.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredTestObject.cs" />
<Compile Include="Objects\Factory\ConcurrentObjectFactoryTests.cs" />

View File

@@ -318,7 +318,9 @@
</Compile>
<Compile Include="HookableContextHandler.cs" />
<Compile Include="Objects\ExpressionTestObject.cs" />
<Compile Include="Objects\Factory\Attributes\PostConstructAttributeTests.cs" />
<Compile Include="Objects\Factory\Attributes\MyRequiredAttribute.cs" />
<Compile Include="Objects\Factory\Attributes\PreDestroyAttributeTests.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredAttributeObjectPostProcessorTests.cs" />
<Compile Include="Objects\Factory\Attributes\RequiredTestObject.cs" />
<Compile Include="Objects\Factory\ConcurrentObjectFactoryTests.cs" />