Компоненты замка располагаются в порядке

У нас есть несколько компонентов Castle Windsor, объявленных в файле конфигурации. Некоторым компонентам где-то глубоко внутри могут потребоваться услуги других компонентов.

Проблема заключается в том, что приложение закрывается, а контейнер удаляется. Во время Dispose()/Stop() компонента Startable/Disposable (A), когда ему требуются услуги какого-либо другого компонента (B), возникает ComponentNotFoundException. К этому времени B уже извлечен из контейнера.

Я заметил, что порядок объявлений компонентов в файле конфигурации приложения важен. И переупорядочение A и B решает проблему.

Есть ли лучший способ повлиять на порядок расположения компонентов?

Отредактировано: После запроса в комментариях я привожу здесь пример кода, который вызовет исключение ComponentNotFoundException:

class Program
{
    static void Main()
    {
        IoC.Resolve<ICriticalService>().DoStuff();
        IoC.Resolve<IEmailService>().SendEmail("Blah");
        IoC.Clear();
    }
}

internal class CriticalService : ICriticalService, IStartable
{
    public void Start()
    {}

    public void Stop()
    {
        // Should throw ComponentNotFoundException, as EmailService is already disposed and removed from the container
        IoC.Resolve<IEmailService>().SendEmail("Stopping");
    }

    public void DoStuff()
    {}
}

internal class EmailService : IEmailService
{
    public void SendEmail(string message)
    {
        Console.WriteLine(message);
    }

    public void Dispose()
    {
        Console.WriteLine("EmailService Disposed.");
        GC.SuppressFinalize(this);
    }
}

internal interface ICriticalService
{
    void DoStuff();
}

internal interface IEmailService : IDisposable
{
    void SendEmail(string message);
}

public static class IoC
{
    private static readonly IWindsorContainer _container = new WindsorContainer(new XmlInterpreter());

    static IoC()
    {
        _container.AddFacility<StartableFacility>();
        // Swapping the following 2 lines resolves the problem
        _container.AddComponent<ICriticalService, CriticalService>();
        _container.AddComponent<IEmailService, EmailService>();
    }

    public static void Clear()
    {
        _container.Dispose();
    }

    public static T Resolve<T>()
    {
        return (T)_container[typeof(T)];
    }
}

Примечание. См. комментарий в коде, как изменение порядка вставки компонентов в контейнер решает проблему.


person Boris Lipschitz    schedule 01.02.2010    source источник
comment
Start/Stop — это две задачи, предоставляемые StartableFacility, а Disposable — еще одна задача, в принципе не связанная с start/stop. StartableFacility гарантирует, что проблема Stop будет выполнена первой при выводе компонента из эксплуатации. Если вы говорите, что Dispose() выполняется до Stop(), то это выглядит как ошибка. Можете ли вы показать нам код, может быть, опубликовать тестовый пример?   -  person Mauricio Scheffer    schedule 02.02.2010
comment
Спасибо за ваш комментарий. Мне кажется, что когда Контейнер удаляется, он перебирает каждый запускаемый компонент и вызывает каждый Stop(), затем Dispose() и удаляет его из Контейнера. Хотя я ожидаю, что сначала он должен вызвать Stop() для всех запускаемых объектов, а затем удалить их и удалить из контейнера.   -  person Boris Lipschitz    schedule 02.02.2010
comment
Да, контейнер выполняет итерацию по каждому запускаемому компоненту и вызывает каждую из них Stop(), а затем Dispose(), это поведение. Можете ли вы опубликовать тестовый пример, который выдает ComponentNotFoundException?   -  person Mauricio Scheffer    schedule 02.02.2010


Ответы (2)


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

Проблема в том, что без надлежащей инъекции Windsor не знает о зависимости CriticalService — IEmailService, поэтому не может обеспечить правильный порядок удаления.

Если вы выполняете рефакторинг, чтобы сделать эту зависимость явной, Windsor размещает компоненты в правильном порядке:

internal class CriticalService : ICriticalService, IStartable
{
    private readonly IEmailService email;

    public CriticalService(IEmailService email) {
        this.email = email;
    }
 ...
}

Вот как это будет выглядеть как после рефакторинга.

person Mauricio Scheffer    schedule 02.02.2010
comment
Спасибо за вашу помощь. Я полностью согласен с вашими комментариями о зависимости конструктора. Однако имейте в виду, что это был всего лишь образец, наша система намного больше и сложнее. Добиться этого непросто, но мы постараемся провести рефакторинг этой области. Что касается StartableFacility, я просто забыл добавить его в пример кода (оставил в конфигурационном файле). См. обновленный пример кода. Это не работает с запускаемым средством. - person Boris Lipschitz; 02.02.2010
comment
Протестировано с 2.1.1 и StartableFacility - ComponentNotFoundException - person Boris Lipschitz; 02.02.2010
comment
Кстати, каково ваше мнение об этом шаблоне Айенде: ayende.com /Blog/archive/2006/07/30/IoCAndFluentInterfaces.aspx - person Boris Lipschitz; 02.02.2010
comment
Борис, насчет поста Айенде - это плохо. Теперь у нас есть намного лучшие инструменты, чем ServiceLocator, а именно TypedFactoryFacility, DynamicParameters, UsingFactoryMethod... стремитесь не вызывать контейнер в вашем коде за пределами одного четко определенного места в вашей инфраструктуре ( как завод контроллеров) - person Krzysztof Kozmic; 02.02.2010
comment
@Boris: можешь скопировать и вставить и попробовать мой код? Можете ли вы прислать мне ZIP-файл с точным кодом и библиотеками DLL, которые вы используете? - person Mauricio Scheffer; 02.02.2010
comment
@Mauricio: я попробовал твой код. Это работает нормально. Я отправил вам мой образец проекта на вашу учетную запись gmail. Спасибо! - person Boris Lipschitz; 03.02.2010
comment
@Krzysztof: Большое спасибо за информацию. Мы разработали нашу структуру около 2 лет назад, основываясь на сообщениях в блоге Айенде о Castle и RhinoMocks. Может пора пересмотреть... - person Boris Lipschitz; 03.02.2010

Я лично считаю, что любая система, которая требует, чтобы Dispose() вызывалась в определенном порядке, имеет недостаток в дизайне.

Dispose() должен всегда вызываться безопасно. Ошибки должны возникать только в том случае, если компонент используется после утилизации, и тогда ObjectDisposedException имеет смысл. В таком случае я бы переработал ваши компоненты, чтобы они не использовали другие компоненты во время своего метода Dispose() (на самом деле речь должна идти об очистке собственных частных ресурсов каждого компонента). Это полностью устранило бы эту проблему.

person Reed Copsey    schedule 01.02.2010
comment
С одной стороны, вы правы, и мне, вероятно, следует выполнить итерацию по всем компонентам Startable и вызвать Stop() для них в том порядке, который мне нравится, а затем вызвать Castle.Windsor.IWindsorContainer.Dispose(). С другой стороны, я ожидаю, что фреймворк Castle сделает это от моего имени, поскольку он предоставляет метод Dispose(). - person Boris Lipschitz; 02.02.2010
comment
Одной из уникальных особенностей Windsor является то, что он управляет жизненным циклом объектов, которые создает для вас. Это означает (среди прочего), что он будет удалять все одноразовые объекты, которые он создает. - person Boris Lipschitz; 02.02.2010
comment
Хотя на самом деле это не имеет к этому никакого отношения. Они будут вызывать Dispose для ваших компонентов, но ваши компоненты не должны полагаться на существование других компонентов после вызова Dispose(). Порядок, в котором происходит удаление, не должен иметь значения... - person Reed Copsey; 02.02.2010