2

以下代码在 Castle.Windsor 2.5.3 中按原样通过,但在升级到 3.1.0 后失败

异常是 InvalidProxyConstructorArgumentsException,它指出“无法实例化类的代理:测试。找不到无参数的构造函数。”

    static void Main(string[] args)
    {
        var container = new WindsorContainer();
        container.Register(Component.For<Interceptor>(),
                           Component.For<Test>().UsingFactoryMethod(() => new Test(""))
                                                .Interceptors<Interceptor>());

        var test = container.Resolve<Test>(); //THROWS IN 3.1.0
    }
}

public class Test
{
    public readonly string S;

    public Test(string s)
    {
        S = s;
    }
}

public class Interceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();
    }
}

在我的真实代码中,Test 是一个使用工厂方法构建并注入到存储库中的 MongoDatabase。

在我的真实代码中,我还使用 AbstractFacility 的继承者来注册拦截器。这样我就不必为每个组件注册拦截器。两种形式的拦截器使用似乎在以后的解决方案中都以相同的方式工作/失败(在 2.5.3/3.1.0 中)。以下是该设施的简化版本供参考:

public class Facility : AbstractFacility
{
    protected override void Init() { Kernel.ComponentRegistered += KernelComponentRegistered; }

    static void KernelComponentRegistered(string key, IHandler handler)
    {
        if (typeof(IInterceptor).IsAssignableFrom(handler.ComponentModel.Implementation)) return;
        handler.ComponentModel.Interceptors.AddIfNotInCollection(InterceptorReference.ForKey("SomeInterceptor"));
    }
}

我查看了 Castle.Windsor 源代码,抛出的代码期望在给定的类周围包装一个代理,这就是它寻找无参数构造函数的原因。但是在 2.5.3 中,我认为代理生成代码从未被执行,并且容器(在我看来是正确的)解析为 Test/MongoDatabase 的非代理版本

所以我猜有两个问题:1)发生了什么变化?2)如何在不为工厂方法解析的对象生成代理的情况下保留我的拦截器注册?或者我猜想如何使用组件的工厂方法生成代理......

4

2 回答 2

2

在 2.5.3 中,Windsor 似乎默默地未能应用拦截器。在 3.1.0 中,Windsor 无法将拦截器应用于您注册的类型时会引发异常。Windsor 使用他们的动态代理库通过生成您请求的实例的代理来支持拦截器 (AOP)。所以这两个版本的问题是你给它的类不能变成一个动态代理,因为它没有一个无参数的构造函数。我认为 3.1.0 中的行为更正确,因为如果您希望应用拦截器,那么找出问题所在会困难得多。

如果您想保持 2.5.3 中它静默失败的行为,只需在您的设施中注册拦截器之前添加一个检查以查看该类型是否可以被代理。这样做可能会更好,但这是我想出的:

try
{
    ProxyGenerator generator = new ProxyGenerator();
    generator.CreateClassProxy(handler.ComponentModel.Implementation);
    handler.ComponentModel.Interceptors.AddIfNotInCollection(InterceptorReference.ForType<MyInterceptor>());
}
catch {}

从很多方面来看,这都是糟糕的代码,但它会重新创建您习惯的行为。请注意,当您真正想要拦截器的不同类并且正在努力弄清楚为什么没有调用它时,它不会咬住您。

于 2012-09-28T23:58:58.193 回答
0

Stefan 让我走上了正轨。在 2.5.3 中没有将拦截器分配给 Test 的事实是伪装成错误的错误/功能。3.1.0 的行为更正确,并迫使您明确说明您想要拦截器的内容。由于我的实际代码涉及一个设施,这里是解决问题的解决方案:

public class Facility : AbstractFacility
{
    protected override void Init() { Kernel.ComponentRegistered += KernelComponentRegistered; }

    static readonly List<Type> TypesNotToIntercept = new List<Type>
    {
        typeof(IInterceptor),   //Don't intercept Interceptors
        typeof(MulticastDelegate),  //Func<> and the like
        typeof(LateBoundComponent), //Resolved with a factory, such as MongoDatabase
    };

    static void KernelComponentRegistered(string key, IHandler handler)
    {
        if (TypesNotToIntercept.Any(type => type.IsAssignableFrom(implementation));
            return;
        handler.ComponentModel.Interceptors.AddIfNotInCollection(InterceptorReference.ForKey("SomeInterceptor"));
    }
}
于 2012-09-28T22:30:18.593 回答