您的位置:首页 > 其它

Autofac官方文档(十三)【捕获依赖】

2017-12-14 11:13 155 查看

捕获依赖

当一个要生存很短时间的组件被一个长期存在的组件持有时,就会发生“captive dependency”。 Mark Seemann的这篇博客文章在解释这个概念方面做得很好。

Autofac不一定会阻止你创建捕获依赖关系。由于捕获的设置方式,您可能会发现解决方案异常的时候,但您并不总是如此。停止捕获依赖是开发者的责任。

一般规则

避免捕获依赖的一般规则:

消费组件的生命周期应该小于或等于所消费的服务的生命周期。

基本上,不要让一个单例执行一个实例每请求的依赖关系,因为它会被保留太久。

简单的例子

假设您有一个Web应用程序,它使用来自入站请求的一些信息来确定连接的正确数据库。您可能有以下组件:

接收当前请求和数据库连接工厂的repository。
当前请求像一个HttpContext,可以用来帮助决定业务逻辑。
数据库连接工厂采用某种参数并返回正确的数据库连接。


在这个例子中,考虑一下你想要为每个组件使用的生命周期范围。当前的请求上下文是一个明显的 - 你想每个请求的实例。那其他的呢?

对于
repository
,假设你选择“singleton”。一个单例创建一次,并为应用程序的生命周期进行缓存。如果选择“singleton”,那么即使在当前请求结束之后,请求上下文也将被传入并保存在应用程序的整个生命周期中。该repository是长期存在的,但可以保持较短的寿命组件。这是一个捕获依赖。

但是,假设您将存储库设置为“每个请求的实例”(instance-per-request) - 现在只要当前请求存在且不再存在。这与它所需的请求上下文一样长,所以现在它不是一个捕获。资源库和请求上下文将同时发布(在请求结束时),一切都会好起来的。

更进一步说,你使repository“每个依赖的实例”,所以你每次都得到一个新的。这还是可以的,因为它意图比现在的要求更短的时间。它不会太长时间,所以没有捕获。

数据库连接工厂经历了类似的思考过程,但可能有一些不同的考虑因素。实例化工厂可能很昂贵,或者需要维护一些内部状态才能正常工作。您可能不希望它是“每个请求的实例”或“每个依赖的实例”。您可能实际上需要它是一个单例。

对于寿命较短的依赖关系,可以采用较长寿命的依赖关系。如果您的repository是“每个请求的实例”或“每个依赖的实例”,那么您仍然会很好。数据库连接工厂有意寿命更长。

代码示例

这是一个单元测试,它显示了它强制创建一个捕获依赖的样子。在这个例子中,使用“规则管理器”来处理通过应用程序使用的一组“规则”。

public class RuleManager
{
public RuleManager(IEnumerable<IRule> rules)
{
this.Rules = rules;
}

public IEnumerable<IRule> Rules { get; private set; }
}

public interface IRule { }

public class SingletonRule : IRule { }

public class InstancePerDependencyRule : IRule { }

[Fact]
public void CaptiveDependency()
{
var builder = new ContainerBuilder();

//规则管理器是一个单实例组件。 它只会被实例化一次,然后将使用缓存的实例。 它将始终从根生存期范围(容器)解析,因为它需要共享。
builder.RegisterType<RuleManager>()
.SingleInstance();

//这个规则是注册的每个依赖的实例。 每次请求时都会创建一个新的实例。
builder.RegisterType<InstancePerDependencyRule>()
.As<IRule>();

//这个规则被注册为一个单例。 像规则管理器一样,它只会被解析一次,并将从根生命周期解析。
builder.RegisterType<SingletonRule>()
.As<IRule>()
.SingleInstance();

using (var container = builder.Build())
using (var scope = container.BeginLifetimeScope("request"))
{
//管理器将是一个单例。 它将包含对单例SingletonRule的引用,这很好。 但是,它也将持有一个InstancePerDependencyRule,可能不正确。 它所拥有的InstancePerDependencyRule将在RuleManager中的容器的生命周期中生存,并持续到容器被处置。
var manager = scope.Resolve<RuleManager>();
}
}


请注意,上面的示例并不直接显示,但如果您要在
container.BeginLifetimeScope()
调用中动态添加规则的注册,则这些动态注册将不会包含在已解析的
RuleManager
中。
RuleManager
是一个单例,从动态添加的注册不存在的根容器中解析出来。

另一个代码示例显示如何创建一个错误地绑定到子生命周期作用域的捕获依赖关系时可能会发生异常。

public class RuleManager
{
public RuleManager(IEnumerable<IRule> rules)
{
this.Rules = rules;
}

public IEnumerable<IRule> Rules { get; private set; }
}

public interface IRule { }

public class SingletonRule : IRule
{
public SingletonRule(InstancePerRequestDependency dep) { }
}

public class InstancePerRequestDependency { }

[Fact]
public void CaptiveDependency()
{
var builder = new ContainerBuilder();

//同样,规则管理器是一个单一实例组件,从根生命周期解析,然后缓存。
builder.RegisterType<RuleManager>()
.SingleInstance();

//这个规则被注册为一个单例。像规则管理器一样,它只会被解析一次,并将从根生命周期解析。
builder.RegisterType<SingletonRule>()
.As<IRule>()
.SingleInstance();

//此规则是基于每个请求进行注册的。它只在请求期间存在。
builder.RegisterType<InstancePerRequestDependency>()
.As<IRule>()
.InstancePerMatchingLifetimeScope("request");

using (var container = builder.Build())
using (var scope = container.BeginLifetimeScope("request"))
{
//问题:当SingletonRule作为规则管理器的依赖关系链的一部分被解析时,规则构造函数中的InstancePerRequestDependency将无法被解析,因为规则来自于根生命周期范围,但是InstancePerRequestDependency在那里不存在。
Assert.Throws<DependencyResolutionException>(() => scope.Resolve<RuleManager>());
}
}


规则的例外

鉴于应用程序的开发人员最终负责确定捕获是否正常,开发人员可以确定单例可以接受,例如,采取“依赖每个实例的”服务。

例如,也许你有一个缓存类被故意设置为缓存消费组件的生命周期的东西。 如果消费者是单例,则可以使用缓存来存储整个应用程序生命周期中的事物; 如果消费者是“每个请求的实例”,那么它只存储单个Web请求的数据。 在这样的情况下,最终可能会有一个寿命较长的组件故意地依赖于一个寿命较短的组件。

只要应用程序开发人员了解设置这些生命周期的后果,这是可以接受的。 也就是说,如果你打算这么做的话,那就有意而非无意地去做。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: