我一直试图让nServiceBus与Ninject 2.0一起作为基础IoC容器使用失败。虽然我可以实现基本集成,但我遇到了将“鬼”消息发送给各个订阅者的问题。我使用Autofac实现作为各种模板,用Ninject特定代码替换必要的部分。此外,我确实需要创建一个自定义启发式来进行自动属性注入。
无论如何,我看到的行为是第一条消息可以被订阅者发布并成功读取;然而,下一个发布的消息导致消息被“接收”三次。
所以,我想知道: 是否有人使用Ninject作为nServiceBus ObjectBuilder做任何事情?或者,在集成当前与nServiceBus 2.0捆绑在一起的其他IoC容器(即Windsor,StructureMap或Autofac)时,是否有人看到并纠正了这种行为。
编辑:我确实看了this,但看起来并不完整,我认为属性注入的启发式应该有点不同。
答案 0 :(得分:5)
在nservicebus组上有一个讨论这个问题的线程,但是还没有解决方案。
答案 1 :(得分:4)
找到解决方案,虽然我有两个问题。
第一个问题源于在IContainer.Configure
类的NinjectObjectBuilder
方法中使用Ninject内核注册/配置对象的方式。在使用其他IoC容器检查了nServiceBus的ObjectBuilder
的现有实现之后,我注意到注册的一般方法是注册具体类型本身以及所实现类型的所有接口。在Ninject中,这相当于“将具体类型绑定到自身”,然后将类型实现的每个接口绑定到该类型。这是相当简单的,除了我在使用dotTrace进行分析后发现的是,在Singleton激活的情况下,我似乎并没有真正得到Singleton引用。实际上,会发生什么事情,我会根据请求的服务类型获得一个新对象。例如,UnicastBus
具体类型实现IBus
以及IStartableBus
,并在单例范围内注册。无论是IBus
还是IStartableBus
,nServiceBus都希望接收相同的对象,如果它们是单例并且都“绑定”到同一个实现。 Ninject对单例的解释似乎与服务或界面有关 - 换句话说,每次请求UnicastBus
时,您都会获得IBus
的相同实例;但是,对于UnicastBus
的请求,您会收到一个新的,不同的IStartableBus
。我解决这个问题的方法是实现IContainer.Configure
方法,如下所示:
void IContainer.Configure(Type concreteComponent,
ComponentCallModelEnum callModel) {
if (HasComponent(concreteComponent))
return;
var services = concreteComponent.GetAllServices()
.Where(t => t != concreteComponent);
var instanceScope = GetInstanceScopeFrom(callModel);
// Bind the concrete type to itself ...
kernel
.Bind(concreteComponent)
.ToSelf()
.InScope(instanceScope);
// Bind "aliases" to the binding for the concrete component
foreach (var service in services)
kernel
.Bind(service)
.ToMethod(ctx => ctx.Kernel.Get(concreteComponent))
.InScope(instanceScope);
}
这解决了以符合nServiceBus期望的方式解决单例实例的问题。但是,我仍然遇到了在处理程序中接收“ghost”消息的问题。梳理完log4net日志文件,分析并最终阅读所讨论的问题here和here。该问题特别源于在属性注入期间附加的多个事件处理程序。具体来说,问题是由于UnicastBus将Transport
属性设置为多个时间而引起的。这是nServiceBus代码库中UnicastBus.cs的代码片段:
public virtual ITransport Transport
{
set
{
transport = value;
transport.StartedMessageProcessing += TransportStartedMessageProcessing;
transport.TransportMessageReceived += TransportMessageReceived;
transport.FinishedMessageProcessing += TransportFinishedMessageProcessing;
transport.FailedMessageProcessing += TransportFailedMessageProcessing;
}
}
在考虑之后,我想知道为什么这个属性被多次设置。 UnicastBus
由nServiceBus在单例范围内注册,我刚刚解决了上述问题。事实证明,Ninject在激活一个对象时 - 无论是新的还是来自它的内部缓存 - 仍然会注入对象的属性。它将调用在其内部内核组件容器中注册的注入启发式类,并根据它们的响应(即调用其bool ShouldInject(MemberInfo member)
实现的结果),在每次激活之前注入属性。因此,解决方案是阻止Ninject对先前已激活且为单例的实例执行属性注入。我的解决方案是创建一个新的属性注入策略,该策略跟踪先前在范围内不是瞬态的激活实例,并跳过针对此类实例的激活请求的默认属性注入策略。我的策略是这样的:
/// <summary>
/// Only injects properties on an instance if that instance has not
/// been previously activated. This forces property injection to occur
/// only once for instances within a scope -- e.g. singleton or within
/// the same request, etc. Instances are removed on deactivation.
/// </summary>
public class NewActivationPropertyInjectStrategy : PropertyInjectionStrategy {
private readonly HashSet<object> activatedInstances = new HashSet<object>();
public NewActivationPropertyInjectStrategy(IInjectorFactory injectorFactory)
: base(injectorFactory) { }
/// <summary>
/// Injects values into the properties as described by
/// <see cref="T:Ninject.Planning.Directives.PropertyInjectionDirective"/>s
/// contained in the plan.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// activated.</param>
public override void Activate(IContext context,
InstanceReference reference) {
if (activatedInstances.Contains(reference.Instance))
return; // "Skip" standard activation as it was already done!
// Keep track of non-transient activations...
// Note: Maybe this should be
// ScopeCallback == StandardScopeCallbacks.Singleton
if (context.Binding.ScopeCallback != StandardScopeCallbacks.Transient)
activatedInstances.Add(reference.Instance);
base.Activate(context, reference);
}
/// <summary>
/// Contributes to the deactivation of the instance in the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// deactivated.</param>
public override void Deactivate(IContext context,
InstanceReference reference) {
activatedInstances.Remove(reference.Instance);
base.Deactivate(context, reference);
}
}
我的实施现在正在运作。我唯一的另一个挑战是“替换”现有的财产注入激活策略。我想过创建一个自定义内核(这可能是最好的方法);但是,您无法支持nServiceBus的“预配置”内核。现在我有一个扩展方法,可以将新组件添加到任何Ninject内核。
public static void ConfigureForObjectBuilder(this IKernel kernel) {
// Add auto inject heuristic
kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();
// Replace property injection activation strategy...
/* NOTE: I don't like this! Thinking about replacing the pipeline component
* in Ninject so that it's smarter and selects our new activation
* property inject strategy for components in the NServiceBus DLLs and
* uses the "regular strategy" for everything else. Also, thinking of
* creating a custom kernel.
*/
kernel.Components.RemoveAll<IActivationStrategy>();
kernel.Components.Add<IActivationStrategy,
NewActivationPropertyInjectStrategy>();
// The rest of the "regular" Ninject strategies ...
kernel.Components.Add<IActivationStrategy, MethodInjectionStrategy>();
kernel.Components.Add<IActivationStrategy, InitializableStrategy>();
kernel.Components.Add<IActivationStrategy, StartableStrategy>();
kernel.Components.Add<IActivationStrategy, BindingActionStrategy>();
kernel.Components.Add<IActivationStrategy, DisposableStrategy>();
}
这是一个彻头彻尾的黑客攻击,因为内核组件容器上没有用于“替换”现有组件的机制。而且,由于我想要覆盖属性注入策略的现有行为,因此我一次不能在内核中拥有多个特定类型的策略。此当前实现的另一个问题是可能已配置的任何其他自定义IActivationStrategy
组件将丢失。我想编写能够获取列表中所有IActivationStrategy
组件的代码,从内核中删除它们,替换我创建的列表中的属性注入策略,然后将它们全部添加回内核,从而有效地替换它们。但是,内核组件容器只支持通用Add
方法,我不想编写时髦的代码来创建动态调用。
**编辑** 我昨天发布后,我决定更好地处理这个策略。这就是我所做的,将所有内容捆绑在扩展方法中以配置Ninject内核:
public static void ConfigureForObjectBuilder(this IKernel kernel) {
// Add auto inject heuristic
kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();
// Get a list of all existing activation strategy types
// with exception of PropertyInjectionStrategy
var strategies = kernel.Components.GetAll<IActivationStrategy>()
.Where(s => s.GetType() != typeof (PropertyInjectionStrategy))
.Select(s => s.GetType())
.ToList();
// Add the new property injection strategy to list
strategies.Add(typeof (NewActivationPropertyInjectStrategy));
// Remove all activation strategies from the kernel
kernel.Components.RemoveAll<IActivationStrategy>();
// Add the list of strategies
var addMethod = kernel.Components.GetType().GetMethod("Add");
strategies
.ForEach(
t => addMethod
.MakeGenericMethod(typeof (IActivationStrategy), t)
.Invoke(kernel.Components, null)
);
}
答案 2 :(得分:3)
将我的想法拉入官方存储库:
https://github.com/NServiceBus/NServiceBus/tree/master/src/impl/ObjectBuilder/ObjectBuilder.Ninject
丹尼尔
答案 3 :(得分:1)
Hy Peter,
我找到了一种动态交换策略的方法(我在NinjectObjectBuilder的构造函数中这样做):
this.kernel.Bind<NewActivationPropertyInjectStrategy>().ToSelf().WithPropertyValue("Settings", ctx => ctx.Kernel.Settings);
this.kernel.Bind<IInjectorFactory>().ToMethod(ctx => ctx.Kernel.Components.Get<IInjectorFactory>());
this.kernel.Components.Get<ISelector>().InjectionHeuristics.Add(this.propertyHeuristic);
IList<IActivationStrategy> activationStrategies = this.kernel.Components.Get<IPipeline>().Strategies;
IList<IActivationStrategy> copiedStrategies = new List<IActivationStrategy>(
activationStrategies.Where(strategy => !strategy.GetType().Equals(typeof(PropertyInjectionStrategy)))
.Union(new List<IActivationStrategy> { this.kernel.Get<NewActivationPropertyInjectStrategy>() }));
activationStrategies.Clear();
copiedStrategies.ToList().ForEach(activationStrategies.Add);
答案 4 :(得分:0)
Here's an example of how to use the NinjectObjectBuilder
此示例显示了一个网站通过NServiceBus将命令发送到后端,并为网站和后端注入了Ninject依赖注入。
我从Daniel Marbach链接的the official repository复制了NinjectObjectBuilder。据我所知,代码尚未作为NServiceBus主流版本的一部分发布。我想今天使用它,所以我复制了代码并将其链接到NServiceBus 2.6。