随着时间的推移,控制器会产生大量的依赖关系,并且为每个请求创建一个控制器实例会变得过于昂贵(尤其是对于DI)。有没有解决方案让控制器单件?
答案 0 :(得分:17)
创建控制器实例非常简单快捷。变得太昂贵的是为每个请求创建依赖项。所以,你真正需要的是许多控制器,它们共享相同的依赖实例。
E.g。你有以下控制器
public class SalesController : Controller
{
private IProductRepository productRepository;
private IOrderRepository orderRepository;
public SalesController(IProductRepository productRepository,
IOrderRepository orderRepository)
{
this.productRepository = productRepository;
this.orderRepository = orderRepository;
}
// ...
}
您应该配置依赖项注入框架,以便为所有应用程序使用相同的存储库实例(请记住,您可能遇到同步问题)。现在创建依赖关系并不昂贵。所有依赖项仅实例化一次,并重用于所有请求。
如果您有很多依赖项并且您担心引用每个依赖项的实例并将这些引用提供给控制器实例(我认为这将不会非常昂贵)的成本,那么您可以对依赖项进行分组(某些内容)比如介绍参数对象重构:
public class SalesController : Controller
{
private ISalesService salesService;
public SalesController(ISalesService salesService)
{
this.salesService = salesService;
}
// ...
}
public class SalesService : ISalesService
{
private IProductRepository productRepository;
private IOrderRepository orderRepository;
public SalesService(IProductRepository productRepository,
IOrderRepository orderRepository)
{
this.productRepository = productRepository;
this.orderRepository = orderRepository;
}
// ...
}
现在你有一个依赖,将很快注入。如果要将依赖项注入框架配置为使用singleton SalesService,则所有SalesControllers都将重用相同的服务实例。创建控制器和提供依赖关系将非常快。
答案 1 :(得分:3)
首先回答原来的问题:
public void ConfigureServices(IServiceCollection services) {
// put other services bindings here
// bind all Controller classes as singletons
services.AddSingleton<HomeController, HomeController>();
// tell framework to obtain Controller instances from ServiceProvider.
services.AddMvc().AddControllersAsServices();
}
如原始问题所述,如果控制器具有主要由请求Scoped或Transient依赖关系组成的大依赖关系树,那么为每个请求单独创建它们可能会对应用程序的可伸缩性产生一些影响(例如,在Java中,Servlet实例是单例的完全出于这个原因默认)。虽然创建一个大的依赖树所需的CPU和实时通常可以忽略不计(除非你在组件的构造函数中有一些繁重的计算或网络通信,这对于瞬态或请求范围的组件几乎不是一个好主意),内存使用足迹是值得考虑的。在常见的DB-Web应用程序的情况下,内存是限制单个机器节点可以处理的并发请求数量的主要因素。如果每个请求都有一个大依赖树的单独副本,那么它们可能会占用大量内存(顺便说一下,另一件需要注意的是新线程的初始堆栈大小)。
接受的答案1220560也解决了这个问题,但我认为它是一个丑陋的黑客,它有一些缺点:你需要创建这个人工单例服务,你的控制器将它用作服务定位器或代理用于其他服务。如果您的所有控制器只有一个这样的单例对象,那么您实际上隐藏了Controller的真正依赖关系:例如,如果有人想为您的Controller编写单元测试,他需要仔细分析其实现以查看它实际上是哪个依赖项使用,以便他知道他需要在他的测试设置中提供什么样的模拟/假货。如果稍后您更改了控制器,并且由于您更改了控制器使用的服务子集也发生了变化,则很容易忘记更新测试设置。这有时会导致难以跟踪的错误。与此相反,如果您的依赖项被明确声明为构造函数参数,您将立即在测试设置中收到编译器错误。你可以做的另一件事就是为每个控制器分别设置一个单独的代理/服务定位器,但基本上它有很多麻烦。
无论您是使用我提出的解决方案还是来自答案#1220560的解决方案,您都必须小心将请求Scoped依赖项注入单个对象,如https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection#registering-your-own-services中所述,在#34;注册结束时 - 您 - 拥有 - 服务&#34;部分。您可以在此处找到此问题的可能解决方案:how to use scoped dependency in a singleton in C# / ASP 需要注意的另一件事是并发问题:单个对象可以由处理不同并发请求的多个线程同时访问,因此请确保为您的单例使用的任何非线程安全资源添加适当的同步。
修改强>
我刚刚意识到最初的问题是关于ASP.NET的,这个答案是针对ASP.NET Core的,所以它可能不会为非核心&#34;。< / p>