您的位置:首页 > 编程语言 > ASP

转载 Aspect Oriented Programming

2005-09-12 20:00 225 查看
ntroduction
Microsoft introduced Declarative
Programming Style with release of MTS and COM+. MTS and COM+ provided
many configurable services. E.g. “Transactional” attributes,
Synchronization attributes, etc. The best thing about all these
services is that it requires minimum efforts on developer side. All the
services are configurable using the attributes. But we could never
really add to set of attributes that MTS or COM+ provided. To some
extent it helped us to visualize the capabilities of the future
application frameworks.

In this article we are going to look at
some features of .Net framework, which enable us to build highly
configurable applications. We can build application frameworks, which
help the developer to concentrate on developing the core business
related functionality. Developers can invoke the services just by
configuring/declaring few attributes. Developer can be ignorant of
underlying framework plumbing.

But before we go to these features we need to understand concept called as Aspect Oriented Programming.

What is AOP?

Reusability is not new to us and we all make conscious efforts to achieve high degree of reusability.

Lets
look at the way we use the reusable components. Lets take a scenario
where we want to create a trace of an application execution. We want to
log the name of each and every method that is executed and some other
statistics such as how much time it took to execute the method.

So
we write a simple logger class. We may provide features to log the
details in a file or database table. We provide some methods to log
details such as “BeginLog”, “EndLog”. Our program calls these methods.
Each method that we want to execute, calls a method such as “BeginLog”.
To take a step further, we may use the magic of reflection to know
which method has called the component method. At the end of the method
we call the “EndLog” method to end the trace for the same method.

So
even after having reusable component, developer needs to do lot of
coding. But how about marking the method with just an attribute/aspect
such as “LogEnable” and rest of the things are taken care by the
underlying components? Well if you like this concept, then Welcome to
Aspect Oriented Programming.

Aspect-oriented Programming (AOP) was invented at Xerox PARC in the 1990s.

In
Aspect oriented programming, we provide the Attribute/Aspects to the
class at various levels such as class/method/ property, etc. We can
treat this aspect as metadata about the class. The run time can
discover the metadata and provide the services required by the
component.

How AOP works?

Looking at COM+ objects, we can think
of attributes, such as Transaction, Security, Synchronization
attributes, as aspects. The COM+ runtime provides stacks of objects,
called as interceptors. These interceptors get in between the client
and server and provide services such as Security, Transaction,
Synchronization, etc.

The AOP framework works in the similar way. To design AOP framework we need the ability to

Create Runtime that binds object and client together
Describe the Metadata of the object. This metadata will provide the Aspect for the object.
AOP
framework works solely depends on the interception and delegation. AOP
framework performs interception and delegation at two levels – Object
activation and Method Invocation.

At the time of object
activation, run time uses the metadata (Aspects/Attributes) of the
object to compose the stack of interceptors. The metadata helps to
identify the services requested by the object. The interceptors are
responsible to provide requested service. The runtime returns the
reference to the interceptor instead of reference of the object, to the
client. When client calls methods on the object, the interceptors
intercept the call to do some pre processing. All the interceptors are
chained to each other. The last interceptor in the chain directs the
call to object. When the call is finished, all the interceptors get
chance to post-process the method call.

The set of aspects
together form the context for the class. The context helps to decide
the right environment for the method execution. We will see in more
details about contexts later.

AOP allows for better separation of tasks, which are commonly used.

The
AOP approach has a number of benefits. First, it improves performance
because the operations are more succinct. Second, it allows programmers
to spend less time rewriting the same code. Overall, AOP enables better
encapsulation of distinct procedures and promotes future interoperation.

AOP and .Net

One of the reasons why we can build
the AOP oriented components in .Net is that it allows us to extend the
.Net framework. .Net framework allows us provide the metadata for each
component. To provide the custom attribute we need to develop custom
attributes.

As you can see, the attribute class itself has some
attributes. The AttributeTarget attribute will control the application
of the attribute to class/method/property and so on.

[AttributeUsage(AttributeTargets.Class)]
public class ContextAtrbAttribute :Attribute
{
private string _fileName;
public ContextAtrbAttribute(string fileName)
{
this._fileName = fileName;
}
}

