Com\’è dura la professione!

23 febbraio 2007

AOP in .net con mono

Filed under: .net,mono,Programmazione — lbell @ 0:32

 

Aspect Oriented Programming è un paradigma di programmazione, cresciuto in importanza negli ultimi tempi, che pone l’accento sulla gestione di funzionalità comuni ad insiemi di oggetti, indispensabili per il corretto funzionamento degli oggetti stessi, ma che non fanno parte del loro compito specifico.

Tali funzionalità sono importanti specialmente quando gli oggetti sono inseriti in un framework, e possono coprire compiti come la gestione della sicurezza, o la tracciabilità delle chiamate ai metodi degli oggetti stessi e la consistenza dei parametri passati, senza che il programmatore debba preoccuparsene nella realizzazione degli oggetti. Un esempio di AOP ante litteram può essere considerato MTS di Microsoft.

.NET è stato ideato anche per indirizzare anche questo tipo di esigenze. La chiave per gestire AOP sta nel CLR che sotto precise condizioni permette di creare ed attivare oggetti ‘iniettando’ wrapper a runtime in modo trasparente sia agli oggetti stessi che alle classi che ne fanno uso. Quando una classe .net (che chiameremo target) deriva da ContextBoundObject, alla creazione di una sua istanza mediante esecuzione dell’operatore new da parte di un chiamante, vengono in realtà creati tre oggetti: un transparent proxy (chiamiamolo tp per semplicità), un real proxy (tp) e l’oggetto target. Tp è l’oggetto effettivamente ritornato al chiamante e che sarà usato come se fosse l’oggetto reale. Lo scopo del tp è intercettare le chiamate effettuate ai metodi del target e trasformarle in veri e propri messaggi, cioè strutture di alto livello indipendenti dall’architettura della cpu, da spedire al rp che quindi può essere tranquillamente scritto in C# o altro linguaggio interfacciato con il CLR. Il rp si occupa di ricevere i messaggi dal tp e chiamare i metodi corrispondenti dell’oggetto finale, ma può gestire una catena di filtri, scritti anch’essi in un linguaggio ad alto livello che intercettano ed elaborano i vari messaggi prima che arrivino al target. In questo modo possiamo controllare gli oggetti a runtime senza modificarli. L’oggetto target stesso, essendo derivato da una particolare classe, ContextBoundObject, viene controllato dal CLR in modo che quando ritorna da un metodo riferimenti a se stesso, in realtà ritorni un riferimento al tp, impedendo un accesso diretto da parte del chiamante. Il fatto che la classe sia derivata da ContextBoundObject obbliga il programmatore a definire in anticipo quali saranno le classi intercettabili, il che da un certo punto di vita è un bene perché obbliga ad effettuare una buona analisi, ma dall’altro toglie quella flessibilità che si voleva introdurre in origine.

Vediamo in dettaglio il funzionamento, facendo riferimento al codice di esempio pubblicato alla fine del post.

Alla creazione dell’oggetto target vengono esaminati gli attributi di tipo IContextAttribute associati alla sua classe; per ognuno di questi (ricordo che un attributo è una classe .net) viene chiamato il metodo IsNewContextOK() se uno di questi metodi torna false, viene creato un nuovo contesto per l’oggetto e di seguito GetPropertiesForNewContext(). Sfruttando quest’ultimo metodo possiamo inserire nel contesto una istanza di una nostra classe (nell’esempio InterceptorInjector) che chiamata dal CLR ed implementando una particolare interfaccia (IContributeServerContextSink per noi), potrà inserire una classe ulteriore (Interceptor) nella catena della gestione dei messaggi scambiati tra rp ed il target. In seguito il CLR nei passaggi che vanno dal chiamante al target attraverso i filtri eventualmente inseriti, si prenderà cura di fare in modo che il target venga eseguito sempre nello stesso contesto, diverso in linea di principio da quello del chiamante. Questi passaggi di filtraggio vengono eseguiti attraverso oggetti che implementano l’interfaccia System.Runtime.Remoting.Message.IMessageSink; ve ne sono di quattro tipi, nell’ordine di invocazione dal rp al target

  • Envoy: ha informazioni sull’istanza; si trova nel contesto del chiamante

  • Client: gestisce il contesto di esecuzione del chiamante. E’ associato al tipo del chiamante (metodo di classe) ;si trova nel contesto del chiamante

  • Server: gestisce e controlla le chiamate nel contesto del target indipendentemente dal target (metodo di classe); si trova nel contesto del target

  • Object: gestisce e controlla le chiamate nel contesto del target ed ha informazioni sull’istanza su cui opera, al contrario del tipo Server; si trova nel contesto del target

Nel passaggio tra Client e Server il CLR esegue un eventuale cambio di contesto.

Le interfacce degli oggetti implementanti IContextProperty, che permettono di iniettare un gestore di messaggi per i quattro diversi tipi di filtri si chiamano rispettivamente, con poca fantasia, IContributeEnvoyContextSink, IContributeClientContextSink, IContributeServerContextSink e IContributeObjectContextSink.

Passiamo ora ad un esempio pratico utilizzando mono come ambiente di sviluppo. Il nostro target sarà un oggetto con due metodi di cui tracceremo parametri di invocazione e tempi di esecuzione. All’ingresso di ogni metodo verrà anche effettuato un log su console, per dare una idea della successione degli avvenimenti.

 

Prima di tutto ci serve la classe che processa i messaggi in arrivo. In questa classe intercettiamo tutti i metodi diretti al target, e in CheckArgs() controlliamo anche che il metodo Metodo2 non sia chiamato con un argomento nullo.

using System;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;

