Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

18 Grudzień 2008

Ugryźć Spring.Net – (cz.2) Programowanie aspektowe

Filed under: Ugryźć Spring.Net — Tags: , , , , , , — Beniamin Zaborski @ 07:03

W tej części zajmiemy się AOP, a raczej tym co ma wspólnego Spring.Net z AOP. Na wstępie odrobina teorii i wyjaśnienia podstawowych terminów. Co to jest AOP?
AOP to skrót od Aspect Oriented Programming czyli po naszemu programowanie aspektowe. Jest to paradygmat programowania, którego główna idea to modularyzacja i enkapsulacja kodu. Autorem całej tej idei jest zespół z firmy Xerox PARC. To oni stworzyli pierwszy i chyba nadal najbardziej popularny aspektowy język programowania – AspectJ.
AOP świetnie uzupełnia podejście OOP czyli Object Oriented Programming. W OOP myślimy o programie jako o hierarchii obiektów, natomiast AOP rozkłada program na tzw. aspekty. Aspekt możemy traktować jako pewne zadanie, które wykonujemy w naszej aplikacji i jest ono rozproszone na wiele obiektów, np. logowanie. Logowanie jest najczęściej przytaczanym przykładem w kontekście programowania aspektowego. Charakter tej operacji idealnie pasuje do paradygmatu aspektowego.

Załóżmy, że mamy w naszej aplikacji sporą grupę obiektów typu Data Access Object i każdy z nich posiada m.in. podstawowe operacje CRUD. Chcemy aby każde wywołanie metody Delete z każdego obiektu DAO było logowane do pliku. Aby się nie napracować zbytnio tu przychodzi nam z pomocą AOP. Zanim przejdziemy do praktycznych przykładów wyjaśnić należy kilka podstawowych terminów związanych z programowaniem aspektowym.

  • Jointpoint (punkt złączenia): dowolne, identyfikowalne miejsce w programie posiadające kontekst, jak: wywołanie metody, wywołanie konstruktora, wyrzucenie wyjątku, itp.
  • Advice (rada): akcja wykonywana przed, po lub zamiast osiągnięcia przez program punktu złączenia. 
  • Pointcut (punkt przekroju): zbiór punktów złączeń dla których powinna się wykonywać wskazana rada.

Odnosząc te terminy do naszego przykładu, to punktem złączenia możemy nazwać metodę Delete, radą – fragment kodu zapisującego informacje do pliku, a punktem przekroju informację np. w postaci wyrażenia regularnego określającego, że rada ma się wykonać we wszystkich obiektach DAO.

Spring.Net posiada wbudowany framework AOP. Oczywiście jeśli nie potrzebujemy go w naszej aplikacji nikt nie wymaga od nas używania go. Trzeba mieć jednak świadomość tego, iż kilka mechanizmów frameworka Spring.Net opiera się na AOP, jak choćby transakcje. Z AOP jest związany jeszcze tzw. proces Weaver czyli tkanie. Polega to na dołączeniu aspektów do oryginalnego kodu. W Spring.Net odbywa się to poprzez generowanie obiektów proxy w trakcie uruchomienia aplikacji (dzięki System.Reflection.Emit jest tworzony odpowiedni kod IL).

Zacznijmy od punktów złączeń odnosząc naszą wiedzę na ten temat do tego co oferuje nam w tym temacie Spring. W Spring-u znajdziemy interfejs Spring.Aop.IPointcut, z którego zainteresuje nas najbardziej property MethodMatcher typu IMethodMatcher.

public interface IMethodMatcher
{
    bool IsRuntime { get; }
     bool Matches(MethodInfo method, Type targetType);
    bool Matches(MethodInfo method, Type targetType, object[] args);
}

 
Metoda Matches(MethodInfo method, Type targetType) jest wywoływana, aby określić czy punkt złączenia będzie osiągnięty/dopasowany na docelowym obiekcie targetType. Takie sprawdzanie będzie wykonane w momencie tworzenia obiektu proxy, aby uniknąć potrzeby weryfikacji przy każdym wywołaniu metody.  Jeśli wystąpi dopasowanie wtedy Matches(MethodInfo method, Type targetType) zwraca true podobnie jak właściwość IsRuntime. Trzy-argumentowa wersja Matches zostanie wywołana na każde wykonanie metody.
Najczęściej stosowaną metodą określania pointcut-ów są wyrażenia regularne. W tym celu Spring dostarcza klasę: Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor. Używając tej klasy możemy dostarczyć listę pattern-ów które określą dopasowanie. Prosty przykład:

<object id=”settersAndAbsquatulatePointcut”
    type=”Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop”>
    <property name=”patterns”>
        <list>
            <value>*Delete</value>
            <value>*Usun</value>
        </list>
    </property>
</object>

Jeśli którekolwiek z tych wyrażeń będzie spełnione wtedy Matches zwróci true – dla metod kończących się na Delete lub Usun. Inne podejście to dopasowanie po atrybucie, np.:

<object id=”cachePointcut” type=”Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop”>
    <property name=”Attribute” value=”Spring.Attributes.CacheAttribute, Spring.Core”/>        
</object>

Takie użycie dopasuje wszystkie metody mające nałożony atrybut Spring.Attributes.CacheAttribute.
Ustaliliśmy już gdzie chcemy wykonać nasz kod, teraz jak określić co chcemy wykonać? Spring.Net dostarcza nam kilka typów rad i odpowiadających im interfejsów :

  • interception around advice – wykonanie w miejsce metody