Now we can specify the metadata for the component using these attributes.

[ContextAtrb(@"c:\ContextClass.txt")]
public class ContextClass: ContextBoundObject
{

}

Contexts in .Net

Now we have to look at the concept called as Contexts in .Net.

Context
is a logical grouping of the objects that have same aspect values. With
help of contexts we establish interceptors to intercept the calls on
the objects.

Two objects with different contexts communicate to
each other using .Net context architecture. Though we do not want to go
into the details of context architecture we need to understand its
basics.

Basics of Context architecture

Two objects with different contexts
communicate to each other using the messages. These messages are passed
thro’ several message sinks.

These message sinks can be custom
built. These sinks are chained to each other and these sinks pass the
message to next sink till they reach the end of the chain. We can build
the custom sinks and introduce them in the chain. This gives us the
opportunity to intercept the message.

There are two processes that take place.

Firstly
when we create a context bound object, the run time sets up the chain
of message sinks. This chain is used when two objects from different
contexts communicate to each other.

Secondly, when two objects
in different contexts communicate to each other, the runtime forms
proxy object between the caller and callee. The proxy is of two types –
Transparent proxy and Real Proxy. The runtime forwards the message to
the transparent proxy. This proxy serializes the stack in to message
and passes it to the real proxy. The real proxy takes the message and
sends it to first message sink in the channel. The first message sink
intercepts the call. Message sink can preprocess the message. This
message sink then passes the message to the next message sink. This
process continues till the last sink i.e. Stack builder sink is
reached. This sink de-serializes the message into the stack and calls
on the method of the object. After the method call, the same sink again
serializes the parameter values and return value into the stack and
returns it to the previous sink. Each sink in the chain gets its turn
to post process the message.

The sequence of flow is illustrated in Figure 1.

The messages are of 3 types – Construction messages, Method call message, and Response message.

From
the above discussion its very clear that to intercept the calls on the
object we need to build our custom sinks and link in the chain of sink
at proper position.

Prerequisites

With all this we need to know what all classes we need to create to build our custom interceptors.

We need to use following namespaces –

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

First of all to make our class aware
of “context” our class should inherit from “ContextBoundObject” .Net
runtime will automatically create a separate context for it to live in
whenever we create object of that class. .Net runtime establishes the
proxies to participate in the calls across the contexts.

Secondly
we need to build the ASPECTS. We need the attributes that we can apply
to the class. So we have to create a class that implements
IContextAttribute interface. It should also inherit from Attribute
framework class. This class will define the attributes or the aspects
that we want to apply to the class.

Next we want to define
Context Properties. Based on the context properties, runtime will
differentiate between two contexts. To create context properties, we
need to implement “IContextProperty” interface and
“IContributeObjectSink" interface.

We are only half done with all these classes. We will have classes required to define the contexts.

After
this we need to define our own Message Sink. To do that we need to
create a class that implements the “IMessageSink” interface.

Lets take look at all these steps in detail.

Implementation Detail

First we need to derive our class from ContextBoundObjects.

[ContextAtrb(@"c:\ContextClass.txt")]
public class ContextClass: ContextBoundObject
{
}

In the above example, the
“ContextAtrb” attribute is the aspect provided for the class. This is
called custom attribute. We have seen how to develop this custom
attribute.

IContextAttribute interface

For this CustomAttribute to
participate in this process of interception it should implement
IContextAttribute interface. This interface has two methods

bool IsContextOK(Context ctx,IConstructionCallMessage msg)

void GetPropertiesForNewContext( IConstructionCallMessage msg);

These two methods are called by the interceptors/proxies for each attribute of the class.

The method IsContextOk provides us
with opportunity to decide if the object being created should have the
same context or not. The parameter ctx is the context of the caller. If
we return the “true” value the object being created will posses the
same context as that of the caller. The other parameter “msg” will be
the message. This message can be Constructor message only as this
method is called only at the time of the construction of the class.

GetPropertiesForNewContext

This method is called only when the
IsContextOk method returns false. This method gives us opportunity to
create the property for the context.

These properties can be
implemented in a separate class. This class should be derived from two
interfaces - IContextProperty, IContributeObjectSink. The context
property allows us to link our own message sink in the chain of
existing message sink.

As we discussed earlier, the
interception mechanism works in two steps. The first step is to setup
the required message sinks. The second step involves the actual
interception.

This property class that implement IContributeObjectSink interface helps us to setup the required message sinks.

IContextProperty interface

IContextProperty has three methods.

string Name {get;}

public bool IsNewContextOK(Context ctx)

void Freeze (Context ctx)

Name

This
is a read only property and returns the name of the property. The
context class provided by .Net will add this property in its collection
using this name. We can access this property using the method
GetProperty of the context class.

IsNewContextOK

If
this method returns true, then only the activation of the object will
continue. If it returns false, the exception will be raised. This
method is called after the context properties are created. Hence it
gives the opportunity to check if the properties created are right for
new context.

IContributeObjectSink interface

IcontributeObjectSink interface is
most important interface. This interface will allow us to hook our own
message sinks in the chain of the message sinks. We need to implement
one method i.e.

IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink m_Next)

GetObjectSink

This method creates the message sink and returns it.

IMessageSink interface

Now the only part left is to create the message sink. To do so we need to write a class deriving from interface IMessageSink

This interface has methods

IMessageSink NextSink

IMessage SyncProcessMessage(IMessage imCall)

IMessageCtrl AsyncProcessMessage(IMessage im, IMessageSink ims)

In addition to all these methods we also have to implement the constructor.

Constructor

The constructor of the class should
have a parameter of type IMessageSink. This parameter is the next
message sink in the chain. The framework passes the next message sink
in the chain to the class. This message sink should be returned in the
method NextSink. The class has the freedom to change the message sink
and return some other message sink as well. This next message sink will
be linked as the next message sink in the chain.

SyncProcess

This method is called for each method call on the object. im parameter represents the messages of type Method call.

The
sink gets created when the GetObjectSink method for context property is
called. And the methods of this interface are called when the methods
on the object are executed.

Implementation for all these interfaces is available in Listing 1.

Applications of AOP

This style of programming has found
many applications. To start with the example of logging that we have
seen in the beginning can be over simplified with this approach. We
have to develop classes such as LogginAttribute, LoggingFileProperty
and LogginSink. The LoggingSink class will intercept the call and do
the logging. We can mark each class and its methods with the
LogginAttribute. At the time of interception, the SyncProcessMessage
method will find out the current method being called. It will check if
the method has attribute “LoggingAttribute” and accordingly log the
method details such as parameters and its values etc. Thus developers
will just have to mark the methods with attribute and rest will be
taken care by the framework.

The same applies for role-based security, declarative transactions and so on. The list is never ending.

Author – Raviraj Bhalerao

Raviraj
is working in IT fields for last 6 years. He has worked on Microsoft
platforms and specialized in VB6, ASP, SQL Server. Currently he is
working in .Net.

Appendix

Figure 1



Listing 1

using System;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.IO;
using System.Threading;
using System.Reflection;

namespace ContextBoundObjects
{
/// <summary>
/// We want to provide tracing functionality to all methods in this class.
/// </summary>
[ContextAtrb(@"c:\ContextClass.txt")] //Mark the class with Aspect.
public class ContextClass: ContextBoundObject //Inherit from ContextBoundObject.
{
private int Id = 100;
public void DoWork()
{
try
{

Utils.printInfo(MethodBase.GetCurrentMethod());

Console.WriteLine("Writing context - {0}", MyContextUtil.currentContextProp.Name);
}
catch (Exception exc)
{

Console.WriteLine(exc.ToString());
}
finally
{
}
}
//To study the interception in case of property
public int myId
{
get
{

Utils.printInfo(MethodBase.GetCurrentMethod());
return Id;
}
}
}

/// <summary>
/// Define the ASPECT class. This is the custom atrribute.
/// </summary>

[AttributeUsage(AttributeTargets.Class)]
public class ContextAtrbAttribute :Attribute,IContextAttribute
{
private string _fileName;
public ContextAtrbAttribute(string fileName)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
this._fileName = fileName;
}
// This method is called at the time of object activation.
public bool IsContextOK(Context ctx, IConstructionCallMessage msg)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
//ctx is the context of the caller. If the context "ContextProp" is already defined
//we do not want to create the context. Else we will create the new context.
if(ctx.GetProperty("ContextProp") == null)

return(false);
return(true);
}
//This method is called only if IsContextProp returns true. This method is called
//at the time of object activation.
public void GetPropertiesForNewContext(IConstructionCallMessage msg)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
//msg parameter is of type Constructor.
//We create the properties of the context.
msg.ContextProperties.Add(new ContextProp(_fileName));
}
}

/// <summary>
/// This class defines the message sink. Sink is created at the time
/// of object activation.
/// </summary>
public class mySink:IMessageSink
{
private IMessageSink m_Next;

/// <summary>
/// Runtime passes the ims as the next sink.
/// </summary>
internal mySink(IMessageSink ims)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
m_Next
= ims;
}
/// <summary>
/// Returns the next sink in the chain.
/// </summary>
public IMessageSink NextSink
{
get
{

Utils.printInfo(MethodBase.GetCurrentMethod());

//Here you can add new message sinks to the chain.

return m_Next;
}
}
/// <summary>
/// Important method for interception. Every method/ property that is called
/// on the object of "ContextClass" class, sink intercepts the method.The call
/// is passed as parameter in the form of imCall.
/// </summary>
public IMessage SyncProcessMessage(IMessage imCall)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
IMessage retMsg;

// Perform pre processing on the message

//Trace the calls on the object
string[] keyArray = new string[imCall.Properties.Count];
string[] valueArray = new string[imCall.Properties.Count];

ContextProp objContextProp;
objContextProp = MyContextUtil.currentContextProp; objContextProp.LogMessage(imCall.Properties["__MethodName"].ToString());

retMsg = m_Next.SyncProcessMessage(imCall); //Calls actual method - DoWork. imCall contains info about the method.
// Perform post processing on the message
return retMsg;
}
public IMessageCtrl AsyncProcessMessage(IMessage im, IMessageSink ims)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
return m_Next.AsyncProcessMessage(im, ims);
}
}

public class ContextProp:IContextProperty, IContributeObjectSink
{
private string _logFileName;
internal static string PropName
{
get
{

Utils.printInfo(MethodBase.GetCurrentMethod());

return("ContextProp");
}
}
public ContextProp(string logFileName)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
_logFileName = logFileName;
}
/// <summary>
/// This property is used to get the context property name.
/// </summary>

public string Name
{
get
{

Utils.printInfo(MethodBase.GetCurrentMethod());

return(PropName);
}
}
public void Freeze (Context ctx)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
}

/// <summary>
/// This method gives us opportunity to check if the new context being created
/// if right one.
/// </summary>
public bool IsNewContextOK(Context ctx)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
//If you return False, then runtime will raise the System.Runtime.Remoting.RemotingException to the client.
return(true);
}
//Custom interface of the property.
public void LogMessage(string msg)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
StreamWriter logFile;
logFile = File.AppendText(_logFileName);
logFile.WriteLine(msg);
logFile.Flush();
logFile.Close();
}

/// <summary>
/// This method allows to create the message sinnk relevant to the context properties.
/// Different context properties can return the different message sinks. This way we
/// can build the stack of the relevant message sinks.
/// </summary>

public IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink m_Next)
{
Utils.printInfo(MethodBase.GetCurrentMethod());
//Here we create the message sink and return.
return new mySink(m_Next);
}
}

/// <summary>
/// This class is used to get the current context of the thread.
/// </summary>

public class MyContextUtil
{
public static ContextProp currentContextProp
{
get
{

return Thread.CurrentContext.GetProperty(ContextProp.PropName) as ContextProp;
}
}
}

/// <summary>
/// This class provides the static method. This method is used to trace the sequence
/// of events in the interception process.
/// </summary>

public class Utils
{
public static void printInfo(MethodBase mBase)
{

Console.WriteLine("Class - {2} Method - {0} type - {1}",mBase.Name,mBase.MemberType.ToString(),mBase.ReflectedType.ToString());

}
}
}

原文地址: http://www.c-sharpcorner.com/Code/2002/Nov/aop.asp
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: