根据需要使用Simple Injector加载组件

本文关键字:Simple Injector 加载 组件 | 更新日期: 2023-09-27 18:27:06

这个问题目前是关于SimpleInjector的,但可以应用于任何可以更好地处理这个问题的DI容器。。。

我现在正在为我的公司做研究。我觉得DI容器可以让我们的事情变得更容易,但当权者有一些问题,所以我已经开始尝试解决其中的一些问题,以证明DI容器的价值。我们有一个非常大的ERP系统,我们即将开始我们旗舰产品的下一次迭代。该产品将包含几个大型组件,可能至少20-30个,所有这些组件都将是大型的。

可以说,用户的主要访问点将是一个shell,它由一个程序集组成,当用户浏览应用程序时,将调用其他程序集。我们不想耽误启动时间,所以我们想找到一种根据需要加载程序集的方法。只有当需要特定程序集中的某个东西时,才应该加载程序集,在此之前,它不应该在内存中。

有没有任何方法可以用Simple Injector或DI Container来实现这一点?我们正在WPF中编写应用程序。

根据需要使用Simple Injector加载组件

您的需求有点棘手,因为DI容器的正常用例是预先加载所有内容。对于Pure DI,这更为琐碎,因为您只需通过为每个根类型(或一组根类型)提供自己的方法来延迟程序集加载。程序集加载发生在引用程序集中类型的方法被JITted时。

因此,使用Pure DI,我将合成根映像为如下所示:

public static object GetRootType(Type type) {
    if (type == typeof(HomeController))
        return GetHomeController();
    if (type == typeof(ShipOrderController))
        return GetShipOrderController();
    if (type == typeof(CancelOrderController))
        return GetCancelOrderController();
    // etc
}
private static object GetCancelOrderController() {
    return new CancelOrderController(
        ApplyAop(new CancelOrderHandler(new UnitOfWork())));
}
private static object GetShipOrderController() { ... }
private static object GetHomeController() { ... }
private static ICommandHandler<T> ApplyAop<T>(ICommandHandler<T> handler) {
    return new TransactionCommandHandlerDecorator<T>(
        new ValidationCommandHandlerDecorator<T>(handler));
}

由于CLR在第一次调用JIT方法时通常只会调用它们,因此程序集加载将在此时发生,当然,除非以前引用过此类类型。例如,如果GetRootType方法中引用的类型引用了需要延迟加载的程序集,则意味着GetRootType的第一次命中将预加载这些程序集。

尽管使用Pure DI可以更容易地延迟程序集加载,但当应用程序很大并且包含许多类时,容器的使用通常会超过Pure DI。

但Simple Injector实际上使这种情况比其他包含更困难,因为它有严格的锁定策略,这意味着在解决项后,您不能通过注册API添加注册。另一方面,这种锁定策略迫使您进入一个干净的模型,这实际上有助于防止大量难以检测的错误。

如何使用SimpleInjector实现这一点在一定程度上取决于应用程序。我想说有两种方法可以解决这个问题。您可以为每个程序集(或程序集组)提供自己的容器。这允许在装载这样的组件时构建这样的容器。这允许您在一个地方定义所需的每个特定注册,可能集中在一个位置。不利的一面是,如果这些程序集不被隔离,注册会变得更加复杂;这些程序集之间共享的类型越多,就越难正确获取每个程序集的注册。

另一个选项是使用SimpleInjector公开的ResolveUnregisteredType事件。这允许您在第一次请求未注册的类型时在最后一刻进行注册。

然而,这两个选项仍然有点棘手,因为您必须确保并非所有程序集都是在第一次调用容器时直接预加载的。因此,运行以检查是否加载了某个程序集的方法本身不能引用该程序集中的任何类型,也不能加载它要查找的程序集。否则你就回到原点了。

当使用多个容器实例时,您的解决方案可能如下所示:

var asm1Container = new lazy<Container>(Asm1Bootstrapper.Bootstrap);
var asm2Container = new lazy<Container>(Asm2Bootstrapper.Bootstrap);
var asm3Container = new lazy<Container>(Asm3Bootstrapper.Bootstrap);
mainContainer.ResolveUnregisteredType += (s, e) =>
{
    var asmContainer = GetAssemblyContainer(e.UnregisteredServiceType.Assembly);
    e.Register(() => asmContainer.GetInstance(e.UnregisteredServiceType));
}
private Container GetAssemblyContainer(Assembly assembly) {
    string assemblyName = assembly.FullName;
    if (assemblyName.Contains("Assembly1"))
        asm1Container.Value;
    if (assemblyName.Contains("Assembly2"))
        asm2Container.Value;
    if (assemblyName.Contains("Assembly3"))
        asm3Container.Value;
    // etc
}

对于第二种方法,您将以相同的方式使用ResolveUnregisteredType事件,但允许所有注册在最后一刻注册到同一容器中。

最后一点。当然,我看不出你正在构建什么样的"flagship"应用程序。对于我在运营商中看到的大多数应用程序来说,这种延迟的组件加载没有多大意义。它通常只适用于真正大的双层应用程序。两层应用程序是直接与数据库对话的厚客户端(Win Forms、WPF等)应用程序。如今,这种双层应用程序非常罕见,因为在大多数情况下,添加第三层(位于客户端和数据库之间的web服务)要好得多。更好的原因是您可以添加额外的安全层,更好的原因在于您可以在该级别拥有额外的控制(日志记录、调优、审计跟踪)。在web服务层,实现延迟的程序集加载通常没有什么好处,因为预加载web应用程序比预加载厚客户端应用程序容易得多。

当然,这并不适用于所有类型的应用程序。例如,如果您正在构建类似Visual Studio的东西(它使用托管扩展性框架来延迟程序集加载btw),那么添加第三层通常是一种非常好的方法。当您围绕这样或那样的模式构建应用程序时,第三层的开销非常小。