namespace ProvaRemoting
{
    public class Interceptor : IMessageSink
    {
        IMessageSink _next=null;
        public Interceptor(IMessageSink NextOne)
        {
            Console.WriteLine("Interceptor::ctor called");
            _next = NextOne;
        }
        // This is the real job
        public IMessage SyncProcessMessage(IMessage TheMessage)
        {
            Console.WriteLine("Interceptor::SyncProcessMessage called");
            DateTime StartTime = DateTime.Now;

            DumpMethod(TheMessage);
            CheckArgs(TheMessage);
            IMessage ReturnMessage = _next.SyncProcessMessage(TheMessage);
            //---- Log excution data
            DateTime StopTime = DateTime.Now;
            TimeSpan TotalTime = StopTime-StartTime;
            Console.WriteLine("  Intercepted: Execution Time {0}", TotalTime.ToString());

            return ReturnMessage;
        }
        public IMessageSink NextSink
        {
            get {
                Console.WriteLine("Interceptor::NextSink called");
                return _next ;
                }
        }
        public IMessageCtrl AsyncProcessMessage(IMessage TheMessage, IMessageSink TheSink)
        {
            Console.WriteLine("Interceptor::AsyncProcessMessage called");
            return null;
        }
        void DumpMethod( IMessage TheMessage)
        {
            IMethodMessage Mth =  TheMessage as IMethodMessage;
            if(null==Mth)
                return ;
            Console.WriteLine( "-->Called: {0}", Mth.MethodName );
            int nMax = Mth.ArgCount ;
            for( int i = 0 ; i < nMax ; i++ )
            {
                object o = Mth.GetArg(i);
                if(null==o)
                    o = new Boolean();
                Console.WriteLine("---->Param {0} \"{1}\" {2}", i, Mth.GetArgName(i), o.ToString() );
            }
        }
        void CheckArgs(IMessage TheMessage)
        {
            IMethodMessage Mth =  TheMessage as IMethodMessage;
            if(null==Mth)
                return ;
            if( Mth.MethodName != "Metodo2" )
                return ;
            Console.WriteLine( " *** check engaged");
            if( Mth.ArgCount>= 1)
            {
                object o = Mth.GetArg(0);
                if(null==o)
                {
                    Console.WriteLine( "#####Check ERROR NULL ARGUMENT");
                }
            }
        }
    }
}

Poi ci serve la classe che, chiamata dal CLR, crea una istanza della classe precedente e la inserisce nella catena di processing dei messaggi. Deve implementare le interfacce IContextProperty, IContributeServerContextSink

using System;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;

namespace ProvaRemoting
{
    // its purpose is to inject a sink
    public class InterceptorInjector : IContextProperty, IContributeServerContextSink
    {
        public InterceptorInjector()
        {
            Console.WriteLine("InterceptorInjector::ctor called");
        }
        public bool IsNewContextOK(Context TheContext)
        {
            Console.WriteLine("InterceptorInjector::IsNewContextOK called");
            return true;
        }
        public void Freeze(Context TheContext)
        {
            Console.WriteLine("InterceptorInjector::Freeze called");
        }
        public IMessageSink GetServerContextSink(IMessageSink NextSink)
        {
            Console.WriteLine("InterceptorInjector::GetServerContextSink called");
            return new Interceptor(NextSink);
        }
        public String Name { get {
            Console.WriteLine("InterceptorInjector::Name called");
            return "It's me";}}
    }
}

In seguito creiamo la classe che servirà da attributo e che metterà in moto il meccanismo

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Contexts;

namespace ProvaRemoting
{
   [AttributeUsage(AttributeTargets.Class)]
   public class ClassAttributeMarker : Attribute, IContextAttribute
   {
       public ClassAttributeMarker()
       {
          Console.WriteLine("ClassAttributeMarker::ctor called");
       }
       public bool IsContextOK(Context TheContext, IConstructionCallMessage Message)
       {
          Console.WriteLine("ClassAttributeMarker::IsContextOK called");
          // This will force the interceptor creation 
          return false;
       }

       public void GetPropertiesForNewContext(IConstructionCallMessage Message)
       {
          Console.WriteLine("ClassAttributeMarker::GetPropertiesForNewContext called");
          Message.ContextProperties.Add(new InterceptorInjector());
       }
    }
}


 

Infine la classe target derivante da ContextBoundObject e marcata con l’attributo visto in precedenza

using System;
using System.Runtime.Remoting;

namespace ProvaRemoting
{
        [ClassAttributeMarker]
        public class TargetClass: ContextBoundObject
        {
                public TargetClass()
                {
                        Console.WriteLine("TargetClass::ctor called");
                }
                public int Metodo1(int Par1, int Par2)
                {
                        Console.WriteLine("TargetClass::Metodo1 called");               
                        return Par1+Par2;
                }
                public void Metodo2(string Par1)
                {
                        Console.WriteLine("TargetClass::Metodo2 called");
                        if(null!=Par1)
                                Par1 += "AAAAAAAAAAA";
                }
        }
}

Finalmente siamo arrivati al main, dove instanziamo la nostra classe target e la utilizziamo

using System;

namespace ProvaRemoting
{
  class MainClass
  {
    public static void Main(string[] args)
    {
      Console.WriteLine("....Start...");
       // Create a new target
       TargetClass MyTarget = new TargetClass();
       // call some method with legal argumentss
       MyTarget.Metodo1(1,2);
       MyTarget.Metodo2("ABC");
       MyTarget.Metodo2(null);
       // done.
       Console.WriteLine("....End...");
    }
  }
}

finalmente!

Lascia un commento »

Non c'è ancora nessun commento.

RSS feed for comments on this post. TrackBack URI

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

Crea un sito o un blog gratuitamente presso WordPress.com.

%d blogger cliccano Mi Piace per questo: