Manmoth
03-06-2007, 10:15 PM
What if you have to (or just want to) dynamically change an existing (singleton) object definition in code at runtime?
Adding a new object definition is explained in Programmatic Add of an Object to a Context (http://forum.springframework.net/showthread.php?t=314).
But what if the object definition (id) already exists? For example: Solution A has a MyApp assembly with Spring configuration in one App.config, and a separate MyUnitTesting assembly with a separate Spring configuration in UnitTesting's App.config. MyApp has class MyClass, which needs to be mocked up for testing purposes, so MyUnitTesting has MockMyClass. MyClass and MockMyClass are defined in both Spring configs with the same name "idMyClass", so when MyApp is started directly, it will get the MyClass object, whereas when started with MyUnitTesting, it gets the MockMyClass object.
But now it's time to write tests for the "real" MyClass, and you want to maintain the separation between MyApp and MyUnitTesting. Wouldn't it be nice to dynamically, at runtime, in the test code itself, redefine "idMyClass" to reference MyApp.MyClass, without changing the reference for the rest of the tests?
I struggled with how to do this without requiring any changes to the existing Spring configuration, just to allow this one case, and I came up with a solution. Perhaps there is another way - please let me know if there is, because this is a hack that I have made to look somewhat elegant...
Essentially I use a similar method as when adding a new definition, but via the IConfigurableListableObjectFactory cast to a DefaultListableObjectFactory, using its RegisterObjectDefinition method, which allows (by default, AllowObjectDefinitionOverriding == true) overriding object definitions. This, together with Aleks Seovic's method of creating a new object definition, is all you need for non-singleton objects. But singletons are cached at the time the context is instantiated, and so this cached singleton must be cleared. This required a reflection trick to access and modify the private IDictionary.
I've packaged this up in the SpringUtil class below. Just use one of the ReplaceObjectDefinition methods. Even better, do your testing or whatever other operation with a "using" block with a TemporaryObjectDefinition object, and the original object definition (but not the cached singleton instance! -- though it could easily be rewritten to accomplish even this) will be restored when the using block falls out of scope.
Like so:
//do some testing (the original object definition from the App.config is being used)
using (new SpringUtil.TemporaryObjectDefinition("idMyClass", "MyClass, MyApp"))
{
//do some testing (the temporary object defintion will be used)
}
//do some more testing (the original object definition from the App.config will be used again)
Let me know what you think or if there is a better way.
Hope this helps.
using System;
using System.Collections;
using System.Text;
using System.Reflection;
using Spring.Context.Support;
using Spring.Objects.Factory.Support;
using Spring.Objects.Factory.Config;
using Spring.Objects;
namespace org.in2bits
{
public static class SpringUtil
{
public class TemporaryObjectDefinition : IDisposable
{
private string _objectId;
private IObjectDefinition _exisitingObjectDefinition;
public TemporaryObjectDefinition(string objectId, string newObjectType)
{
_objectId = objectId;
_exisitingObjectDefinition = ContextRegistry.GetContext().GetObjectDefinition(_ objectId);
ReplaceObjectDefinition(_objectId, newObjectType);
}
#region IDisposable Members
public void Dispose()
{
ReplaceObjectDefinition(_objectId, _exisitingObjectDefinition);
}
#endregion
}
public static void ReplaceObjectDefinition(string objectId, string newObjectType)
{
IObjectDefinition existingObjectDefinition = ContextRegistry.GetContext().GetObjectDefinition(o bjectId);
ReplaceObjectDefinition(objectId, newObjectType, existingObjectDefinition.IsLazyInit, existingObjectDefinition.IsSingleton);
}
public static void ReplaceObjectDefinition(string objectId, string newObjectType, bool forceLazyInit, bool forceSingleton)
{
ReplaceObjectDefinition(objectId, GetNewObjectDefinition(newObjectType, forceLazyInit, forceSingleton));
}
public static void ReplaceObjectDefinition(string objectId, IObjectDefinition newObjectDefinition)
{
XmlApplicationContext xmlApplicationContext = ContextRegistry.GetContext() as XmlApplicationContext;
DefaultListableObjectFactory objectFactory = xmlApplicationContext.ObjectFactory as DefaultListableObjectFactory;
objectFactory.RegisterObjectDefinition(objectId, newObjectDefinition);
ClearSingletonCache(objectFactory, objectId);
}
private static void ClearSingletonCache(DefaultListableObjectFactory objectFactory, string objectId)
{
Type factoryType = objectFactory.GetType().BaseType.BaseType;
FieldInfo fieldInfo = factoryType.GetField("singletonCache", BindingFlags.Instance | BindingFlags.NonPublic);
IDictionary dictionary = (IDictionary)fieldInfo.GetValue(objectFactory);
if (dictionary.Contains(objectId))
dictionary.Remove(objectId);
}
private static IObjectDefinition GetNewObjectDefinition(string objectType, bool forceLazyInit, bool forceSingleton)
{
DefaultObjectDefinitionFactory objectDefinitionFactory = new DefaultObjectDefinitionFactory();
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
IConfigurableObjectDefinition objectDefinition = objectDefinitionFactory.CreateObjectDefinition(
objectType,
null,
constructorArgumentValues,
mutablePropertyValues,
AppDomain.CurrentDomain);
objectDefinition.IsLazyInit = forceLazyInit;
objectDefinition.IsSingleton = forceSingleton;
return objectDefinition;
}
}
}
Adding a new object definition is explained in Programmatic Add of an Object to a Context (http://forum.springframework.net/showthread.php?t=314).
But what if the object definition (id) already exists? For example: Solution A has a MyApp assembly with Spring configuration in one App.config, and a separate MyUnitTesting assembly with a separate Spring configuration in UnitTesting's App.config. MyApp has class MyClass, which needs to be mocked up for testing purposes, so MyUnitTesting has MockMyClass. MyClass and MockMyClass are defined in both Spring configs with the same name "idMyClass", so when MyApp is started directly, it will get the MyClass object, whereas when started with MyUnitTesting, it gets the MockMyClass object.
But now it's time to write tests for the "real" MyClass, and you want to maintain the separation between MyApp and MyUnitTesting. Wouldn't it be nice to dynamically, at runtime, in the test code itself, redefine "idMyClass" to reference MyApp.MyClass, without changing the reference for the rest of the tests?
I struggled with how to do this without requiring any changes to the existing Spring configuration, just to allow this one case, and I came up with a solution. Perhaps there is another way - please let me know if there is, because this is a hack that I have made to look somewhat elegant...
Essentially I use a similar method as when adding a new definition, but via the IConfigurableListableObjectFactory cast to a DefaultListableObjectFactory, using its RegisterObjectDefinition method, which allows (by default, AllowObjectDefinitionOverriding == true) overriding object definitions. This, together with Aleks Seovic's method of creating a new object definition, is all you need for non-singleton objects. But singletons are cached at the time the context is instantiated, and so this cached singleton must be cleared. This required a reflection trick to access and modify the private IDictionary.
I've packaged this up in the SpringUtil class below. Just use one of the ReplaceObjectDefinition methods. Even better, do your testing or whatever other operation with a "using" block with a TemporaryObjectDefinition object, and the original object definition (but not the cached singleton instance! -- though it could easily be rewritten to accomplish even this) will be restored when the using block falls out of scope.
Like so:
//do some testing (the original object definition from the App.config is being used)
using (new SpringUtil.TemporaryObjectDefinition("idMyClass", "MyClass, MyApp"))
{
//do some testing (the temporary object defintion will be used)
}
//do some more testing (the original object definition from the App.config will be used again)
Let me know what you think or if there is a better way.
Hope this helps.
using System;
using System.Collections;
using System.Text;
using System.Reflection;
using Spring.Context.Support;
using Spring.Objects.Factory.Support;
using Spring.Objects.Factory.Config;
using Spring.Objects;
namespace org.in2bits
{
public static class SpringUtil
{
public class TemporaryObjectDefinition : IDisposable
{
private string _objectId;
private IObjectDefinition _exisitingObjectDefinition;
public TemporaryObjectDefinition(string objectId, string newObjectType)
{
_objectId = objectId;
_exisitingObjectDefinition = ContextRegistry.GetContext().GetObjectDefinition(_ objectId);
ReplaceObjectDefinition(_objectId, newObjectType);
}
#region IDisposable Members
public void Dispose()
{
ReplaceObjectDefinition(_objectId, _exisitingObjectDefinition);
}
#endregion
}
public static void ReplaceObjectDefinition(string objectId, string newObjectType)
{
IObjectDefinition existingObjectDefinition = ContextRegistry.GetContext().GetObjectDefinition(o bjectId);
ReplaceObjectDefinition(objectId, newObjectType, existingObjectDefinition.IsLazyInit, existingObjectDefinition.IsSingleton);
}
public static void ReplaceObjectDefinition(string objectId, string newObjectType, bool forceLazyInit, bool forceSingleton)
{
ReplaceObjectDefinition(objectId, GetNewObjectDefinition(newObjectType, forceLazyInit, forceSingleton));
}
public static void ReplaceObjectDefinition(string objectId, IObjectDefinition newObjectDefinition)
{
XmlApplicationContext xmlApplicationContext = ContextRegistry.GetContext() as XmlApplicationContext;
DefaultListableObjectFactory objectFactory = xmlApplicationContext.ObjectFactory as DefaultListableObjectFactory;
objectFactory.RegisterObjectDefinition(objectId, newObjectDefinition);
ClearSingletonCache(objectFactory, objectId);
}
private static void ClearSingletonCache(DefaultListableObjectFactory objectFactory, string objectId)
{
Type factoryType = objectFactory.GetType().BaseType.BaseType;
FieldInfo fieldInfo = factoryType.GetField("singletonCache", BindingFlags.Instance | BindingFlags.NonPublic);
IDictionary dictionary = (IDictionary)fieldInfo.GetValue(objectFactory);
if (dictionary.Contains(objectId))
dictionary.Remove(objectId);
}
private static IObjectDefinition GetNewObjectDefinition(string objectType, bool forceLazyInit, bool forceSingleton)
{
DefaultObjectDefinitionFactory objectDefinitionFactory = new DefaultObjectDefinitionFactory();
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
IConfigurableObjectDefinition objectDefinition = objectDefinitionFactory.CreateObjectDefinition(
objectType,
null,
constructorArgumentValues,
mutablePropertyValues,
AppDomain.CurrentDomain);
objectDefinition.IsLazyInit = forceLazyInit;
objectDefinition.IsSingleton = forceSingleton;
return objectDefinition;
}
}
}