处理插件时是否可以从组合根目录之外的 DI 容器进行解析
本文关键字:DI 是否 插件 根目录 组合 处理 | 更新日期: 2023-09-27 18:22:50
我正在构建一个WPF(桌面(应用程序,利用依赖注入,DI容器和Register-Resolve-Release模式。我的应用程序还在启动期间从单独的程序集加载插件,并且插件已注册到 DI 容器。我在启动时在组合根中解析了整个对象图,但是,我在解决插件时遇到了一些问题,我想知道是否可以将 DI 容器注入工厂以解析未知类型?
我的插件全部:
- 实现通用接口,
IPlugin
- 需要瞬态(因为多个实例可以同时存在(
- 依赖于运行时值,以便在初始化时对其进行配置
- 可能(也可能没有(通过其构造函数注入其他依赖项
我已经开始实现一个PluginFactory
,在需要时初始化插件,因为正如 Mark Seemann 所说:">任何需要运行时值来构造特定依赖项的地方,抽象工厂都是解决方案。
但是,我不能只new()
工厂中的插件,因为我不知道它们的类型和依赖项。
我想到了几个解决方案:
-
我可以将 DI 容器注入
PluginFactory
的构造函数中,在运行时解析插件并调用它一天。然而,这与RRR模式背道而驰。 -
我可以通过
PluginFactory
的构造函数注入IEnumerable<IPlugin> plugins
.但是,这将违反瞬态要求,因为我每个实例只有一个副本[*错误]。我可以通过在我所有的插件上实现ICloneable
并在工厂中克隆它们来解决这个问题,但是,这既很难用依赖项来纠正,又会使所有插件混乱。
[*错误] 编辑:注意,根据选择的答案,这是错误的!因此,选项 #2 是最佳选择,只要您将插件的生命周期注册为瞬态。
-
我可以将插件类型而不是实例注入
PluginFactory
并使用Activator.CreateInstance<T>()
来创建实例。我甚至可以为插件定义一个通用的构造函数签名,并将参数传递给激活器以使用依赖项初始化它们。但是,这违反了我的要求,即插件具有不同的依赖项,并且还导致了约束构造反模式。 -
最后,在 3.我可以注入插件类型并使用反射来整理插件的依赖关系。但是,这听起来像是很多工作,让我想知道我是否正在尝试从 DI 容器构建或复制解析方法。后者是我绝对不想做的事情,也不想看到这一点。
因此,在我更喜欢#1中的替代方案中,即使这与RRR模式背道而驰。
实例化我的插件的最佳方式是什么(我倾向于#1(。我是否完全错过了一些替代方案?还是我可能误判了我的其他一些选择?
既然你已经引用了Mark Seemann,看看他的合成根文章和他的服务定位器角色与力学文章。长话短说,可以将容器引用/注入到作为组合根一部分的基础结构组件中。
因此,解决方案是定义一个IPluginFactory
抽象并将其放置在应用程序的核心库中。在你的组合根中(注意:将CR视为一个层,而不是一个类(,你可以为这个抽象创建一个实现,在这里可以注入容器。
在简单注入器中,注入的IEnumerable<T>
实例的行为就像流一样。这意味着每次迭代枚举对象时,都会再次要求容器提供实例。简单喷油器文档指出:
注意:简单注入器保留了返回的实例的生活方式 从注入的
IEnumerable<T>
实例。实际上你不应该 将注入的IEnumerable<IValidator<T>>
视为 实现 - 您应该将其视为实例流。简单 注入器将始终注入对同一流的引用(IEnumerable<T>
本身就是一个单例(,每次迭代IEnumerable<T>
,对于每个单独的组件,询问容器 以根据该组件的生活方式解析实例。 无论 [消费组件] 是否注册为 单例,它包装的验证器将每个都有自己特定的 生活方式。
容器将根据实例的生活方式返回实例。如果实例注册为暂时性实例,则意味着每次迭代集合时都会获得一个新实例。从某种意义上说,简单注入器注入的枚举项的行为就像工厂一样。下面是一个显示此行为的小测试:
[TestMethod]
public void EnumerablesBehaveAsStreams()
{
// Arrange
var container = new Container();
container.Collection.Register<ILogger>(typeof(SqlLogger), typeof(FileLogger));
IEnumerable<ILogger> loggers = container.GetAllInstances<ILogger>();
// Act
ILogger sqlLogger1 = loggers.First();
ILogger sqlLogger2 = loggers.First();
// Assert
Assert.AreNotSame(sqlLogger1, sqlLogger2);
}
一些开发人员对此感到困惑,但我认为这是唯一正确的行为,因为IEnumerable<T>
流的定义,而Simple Injector尊重这一点,因此为您提供了一个流。这样做有一些有趣的优势。例如,它允许将枚举注入到单例中,同时保留元素的生活方式。
如果你想注册一个插件集合,这将是这样做的方法:
List<Type> pluginTypes = LoadPluginTypes();
container.Collection.Register<IPlugin>(pluginTypes);
Collection.Register
将已注册类型的解析转发回容器,因此尊重注册每个类型的生活方式。在上面的示例中,我们没有单独注册这些类型。这意味着默认情况下假定所有元素都是瞬态的。但您可以按如下方式覆盖它:
List<Type> pluginTypes = LoadPluginTypes();
container.Register(pluginTypes.First(), Lifestyle.Singleton);
container.Collection.Register<IPlugin>(pluginTypes);
现在,集合中的第一个插件被注册为单例,而其他插件仍然是瞬态的。如果您想以相同的生活方式注册它们,您可以执行以下操作:
List<Type> pluginTypes = LoadPluginTypes();
pluginTypes.ForEach(type => container.Register(type, Lifestyle.Singleton));
container.Collection.Register<IPlugin>(pluginTypes);
另一种选择是将 Registration
实例的集合传递到 Collection.Register
方法中:
List<Type> pluginTypes = LoadPluginTypes();
container.Collection.Register(typeof(IPlugin),
from type in pluginTypes
select Lifestyle.Singleton.CreateRegistration(
typeof(IPlugin), type, container));
在这种情况下,以前的注册之间没有实际区别,但是当您想要执行更高级的操作(例如在多个抽象上共享同一注册实例(时,注册Registration
实例变得很有趣。
但是流行为在您的工厂中非常有用,因为您只需将IEnumerable<IPlugin>
注入工厂,生活方式就会保留:
public class PluginFactory : IPluginFactory
{
private readonly IEnumerable<IPlugin> plugins;
public PluginFactory(IEnumerable<IPlugin> plugins)
{
this.plugins = plugins;
}
public IPlugin GetPluginByName(string name)
{
return this.plugins.Single(plugin => plugin.Name == name);
}
}
这里唯一需要注意的是,如果您有兴趣通过过滤集合一次返回一个插件,则迭代集合将生成许多插件实例,即使您可能只对一个插件感兴趣。在上面的例子中,Single
将导致创建所有插件,而你只返回一个插件。更有效的做法是 First
,因为当插件符合标准时会停止,这会阻止创建所有以后的插件。
Simple Injector非常快,因此在这通常成为问题之前,收集可能会变得非常大,但这是需要注意的。如果您打算筛选列表并始终选择一个列表,则其他方法可能会更好。