Castle.Windsor: изменения для фабрик и перехватчиков в 3.1?

Следующий код проходит как есть с Castle.Windsor 2.5.3, но не проходит после обновления до 3.1.0

Исключением является InvalidProxyConstructorArgumentsException, в котором говорится: "Не удалось создать экземпляр прокси-сервера класса: Test. Не удалось найти конструктор без параметров".

    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) Как сохранить регистрацию перехватчика, не создавая прокси для объекта, разрешенного заводским методом? Или я думаю, как прокси генерируется с использованием фабричного метода для компонента...

2 ответа

Решение

В 2.5.3 Виндзор, похоже, молча не может применить перехватчик. В 3.1.0 Windsor генерирует исключение, когда не может применить перехватчик к типу, который вы зарегистрировали. Windsor использует свою библиотеку Dynamic Proxy для поддержки перехватчиков (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 {}

Это ужасный код во многих отношениях, но он воссоздает поведение, к которому вы привыкли. Просто будьте осторожны, чтобы не укусить вас в будущем, когда есть другой класс, на котором вы хотите включить перехватчик, и пытаетесь понять, почему он не вызывается.

Стефан поставил меня на правильный путь. Тот факт, что перехватчик не был назначен для тестирования в 2.5.3, является ошибкой / функцией, замаскированной под ошибку. Поведение 3.1.0 более корректно и заставляет вас четко указывать, на что вы хотите перехватить. Поскольку мой настоящий код включал в себя Facility, вот решение, которое решает проблему:

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"));
    }
}
Другие вопросы по тегам