public interface IMethodInterceptor : IInterceptor
{
    object Invoke(IMethodInvocation invocation);
}

Nasza przykładowa implementacja takiego typu advice-a może wyglądać tak;

public class DebugInterceptor : IMethodInterceptor
{
    public object Invoke(IMethodInvocation invocation)
   {
        Console.WriteLine(„Before: invocation=[{0}]”, invocation);
        object rval = invocation.Proceed();
        Console.WriteLine(„Invocation returned”);
        return rval;
    }
}

Jak widzimy do metody Invoke jest przekazywany parametr invocation, który pozwala nam samemu wykonać właściwą metodę i zwrócić jej wynik.

  • before advice – wykonanie przed wykonaniem metody
public interface IMethodBeforeAdvice : IBeforeAdvice
{
    void Before(MethodInfo method, object[] args, object target);
}

 

  • throws advice – wykonanie przy wyrzucaniu wyjątku
public interface IThrowsAdvice : IAdvice

 

  • after returning advice – wykonanie po wykonaniu metody, przed zwróceniem wyniku
public interface IAfterReturningAdvice : IAdvice
{
  void AfterReturning(object returnValue, MethodBase method, object[] args, object target);
}

Wracając do naszego przykładu najbardziej odpowiedni typ advice to before advice. Zaimplementujmy go zatem:

public class LogujUsuwanieBeforeAdvice : IMethodBeforeAdvice
{
    public void Before(MethodInfo method, object[] args, object target)
    {
        Logger.ToFile(“Usunięto obiekt”, LogLevel.Warning);
       }
}

A tak powinien wyglądać kompletny plik konfiguracyjny do naszego przykładu:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <configSections>
    <sectionGroup name=”spring”>
      <section name=”context” type=”Spring.Context.Support.ContextHandler, Spring.Core”/>
      <section name=”objects” type=”Spring.Context.Support.DefaultSectionHandler, Spring.Core” />
    </sectionGroup>
  </configSections>
  <spring>
    <context>
      <resource uri=”config://spring/objects”/>
    </context>
    <objects xmlns=”http://www.springframework.net”&gt;
      <object id=”abonentDAO” type=”BizDev.SimpleSpringAOP.AbonentDAO, BizDev.SimpleSpringAOP”/>
      <object id=”uzytkownikDAO” type=”BizDev.SimpleSpringAOP.UzytkownikDAO, BizDev.SimpleSpringAOP”/>

      <object id=”logujUsuwanieBeforeAdvice” type=”Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop”>
        <property name=”patterns”>
          <list>
            <value>.*Delete</value>
            <value>.*Usun</value>
          </list>
        </property>
        <property name=”advice”>
          <object type=”BizDev.SimpleSpringAOP.LogujUsuwanieBeforeAdvice, BizDev.SimpleSpringAOP”/>
        </property>
      </object>

      <object id=”daos” type=”Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop”>
        <property name=”ObjectNames”>
          <list>
            <value>*DAO</value>
          </list>
        </property>
        <property name=”interceptorNames”>
          <list>
            <value>logujUsuwanieBeforeAdvice</value>
          </list>
        </property>
      </object>

    </objects>
  </spring>
</configuration>

Kilka słów podsumowania tego co widzimy w konfiguracji. Utworzyliśmy własną radę LogujUsuwanieBeforeAdvice wykonującą nasze zadanie tj. logowanie informacji do pliku. Następnie zdefiniowaliśmy pointcut dla tego advice-a określając, że ma się wykonywać dla metod kończących się ciągiem znaków Delete lub Usun. Na końcu zdefiniowaliśmy obiekt typu ObjectNameAutoProxyCreator wskazując nasz advice oraz określając, że ma być wykonany na wszystkich obiektach, których nazwa kończy się na DAO.
       
Teraz fragment kodu aktywujący wywołanie zaaplikowanych advice-ów:
     

IApplicationContext ctx = ContextRegistry.GetContext();
IDAO adao = (IDAO)ctx.GetObject(„abonentDAO”);
adao.Delete();
adao.SafeDelete();
       
IDAO udao = (IDAO)ctx.GetObject(„uzytkownikDAO”);
udao.Delete();
udao.SafeDelete();

      
A więc zadanie, które sobie założyliśmy na początku zostało zrealizowane bez modyfikacji kodu klas Data Access Object. Warto wspomnieć, iż to wszystko co skonfigurowaliśmy w pliku konfiguracyjnym można zrobić w kodzie bez udziału kontenera IoC, np.:
     

ProxyFactory factory = new ProxyFactory(new AbonentDAO());
factory.AddAdvice(new LogujUsuwanieBeforeAdvice());
IDAO dao = (IDAO)factory.GetProxy();
dao.Delete();

      
Do tego artykułu wybrałem przypadek nie do końca najbardziej trywialny od strony konfiguracji, ale jakże praktyczny. Oczywiście advice-y można aplikować jeszcze na kilka innych sposobów, ale po szczegóły odsyłam wszystkich zainteresowanych  do dokumentacji projektu. W kolejnej części napiszę o mechanizmach dostępu do danych w Spring.Net.

Dodaj komentarz »

Brak komentarzy.

RSS feed for comments on this post. TrackBack URI

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

Stwórz darmową stronę albo bloga na WordPress.com.

%d bloggers like this: