强制使用 Castle Windsor 进行属性注入






4.89/5 (4投票s)
如何重写 Castle Windsor 的默认行为,并将属性设为必需的依赖项。
引言
本技巧演示了如何修改 Castle Windsor 的默认行为,以便将属性标记为必需的。
背景
在 Castle Windsor 中,容器系统可以通过两种基本方式注入依赖项。第一种(也是最常见的)是通过构造函数注入,容器会自动使用正确的依赖项调用你的构造函数。第二种方式是通过属性注入。通过属性注入,容器会在调用对象的构造函数之前自动注入任何它能够满足的依赖项。
使用构造函数注入时,开发人员需要提前声明对象所需的所有依赖项。这可以使编写测试或仅仅是理解代码变得更容易,因为“必需”和“可选”之间没有歧义。然而,这种方法的缺点是并非所有这些“必需”的依赖项**确实**是必需的(例如,某些方法可能只需要某些依赖项)。此外,构造函数注入迫使任何子类显式了解基类的依赖项,因为子类的构造函数必须依次调用基类的构造函数。最后,为了实现这一点,必须编写一定量的样板代码。首先必须为依赖项创建一个只读字段,然后创建一个接受依赖项的构造函数,最后创建一个设置该字段的赋值语句。
另一方面,我们有属性注入。通过属性注入,我们只需创建一个带有 getter/setter 的 `public` 属性,容器系统就会处理其余的事情。虽然这消除了构造函数注入的大部分开销,但它有一个注意事项。默认情况下,如果容器系统无法满足依赖项,它会简单地跳过它。这与构造函数注入不同,后者如果任何必需的依赖项无法解析,容器系统就会抛出异常。那么,有没有办法重写这种行为,以便我们可以利用属性注入的简洁性,而又不牺牲构造函数注入提供的安全保障?幸运的是,答案是肯定的!
Using the Code
我是 Castle Windsor 的忠实粉丝。我喜欢它的原因之一是它能够轻松地重写系统工作的几乎所有方式。虽然“它通常都能正常工作”99% 的时间,但总会有相应的接口和/或回调,以应对那些需要额外一点控制的偶尔情况。在这个例子中,我们将仔细研究 IContributeComponentModelConstruction 接口。
Castle 已经有关于此接口如何工作的优秀文档,我不会抄袭它们。本质上,这个接口允许我们在组件被正式“烘焙”到容器中之前对其进行“调整”。Castle 自己的文档已经展示了一个使用此接口的简单示例,以便使特定属性成为强制性的。我们在这里要做的是创建一个更通用的解决方案,以便任何属性都可以被标记为必需的。
为了实现这一点,我们需要做的第一件事就是为我们的属性添加注解,以便可以轻松地将它们识别为必需的。这可以通过一个简单的属性来完成。
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MandatoryAttribute : Attribute { }
第二步是用该属性来修饰我们的属性,以将它们指定为必需的。这就像
public class MyClass
{
[Mandatory] public IMyDependency SomeDependency { get; set; }
}
第三步是创建组件模型构建贡献者本身。这里的想法是使用反射来查找我们的 Mandatory 属性是否存在,以便允许该属性“选择加入”我们更新的行为。该实现如下所示
public class MandatoryPropertyComponentModelHelper : IContributeComponentModelConstruction
{
public void ProcessModel(IKernel kernel, ComponentModel model)
{
foreach(var property in model.Properties)
{
if(property.Property.GetCustomAttributes(inherit: true).Any(x => x is MandatoryAttribute))
{
property.Dependency.IsOptional = false;
}
}
}
}
最后一步是告诉 Castle 使用我们的贡献者。最简单的方法是直接调用内核的 `ComponentModelBuilder` 属性的 `AddContributor()` 方法。
最终想法
每当我向开发人员展示一个工具或“技巧”时,我都会提醒他们,他们永远不应该将其视为万能药。这种技术可以帮助消除样板代码并隐藏子类的依赖项,但它也可能使理解类的需求变得更加困难,因为这些信息不再编码在构造函数中。良好的判断力是关键。
另外,本技巧仅关注 Castle Windsor。不是要贬低其他优秀的 DI 系统(例如 Autofac、StructureMap、Ninject、Unity 等),但我对它们不够精通,无法提供代码类比。如果有人想将代码示例翻译成特定容器的特定语言,我将很乐意进行更新。
最后,还有一件事需要记住,在使用属性注入时,Castle 会在构造函数被调用**之后**注入属性。如果你需要执行需要这些依赖项的初始化,请牢记这一点。