简单注入器:如何跳过容器中对象的验证
Simple Injector: How can I skip verification of an object in the container
我正在使用简单注入器通过构造函数注入将依赖项注入到我的对象中。
对于一组特定的对象(全部派生自公共抽象基础 class),我注入一个工厂而不是具体对象,以便我可以在 运行 时确定哪个派生实例应该注射。工厂作为单例注册到容器中,派生实例也是如此。
在向 DI 容器注册我的所有对象后,我调用 Container.Verify() 方法来验证我的配置。问题是,此方法的实现会为在容器中注册的每种类型创建一个实例。这些派生实例的创建成本很高,而且它们的创建有副作用(它们来自正在更新以使用 DI 的遗留代码)。长期我会消除副作用但短期它不可行。
如何告诉容器不要验证这些派生实例?
我想保留 Verify() 调用(至少对于调试版本),但不能接受由 Verify() 调用实例化的这些实例。此外,它们需要在 Singleton Lifestyle 中注册,所以我不能不在容器中注册它们。
我想到的一个解决方案是不向容器注册派生对象(从而使它们不可验证),然后让(Singleton Lifestyle)工厂实现缓存它创建的第一个实例。它有效,但它很脏,如果有人在其他地方(不太可能)明确请求派生类型的实例,则会创建一个新实例(因为默认的 Lifestyle 是 Transient)。
这是我拥有的 class 结构的示例:
public class AbstractBase { }
public class Derived1 : AbstractBase { }
public class Derived2 : AbstractBase { }
public class Derived3 : AbstractBase { }
public class MyFactory
{
private readonly Func<Derived1> _factory1;
private readonly Func<Derived2> _factory2;
private readonly Func<Derived3> _factory3;
public MyFactory(Func<Derived1> factory1, Func<Derived2> factory2, Func<Derived3> factory3)
{
_factory1 = factory1;
_factory2 = factory2;
_factory3 = factory3;
}
public AbstractBase Create()
{
if (AppSettings.ProductType == ProductType.Type1)
return _factory1();
if (AppSettings.ProductType == ProductType.Type2)
return _factory2();
if (AppSettings.ProductType == ProductType.Type3)
return _factory3();
throw new NotSupportedException();
}
}
以及使用 DI 容器的注册:
Container container = new Container();
container.RegisterSingle<Derived1>();
container.RegisterSingle<Derived2>();
container.RegisterSingle<Derived3>();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
container.RegisterSingle<Func<Derived2>>(() => container.GetInstance<Derived2>());
container.RegisterSingle<Func<Derived3>>(() => container.GetInstance<Derived3>());
container.RegisterSingle<MyFactory>();
container.Verify(); // <-- will instantiate new instances of Derived1, Derived2, and Derived3. Not desired.
根据您的问题,我了解到您非常了解当前方法的缺点,并且您目前正在处理一些无法在单次迭代中更改的遗留代码。但由于其他人也会阅读这篇文章,所以我想像往常一样注意 injection constructors should be simple, fast and reliable.
顺便回答一下问题:不,无法在 Simple Injector 中将注册标记为跳过注册。
在调用 Verify()
之前为容器创建的所有 InstanceProducer
实例都将得到验证(除非它们之前被垃圾收集)。通常,当您调用 Register
重载时,会为您隐式创建 InstanceProducer
个实例,但您也可以通过调用 Lifestyle.CreateProducer
创建新的 InstanceProducer
个实例。这些生产者不会成为任何对象图的一部分(这意味着 Simple Injector 不会使用它们来自动连接其他类型),并且它们充当根类型。然而,容器仍然跟踪这些生产者,验证也将适用于他们。
所以这里的技巧是在验证过程之后触发新的 InstanceProducer
实例的创建。您可以通过调用 Lifestyle.CreateProducer
来做到这一点,也可以通过解析未注册的具体类型来做到这一点,就像您在示例中所做的那样。解析未注册类型的缺点当然是默认情况下它被解析为瞬态。有两种解决方法。您可以自己缓存实例,也可以指示容器将特定类型创建为单例。
自己进行缓存可能如下所示:
var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);
然而,自己缓存它有一个缺点,那就是你会蒙蔽诊断系统并且无法发现任何 Lifestyle mismatches for you. So a better option is to override the lifestyle selection behavior,它看起来像:
// Custom lifestyle selection behavior
public class AbstractBaseDerivativesAsSingleton : ILifestyleSelectionBehavior {
public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) {
typeof(AbstractBase).IsAssignableFrom(implementationType)
? Lifestyle.Singleton
: Lifestyle.Transient;
}
}
// Usage
var container = new Container();
container.Options.LifestyleSelectionBehavior =
new AbstractBaseDerivativesAsSingleton();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
解决此问题的另一种方法是使用 Lifestyle.CreateProducer
调用自己创建 InstanceProducers
。因为你想要单例,所以你必须调用 Lifestyle.Singleton.CreateProducer
。这些生产者需要在验证调用后创建,所以你仍然需要使用 Lazy 作为延迟机制:
// This factory should be part of your composition root,
// because it now depends on the container.
public class MyFactory : IMyFactory
{
private readonly Container container;
private readonly Dictionary<ProductType, Lazy<InstanceProducer>> producers;
public MyFactory(Container container) {
this.container = container;
this.producers = new Dictionary<ProductType, Lazy<InstanceProducer>>
{
{ProductType.Type1, new Lazy<InstanceProducer>(this.CreateProducer<Derived1>)},
{ProductType.Type2, new Lazy<InstanceProducer>(this.CreateProducer<Derived2>)},
{ProductType.Type3, new Lazy<InstanceProducer>(this.CreateProducer<Derived3>)}
};
}
public AbstractBase Create() {
return (AbstractBase)this.producers[AppSettings.ProductType].GetInstance()
}
private InstanceProducer CreateProducer<T>() where T : AbstractBase {
Lifestyle.Singleton.CreateProducer<AbstractBase, T>(this.container);
}
}
也可以考虑将您的工厂更改为调解员或代理人。工厂通常是 not the right abstraction,因为它们通常只会增加复杂性。相反,您可以制作一个采用相同接口并将调用委托给真实实例的代理(您仍然在后台使用类似工厂的行为):
public class AbstractBaseAppSettingsSwitchProxy : AbstractBase
{
private readonly IMyFactory factory;
public AbstractBaseAppSettingsSwitchProxy(IMyFactory factory) {
this.factory = factory;
}
public override void SomeFunction() {
this.factory.Create().SomeFunction();
}
}
使用此代理,您可以向任何消费者隐藏存在多个可能的 AbstractBase 实现的事实。消费者可以简单地与 AbstractBase
通信,就好像总是只有一个。这可以使您的应用程序代码更干净,消费者代码更简单,并使消费者更容易测试。
如果代理不是一个选项,你仍然可以考虑中介者模式。中介者的工作方式与上面的代理大致相同,但不同之处在于它有自己的接口。所以它很像工厂(有自己的接口),但不同的是中介者负责调用委托对象。它不会 return 一个实例给消费者。
我知道这些解决方案可能不适用于您,因为 AbstractBase
的结构。如果你有一个胖基础 class,其中一些方法是虚拟的,而另一些不是,那么这样做可能会非常困难。这些解决方案通常只在设计良好的 (SOLID) 系统中运行良好。但这实际上就是一切的运作方式。我的经验是,如果没有 SOLID 代码,一切都会变得很麻烦。作为软件开发人员,我们的主要工作之一是降低我们软件的总拥有成本,而实现这一目标的最佳方法之一就是将 SOLID 原则应用于我们的软件。
最后一点。在我看来,您正在读取工厂内部的一些配置值。如果在应用程序的配置文件中定义了该值,则只能通过重新启动应用程序来更改该值(这是 IIS 自动为您做的事情)。如果这是这样的配置值,你实际上根本不需要所有这些废话。您可以简单地进行以下注册:
Container container = new Container();
container.RegisterSingle(typeof(AbstractBase, GetConfiguredAbstractBaseType()));
private static Type GetConfiguredAbstractBaseType() {
switch (AppSettings.ProductType) {
case ProductType.Type1: return typeof(Derived1);
case ProductType.Type2: return typeof(Derived2);
case ProductType.Type3: return typeof(Derived3);
default: throw new NotSupportedException();
}
}
当然这又把我们带回了最初无法验证的问题。但是我们可以再次使用代理解决这个问题:
public class LazyAbstractBaseProxy : AbstractBase
{
private readonly Lazy<AbstractBase> lazy;
public LazyAbstractBaseProxy(Lazy<AbstractBase> lazy) {
this.lazy = lazy;
}
public override void SomeFunction() {
this.lazy.Value.SomeFunction();
}
}
Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<AbstractBase>(new LazyAbstractBaseProxy(
new Lazy<AbstractBase>(() => (AbstractBase)lazy.Value.GetInstance()));
如果这不可能,您甚至可以跳过工厂并直接将 Func 注入消费者:
Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<Func<AbstractBase>>(() => (AbstractBase)lazy.Value.GetInstance());
您可以使用 Lazy<T>
注册您的 Func<T>
,这将延迟加载 InstanceProducer
,如下所示:
private static Lazy<T> Lazy<T>(Func<T> func) => new Lazy<T>(func);
public static void RegisterDelayedFunc<T>(this Container container, Lifestyle lifestyle)
where T : class
{
var lazy = Lazy(() => lifestyle.CreateProducer<T, T>(container));
container.RegisterSingleton<Func<T>>(() => lazy.Value.GetInstance());
}
Container container = new Container();
container.RegisterDelayedFunc<Derived1>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived2>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived3>(Lifestyle.Singleton);
container.RegisterSingleton<MyFactory>();
回答主要问题:如何跳过容器中对象的验证。
你可以这样做:
Container container = new Container();
Lifestyle singletonLifestyle = Lifestyle.CreateHybrid(
lifestyleSelector: () => !container.IsVerifying,
trueLifestyle: Lifestyle.Singleton,
falseLifestyle: Lifestyle.Transient);
container.Register<TConcrete>(singletonLifestyle);
我正在使用简单注入器通过构造函数注入将依赖项注入到我的对象中。
对于一组特定的对象(全部派生自公共抽象基础 class),我注入一个工厂而不是具体对象,以便我可以在 运行 时确定哪个派生实例应该注射。工厂作为单例注册到容器中,派生实例也是如此。
在向 DI 容器注册我的所有对象后,我调用 Container.Verify() 方法来验证我的配置。问题是,此方法的实现会为在容器中注册的每种类型创建一个实例。这些派生实例的创建成本很高,而且它们的创建有副作用(它们来自正在更新以使用 DI 的遗留代码)。长期我会消除副作用但短期它不可行。
如何告诉容器不要验证这些派生实例?
我想保留 Verify() 调用(至少对于调试版本),但不能接受由 Verify() 调用实例化的这些实例。此外,它们需要在 Singleton Lifestyle 中注册,所以我不能不在容器中注册它们。
我想到的一个解决方案是不向容器注册派生对象(从而使它们不可验证),然后让(Singleton Lifestyle)工厂实现缓存它创建的第一个实例。它有效,但它很脏,如果有人在其他地方(不太可能)明确请求派生类型的实例,则会创建一个新实例(因为默认的 Lifestyle 是 Transient)。
这是我拥有的 class 结构的示例:
public class AbstractBase { }
public class Derived1 : AbstractBase { }
public class Derived2 : AbstractBase { }
public class Derived3 : AbstractBase { }
public class MyFactory
{
private readonly Func<Derived1> _factory1;
private readonly Func<Derived2> _factory2;
private readonly Func<Derived3> _factory3;
public MyFactory(Func<Derived1> factory1, Func<Derived2> factory2, Func<Derived3> factory3)
{
_factory1 = factory1;
_factory2 = factory2;
_factory3 = factory3;
}
public AbstractBase Create()
{
if (AppSettings.ProductType == ProductType.Type1)
return _factory1();
if (AppSettings.ProductType == ProductType.Type2)
return _factory2();
if (AppSettings.ProductType == ProductType.Type3)
return _factory3();
throw new NotSupportedException();
}
}
以及使用 DI 容器的注册:
Container container = new Container();
container.RegisterSingle<Derived1>();
container.RegisterSingle<Derived2>();
container.RegisterSingle<Derived3>();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
container.RegisterSingle<Func<Derived2>>(() => container.GetInstance<Derived2>());
container.RegisterSingle<Func<Derived3>>(() => container.GetInstance<Derived3>());
container.RegisterSingle<MyFactory>();
container.Verify(); // <-- will instantiate new instances of Derived1, Derived2, and Derived3. Not desired.
根据您的问题,我了解到您非常了解当前方法的缺点,并且您目前正在处理一些无法在单次迭代中更改的遗留代码。但由于其他人也会阅读这篇文章,所以我想像往常一样注意 injection constructors should be simple, fast and reliable.
顺便回答一下问题:不,无法在 Simple Injector 中将注册标记为跳过注册。
在调用 Verify()
之前为容器创建的所有 InstanceProducer
实例都将得到验证(除非它们之前被垃圾收集)。通常,当您调用 Register
重载时,会为您隐式创建 InstanceProducer
个实例,但您也可以通过调用 Lifestyle.CreateProducer
创建新的 InstanceProducer
个实例。这些生产者不会成为任何对象图的一部分(这意味着 Simple Injector 不会使用它们来自动连接其他类型),并且它们充当根类型。然而,容器仍然跟踪这些生产者,验证也将适用于他们。
所以这里的技巧是在验证过程之后触发新的 InstanceProducer
实例的创建。您可以通过调用 Lifestyle.CreateProducer
来做到这一点,也可以通过解析未注册的具体类型来做到这一点,就像您在示例中所做的那样。解析未注册类型的缺点当然是默认情况下它被解析为瞬态。有两种解决方法。您可以自己缓存实例,也可以指示容器将特定类型创建为单例。
自己进行缓存可能如下所示:
var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);
然而,自己缓存它有一个缺点,那就是你会蒙蔽诊断系统并且无法发现任何 Lifestyle mismatches for you. So a better option is to override the lifestyle selection behavior,它看起来像:
// Custom lifestyle selection behavior
public class AbstractBaseDerivativesAsSingleton : ILifestyleSelectionBehavior {
public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) {
typeof(AbstractBase).IsAssignableFrom(implementationType)
? Lifestyle.Singleton
: Lifestyle.Transient;
}
}
// Usage
var container = new Container();
container.Options.LifestyleSelectionBehavior =
new AbstractBaseDerivativesAsSingleton();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
解决此问题的另一种方法是使用 Lifestyle.CreateProducer
调用自己创建 InstanceProducers
。因为你想要单例,所以你必须调用 Lifestyle.Singleton.CreateProducer
。这些生产者需要在验证调用后创建,所以你仍然需要使用 Lazy 作为延迟机制:
// This factory should be part of your composition root,
// because it now depends on the container.
public class MyFactory : IMyFactory
{
private readonly Container container;
private readonly Dictionary<ProductType, Lazy<InstanceProducer>> producers;
public MyFactory(Container container) {
this.container = container;
this.producers = new Dictionary<ProductType, Lazy<InstanceProducer>>
{
{ProductType.Type1, new Lazy<InstanceProducer>(this.CreateProducer<Derived1>)},
{ProductType.Type2, new Lazy<InstanceProducer>(this.CreateProducer<Derived2>)},
{ProductType.Type3, new Lazy<InstanceProducer>(this.CreateProducer<Derived3>)}
};
}
public AbstractBase Create() {
return (AbstractBase)this.producers[AppSettings.ProductType].GetInstance()
}
private InstanceProducer CreateProducer<T>() where T : AbstractBase {
Lifestyle.Singleton.CreateProducer<AbstractBase, T>(this.container);
}
}
也可以考虑将您的工厂更改为调解员或代理人。工厂通常是 not the right abstraction,因为它们通常只会增加复杂性。相反,您可以制作一个采用相同接口并将调用委托给真实实例的代理(您仍然在后台使用类似工厂的行为):
public class AbstractBaseAppSettingsSwitchProxy : AbstractBase
{
private readonly IMyFactory factory;
public AbstractBaseAppSettingsSwitchProxy(IMyFactory factory) {
this.factory = factory;
}
public override void SomeFunction() {
this.factory.Create().SomeFunction();
}
}
使用此代理,您可以向任何消费者隐藏存在多个可能的 AbstractBase 实现的事实。消费者可以简单地与 AbstractBase
通信,就好像总是只有一个。这可以使您的应用程序代码更干净,消费者代码更简单,并使消费者更容易测试。
如果代理不是一个选项,你仍然可以考虑中介者模式。中介者的工作方式与上面的代理大致相同,但不同之处在于它有自己的接口。所以它很像工厂(有自己的接口),但不同的是中介者负责调用委托对象。它不会 return 一个实例给消费者。
我知道这些解决方案可能不适用于您,因为 AbstractBase
的结构。如果你有一个胖基础 class,其中一些方法是虚拟的,而另一些不是,那么这样做可能会非常困难。这些解决方案通常只在设计良好的 (SOLID) 系统中运行良好。但这实际上就是一切的运作方式。我的经验是,如果没有 SOLID 代码,一切都会变得很麻烦。作为软件开发人员,我们的主要工作之一是降低我们软件的总拥有成本,而实现这一目标的最佳方法之一就是将 SOLID 原则应用于我们的软件。
最后一点。在我看来,您正在读取工厂内部的一些配置值。如果在应用程序的配置文件中定义了该值,则只能通过重新启动应用程序来更改该值(这是 IIS 自动为您做的事情)。如果这是这样的配置值,你实际上根本不需要所有这些废话。您可以简单地进行以下注册:
Container container = new Container();
container.RegisterSingle(typeof(AbstractBase, GetConfiguredAbstractBaseType()));
private static Type GetConfiguredAbstractBaseType() {
switch (AppSettings.ProductType) {
case ProductType.Type1: return typeof(Derived1);
case ProductType.Type2: return typeof(Derived2);
case ProductType.Type3: return typeof(Derived3);
default: throw new NotSupportedException();
}
}
当然这又把我们带回了最初无法验证的问题。但是我们可以再次使用代理解决这个问题:
public class LazyAbstractBaseProxy : AbstractBase
{
private readonly Lazy<AbstractBase> lazy;
public LazyAbstractBaseProxy(Lazy<AbstractBase> lazy) {
this.lazy = lazy;
}
public override void SomeFunction() {
this.lazy.Value.SomeFunction();
}
}
Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<AbstractBase>(new LazyAbstractBaseProxy(
new Lazy<AbstractBase>(() => (AbstractBase)lazy.Value.GetInstance()));
如果这不可能,您甚至可以跳过工厂并直接将 Func 注入消费者:
Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<Func<AbstractBase>>(() => (AbstractBase)lazy.Value.GetInstance());
您可以使用 Lazy<T>
注册您的 Func<T>
,这将延迟加载 InstanceProducer
,如下所示:
private static Lazy<T> Lazy<T>(Func<T> func) => new Lazy<T>(func);
public static void RegisterDelayedFunc<T>(this Container container, Lifestyle lifestyle)
where T : class
{
var lazy = Lazy(() => lifestyle.CreateProducer<T, T>(container));
container.RegisterSingleton<Func<T>>(() => lazy.Value.GetInstance());
}
Container container = new Container();
container.RegisterDelayedFunc<Derived1>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived2>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived3>(Lifestyle.Singleton);
container.RegisterSingleton<MyFactory>();
回答主要问题:如何跳过容器中对象的验证。
你可以这样做:
Container container = new Container();
Lifestyle singletonLifestyle = Lifestyle.CreateHybrid(
lifestyleSelector: () => !container.IsVerifying,
trueLifestyle: Lifestyle.Singleton,
falseLifestyle: Lifestyle.Transient);
container.Register<TConcrete>(singletonLifestyle);