Kamil Kliczbor @ asptip.net

10Dec/130

Castle Windsor – cykl życia komponentów

Castle Windsor

Wprowadzenie

Komponenty tworzone przez kontener posiadają swój cykl życia. Cykl życia określa warunki w jakich są tworzone, używane oraz niszczone. W Castle można skonfigurować mechanizmy, które w sposób dedykowany  będą umiały zarządzać dodatkową logiką przy powoływaniu oraz niszczeniu wcześniej utworzonych instancji. Wpis ten nie będzie omawiał dedykowanych zarządców powoływania i odwoływania instancji.

Powoływanie

W kontenerze w odniesieniu do tworzenia obiektów użyte jest sformułowanie"commission concerns". Metody powoływujące są uruchamiane na rzecz wcześniej już zinstancjonowanych komponentów, dla których zostały wstrzyknięte wszystkie zależności. Konfigurując więc conmmision concerny musimy mięć na uwadze, że otrzymujemy gotową do użycia instancję. Castle w trójnasób wspiera powoływanie obiektów poprzez wywołanie metody OnCreate w rejestratorze, implementację interfejsu IInitializable lub ISupportInitialize.

OnCreate

Jest to metoda, której można użyć na konfiguratorze rejestracji, jeżeli chcemy skonfigurować inline przetwarzanie instancji zaraz po jej inicjalizacji. Poniżej został przedstawiony listing użycia API na podstawie przykładu ze strony Castla:

container.Register(
   Component.For<IService>()
      .ImplementedBy<MyService>()
      .OnCreate((kernel, instance) => instance.Timestamp = DateTime.UtcNow)
   );

IInitializable

Interfejs ten jest ulokowany w przestrzeni nazw Castle.Core. Castle traktuje w sposób szczególny klasy implementujące ten interfejs - jest on uruchamiany po podstawowej inicjalizacji instancji. Jedynym minusem użycia tego interfejsu jest to, że nie można przekazać żadnych zależności z zewnątrz do takiej instancji.

void Initialize();

ISupportInitalization

W przypadku, jeżeli nie chcemy mieć bezpośredniego odwołania do Castle w domenie,  można posłużyć się niezależnym interfejsem ISupportInitalization z przestrzeni nazw System.ComponentModel. Interfejs tak samo jak w powyższym przypadku ma ograniczenie odnośnie uzyskiwania dodatkowych zależności od kontenera. Metody BeginInit oraz EndInit wywoływane są bezpośrednio po sobie, więc nie ma znaczenia w której z nich umieścimy logikę inicjalizacyjną.

void BeginInit();
void EndInit();

Odwoływanie

Udział w odwoływaniu obiektów (decomission) można zrealizować na dwa sposoby: wywołanie metody OnDestroy w rejestratorze lub implementację interfejsu IDisposable. Castle trzyma odniesienia do każdej encji, którą stworzył. Ważne jest, aby pamiętać o odpowiednim zarządzaniu pamięcią i zwalnianiu zasobów, kiedy nie są już używane.

OnDestroy

Jest to metoda analogiczna w stosunku do OnCreate, przy czym w tym przypadku nie mamy "w ręku" implementacji IKernel. Poniżej został przedstawiony listing użycia API na podstawie przykładu ze strony Castla:

container.Register(Component.For<MyClass>()
   .LifestyleTransient()
   .OnDestroy(myInstance => myInstance.ByeBye())
);

IDisposable

W tym przypadku kontener jest w stanie obsłużyć standardowy interfejs IDisposable używany przez .NET przy niszczeniu obiektów. Jeżeli dana instancja będzie miała być zniszczona, to zostanie wywołana metoda Dispose.

Przykład - eksperyment

W przykładzie pokażę sposób użycia wyżej wymienionych implementacji. Będziemy mieli następujące komponenty: IService wraz z implementacją MyService oraz DateTimeProvider wstrzykiwany do tej implementacji .

using System;
using System.ComponentModel;
using Castle.Core;

namespace CommissionConcerns
{
    public interface IService
    {
    }

    public class MyService : IService, ISupportInitialize, IInitializable, IDisposable
    {
        private readonly DateTimeProvider _dateTimeProvider;

        public MyService(DateTimeProvider dateTimeProvider)
        {
            _dateTimeProvider = dateTimeProvider;
            Console.WriteLine("MyService.ctor {0}", _dateTimeProvider.GetCurrentDateTime());
        }

        public void BeginInit()
        {
            Console.WriteLine("BeginInit()");
        }

        public void EndInit()
        {
            Console.WriteLine("EndInit()");
        }

        public void Initialize()
        {
            Console.WriteLine("Initialize()");
        }

        public void Dispose()
        {
            Console.WriteLine("Dispose()");
        }
    }

    public class DateTimeProvider
    {
        public DateTime GetCurrentDateTime()
        {
            return DateTime.Now;
        }
    }
}

Rejestracje oraz konfiguracja powoływania oraz odwoływania instancji:

using System;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;

namespace CommissionConcerns
{
    internal class WindsorInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Component
                    .For<IService>()
                    .ImplementedBy<MyService>()
                    .OnCreate(
                        (kernel, instance) => Console.WriteLine("OnCreate: instance is null = {0}",  instance == null))
                    .OnDestroy(
                        (kernel, instance) => Console.WriteLine("OnDestroy: instance is null = {0}",  instance == null)),
                Component
                    .For<DateTimeProvider>()
                );
        }
    }
}

Oraz widok na listing z metody Main:

using System;
using Castle.Windsor;

namespace CommissionConcerns
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            using (var container = new WindsorContainer())
            {
                container.Install(new WindsorInstaller());
                var service = container.Resolve<IService>();
                Console.WriteLine("After object comission");
            }

            Console.WriteLine("After object decomission");
            Console.ReadKey();
        }
    }
}

Rezultat wykonania przykładu:

MyService.ctor 12/10/2013 20:37:53 AM
OnCreate: instance is null = False
Initialize()
BeginInit()
EndInit()
After object comission
OnDestroy: instance is null = False
Dispose()
After object decomission

Wnioski

Windsor Castle dostarcza kilka miejsc rozszerzeń, dzięki którym można kontrolować proces inicjalizacji oraz niszczenia instancji. Co więcej, konfiguracja umożliwia zdefiniowanie logiki inicjalizacji wprost przy rejestracji komponentu lub poprzez implementację interfejsu z przestrzeni nazw Castle lub systemowych. Przy czym przy drugim podejściu nie musimy skazywać np. projektu domeny na bezpośrednią referencję do kontenera. Innym wnioskiem jest to, że poszczególne podejścia zajmują swoje ustalone miejsce w kolejności wywoływania.

Bibliografia

Głównie http://docs.castleproject.org/Windsor.Lifecycle.ashx.

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.