原文地址:http://www.uml.org.cn/net/201004213.asp

一、AOP实现初步

AOP将软件系统分为两个部分:核心关注点和横切关注点。核心关注点更多的是Domain Logic,关注的是系统核心的业务;而横切关注点虽与核心的业务实现无关,但它却是一种更Common的业务,各个关注点离散地分布于核心业务的多处。这意味着,如果不应用AOP,那么这些横切关注点所代表的业务代码,就会分散在系统各处,导致系统中的每个模块都与这些业务具有很强的依赖性。在这里,所谓横切关注点所代表的业务,即为“方面(Aspect)”,常见的包括权限控制、日志管理、事务处理等等。

以权限控制为例,假设一个电子商务系统,需要对订单管理用户进行权限判定,只有系统用户才能添加、修改和删除订单,那么传统的设计方法是:

public class OrderManager

{

private ArrayList m_Orders;

public OrderManager()

{

m_Orders = new ArrayList();

}

public void AddOrder(Order order)

{

if (permissions.Verify(Permission.ADMIN))

{

m_Orders.Add(order);

}

}

public void RemoveOrder(Order order)

{

if (permissions.Verify(Permission.ADMIN))

{

m_Orders.Remove(order);

}

}

}

这样的设计其缺陷是将订单管理业务与权限管理完全结合在一起,耦合度高。而在一个系统中,类似的权限控制会很多,这些代码就好像一颗颗毒瘤一般蔓延于系统中的各处,一旦需要扩展,则给程序员们带来的困难是不可估量的。

让我们来观察一下订单管理业务中的权限管理。不管是添加订单,还是删除订单,有关权限管理的内容是完全相同的。那么,为什么我们不能将这些相同的业务,抽象为一个对象,并将其从订单管理业务中完全剥离出来呢?在传统的OO设计思想,这种设想是不能实现的。因为订单管理业务作为一个类对象,它封装了诸如添加、删除订单等行为。这种封装性,就决定了我们不可能切入到对象内部,通过获取方法消息的形式,对对象行为进行监控与操作。

AOP的思想解决了这个问题,之所以称为“方面(Aspect)”,就是把这些对象剖开,仅获取其内部相一致的逻辑,并剥离出来,以“方面”的形式存在。要让这些方面能够对核心业务进行控制,就需要有一套获取方法消息的机制。在.Net中,其中一种技术称为动态代理。

在.Net中,要实现动态代理,需要用到.Net Remoting中的消息机制,以及.Net Framework内部提供的ContextAttribute类来自定义自己的Attribute。另外,.Net还要求调用“Aspect”的核心业务类,必须继承ContextBoundObject类。只有这样,我们才能截取其内部传递的方法消息。以下,是相关接口和类的说明。

ContextAttribute类

该类继承了Attribute类,它是一个特殊的Attribute,通过它,可以获得对象需要的合适的执行环境,即Context(上下文)。它还实现了IContextAttribute和IContextProperty接口。我们自定义的Attribute将从ContextAttribute类派生。

构造函数:

ContextAttribute类的构造函数带有一个参数,用来设置ContextAttribute的名称。

公共属性:

Name:只读属性。返回ContextAttribute的名称

公共方法:

GetPropertiesForNewContext:虚拟方法。向新的Context添加属性集合。

IsContextOK虚拟方法。查询客户Context中是否存在指定的属性。

IsNewContextOK虚拟方法。默认返回true。一个对象可能存在多个Context,使用这个方法来检查新的Context中属性是否存在冲突。

Freeze:虚拟方法。该方法用来定位被创建的Context的最后位置。

                         

ContextBoundObject                                                   

这个类的对象通过Attribute来指定它所在的Context,凡是进入该Context的调用都可以被拦截。该类从MarshalByRefObject派生。

                         

IMessage:定义了被传送的消息的实现。一个消息必须实现这个接口。

IMessageSink:定义了消息接收器的接口,一个消息接收器必须实现这个接口。

该接口主要提供了两个方法,分别进行同步和异步操作:

SyncProcessMessage(IMessage msg):接口方法,当消息传递的时候,该方法被调用;

AsyncProcessMessage(IMessage msg, IMessageSink replySink):该方法用于异步处理;

下面是实现权限控制AOP的简单实现,首先我们自定义一个Attribute,它继承了ContextAttribute:

[AttributeUsage(AttributeTargets.Class)]

public class AOPAttribute:ContextAttribute

{

public AOPAttribute()

: base("AOP")

{

}

public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)

{

ctorMsg.ContextProperties.Add(new AOPProperty());

}

}

在GetPropertiesForNewContext()方法中,添加了AOPProperty对象,它是一个上下文环境属性:

public class AOPProperty : IContextProperty, IContributeObjectSink

{

public AOPProperty()

{

}

#region IContributeObjectSink Members

public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)

{

return new AOPSink(nextSink);

}

#endregion

#region IContextProperty Members

public void Freeze(Context newContext)

{

}

public bool IsNewContextOK(Context newCtx)

{

return true;

}

public string Name

{

get { return "AOP"; }

}

#endregion

AOPProperty属性实现了接口IContextProperty,IContributeObjectSink。GetObjectSink()方法为IContributeObjectSink接口的方法,在其实现中,创建了一个IMessageSink对象AOPSink,该对象实现了IMessageSink接口:

public class AOPSink : IMessageSink

{

private IMessageSink m_NextSink;

public AOPSink(IMessageSink nextSink)

{

m_NextSink = nextSink;

}

public IMessageSink NextSink

{

get { return m_NextSink; }

}

public IMessage SyncProcessMessage(IMessage msg)

{

IMethodCallMessage call = msg as IMethodCallMessage;

if (call == null)

{

return null;

}

IMessage retMsg = null;

if (call.MethodName == "AddOrder" || call.MethodName == "DeleteOrder")

{

if (permissions.Verify(Permission.ADMIN))

{

retMsg = m_NextSink.SyncProcessMessage(msg);

}

}

return retMsg;

}

public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)

{

return null;

}

}

在AOPSink中,最重要的是SyncProcessMessage()方法,在这个方法中,实现了权限控制,并通过IMessage,截取了需要权限控制的方法。在检验了权限之后,然后再执行OrderManager的AddOrder和DeleteOrder方法。

通过AOP的实现,原来的OrderManager,就可以修改为:

[AOP]

public class OrderManager: ContextBoundObject

{

private ArrayList m_Orders;

public OrderManager()

{

m_Orders = new ArrayList();

}

public void AddOrder(Order order)

{

m_Orders.Add(order);

}

public void RemoveOrder(Order order)

{

m_Orders.Remove(order);

}

}

在上述的OderManager类中,完全消除了permissions.Verify()等有关权限的代码,解除了订单管理与权限管理之间的耦合。

二、与AspectJ比较                                                   

上述的方案虽然解除了订单管理与权限管理的耦合,但从SyncProcessMessage()方法可以看出,它的实现具有很大的局限性。试想一下这样的应用场景,在订单管理系统中,用户要求对修改订单的方法增加权限验证,同时要求在验证权限时,允许业务经理(Permission.Manager)也具备管理订单的权限,应该怎样做?仔细思考,我们会发觉以上的实现未免太过死板了。

让我们来参考一下AspectJ在java中的实现。AspectJ提供了自己的一套语法,其中包括aspect、pointcut、before、after等。我们可以通过aspect定义一个“方面”,如上的权限管理:

private static aspect AuthorizationAspect{……}

pointcut为切入点,在其中定义了需要截取上下文消息的方法,例如:

private pointcut authorizationExecution():

execution(public void OrderManager.AddOrder(Order)) ||

execution(public void OrderManager.DeleteOrder(Order)) ||

execution(public void OrderManager.UpdateOrder(Order));

由于权限验证是在订单管理方法执行之前完成,因此在before中,定义权限检查:

before(): authorizationExecution()

{

if !(permissions.Verify(Permission.ADMIN))

{

throw new UnauthorizedException();

}

}

从上述AspectJ的实现中,我们可以看到,要定义自己的aspect是非常容易的,而通过pointcut的方式,可以将需要截取消息的方法,集中在一起。before和after则是具体的方面执行的逻辑,它们就好像Decorator模式那样,对原有方法进行了一层装饰,从而达到将aspect代码植入的目的。

另外,AspectJ还提供了更简单的语法,可以简化前面pointcut中一系列方法的列举:

private pointcut authorizationExecution():

execution (public * OrderManager.*(.))

AspectJ在应用AOP领域,已经非常成熟。它提供了自成一体的特有AspectJ语法,并需要专门的java编译器,使用起来较为复杂。那么,在.Net下,可否实现类似AspectJ的功能呢?我想,由于.Net与java在很多技术的相似性,它们彼此之间在很多领域是相通的,因此要达到这一目标应该是可行的。事实上,开源项目中的Aspect#,就与AspectJ相似。

事实上,如果我们利用前面描述的动态代理机制,辅以设计模式的OO设计方法,直接在代码中也可以实现AspectJ中的部分AOP特性。

三、.Net中AOP的深入实现                                                   

我们先分析AspectJ中的pointcut和.Net中的SyncProcessMessage()方法。Pointcut可以添加一系列需要截取上下文的方法,那么在.Net中,我们也可以利用集合,动态地添加方法,并创建这些方法与“方面”的映射。同样的,AspectJ中的before和after,是“方面”的核心实现,那么在.Net中,我们也可以利用委托,使其对应相关的方法,来实现其核心逻辑。

结合动态代理的知识,我们先定义两个委托,分别代表before和after操作:

public delegate void BeforeAOPHandle(IMethodCallMessage callMsg);

public delegate void AfterAOPHandle(IMethodReturnMessage replyMsg);

BeforeAOPHandle中的参数callMsg,其值为要截取上下文的方法的消息;AfterAOPHandle中的参数replyMsg,则是该方法执行后返回的消息。

接下来,定义一个抽象基类AOPSink,它实现了IMessageSink接口:

public abstract class AOPSink : IMessageSink

{

private SortedList m_BeforeHandles;

private SortedList m_AfterHandles;

private IMessageSink m_NextSink;

}

在类AOPSink中,定义了两个SortedList类型的字段:m_BeforeHandles和m_AfterHandles。它们负责存放方法名与BeforeAOPHandle和AfterAOPHandle对象之间的映射。添加这些映射的职责由如下两个方法完成:

protected virtual void AddBeforeAOPHandle(string methodName, BeforeAOPHandle beforeHandle)

{

lock (this.m_BeforeHandles)

{

if (!m_BeforeHandles.Contains(methodName))

{

m_BeforeHandles.Add(methodName, beforeHandle);

}

}

}

protected virtual void AddAfterAOPHandle(string methodName, AfterAOPHandle afterHandle)

{

lock (this.m_AfterHandles)

{

if (!m_AfterHandles.Contains(methodName))

{

m_AfterHandles.Add(methodName, afterHandle);

}

}

}

考虑到我们要截取的方法可能会有多个,因此在类AOPSink中,又定义了两个抽象方法,负责添加所有的映射关系:

protected abstract void AddAllBeforeAOPHandles();

protected abstract void AddAllAfterAOPHandles();

然后在构造函数中,我们初始化两个SortedList对象,并调用上述的两个抽象方法:

public AOPSink(IMessageSink nextSink)

{

m_NextSink = nextSink;

m_BeforeHandles = new SortedList();

m_AfterHandles = new SortedList();

AddAllBeforeAOPHandles();

AddAllAfterAOPHandles();

}

为了能够根据方法名获得相对应的委托对象,我们又定义了两个Find方法。考虑到可能会有多个用户同时调用,在这两个方法中,我利用lock避免了对象的争用:

protected BeforeAOPHandle FindBeforeAOPHandle(string methodName)

{

BeforeAOPHandle beforeHandle;

lock (this.m_BeforeHandles)

{

beforeHandle = (BeforeAOPHandle)m_BeforeHandles[methodName];

}

return beforeHandle;

}

protected AfterAOPHandle FindAfterAOPHandle(string methodName)

{

AfterAOPHandle afterHandle;

lock (this.m_AfterHandles)

{

afterHandle = (AfterAOPHandle)m_AfterHandles[methodName];

}

return afterHandle;

}

接下来是IMessageSink接口要求实现的方法和属性:

public IMessageSink NextSink

{

get { return m_NextSink; }

}

public IMessage SyncProcessMessage(IMessage msg)

{

IMethodCallMessage call = msg as IMethodCallMessage;

string methodName = call.MethodName.ToUpper();

BeforeAOPHandle beforeHandle = FindBeforeAOPHandle(methodName);

if (beforeHandle != null)

{

beforeHandle(call);

}

IMessage retMsg = m_NextSink.SyncProcessMessage(msg);

IMethodReturnMessage replyMsg = retMsg as IMethodReturnMessage;

AfterAOPHandle afterHandle = FindAfterAOPHandle(methodName);

if (afterHandle != null)

{

afterHandle(replyMsg);

}

return retMsg;

}

public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)

{

return null;

}

需要注意的是SyncProcessMessage()方法。在该方法中,通过FindBeforeAOPHandle()和FindAfterAOPHandle()方法,找到BeforeAOPHandle和AfterAOPHandle委托对象,并执行它们。即执行这两个委托对象具体指向的方法,类似与AspectJ中的before和after的execution。

现在,我们就可以象AspectJ那样定义自己的aspect了。如权限管理一例,我们定义一个类AuthorizationAOPSink,它继承了AOPSink:

public class AuthorizationAOPSink : AOPSink

{

public AuthorizationAOPSink(IMessageSink nextSink)

: base(nextSink)

{

}

}

然后在这个方法中,实现before和after的逻辑。注意before和after方法应与之前定义的委托BeforeAOPHandle和AfterAOPHandle一致。不过,以本例而言,并不需要实现after逻辑:

private void Before_Authorization(IMethodCallMessage callMsg)

{

if (callMsg == null)

{

return;

}

if (!permissions.Verify(Permission.ADMIN))

{

throw UnauthorizedException();

}

}

然后我们override基类中的抽象方法AddAllBeforeAOPHandles()和AddAllAfterAOPHandles():

protected override void AddAllBeforeAOPHandles()

{

AddBeforeAOPHandle("ADDORDER", new BeforeAOPHandle(Before_Authorization));

AddBeforeAOPHandle("DELETEORDER", new BeforeAOPHandle(Before_Authorization));

}

protected override void AddAllAfterAOPHandles()

{

}

因为after逻辑不需要实现,因此重写AddAllAfterAOPHandles()时,使其为空就可以了(必须重写,因为该方法为抽象方法)。在AOPProperty类中,需要返回IMessageSink对象,所以还应修改原来的AOPProperty类中的GetObjectSink方法:

public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)

{

return new AOPSink(nextSink);

return new AuthorizationAOPSink(nextSink);

}

比较一下上述的实现方案,自定义的继承AOPSink类的AuthorizationAOPSink就相当于AspectJ中的aspect。而与BeforeAOPHandle和AfterAOPHandle委托对应的方法,则相当于AspectJ的before和after语法。AddAllBeforeAOPHandles()和AddAllAfterAOPHandle()则相当于AspectJ的pointcut。通过引入委托的方法,使得我们的AOP实现,具有了AspectJ的一些特性,而这些实现是不需要专门的编译器的。

很明显,如果我们要求OrderManager类中新增的UpdateOrder方法,也要加入权限控制,那么我们可以在AddAllBeforeAOPHandles()方法中,增加UpdaeOrder方法与before逻辑的映射:

AddBeforeAOPHandle("UPDATEORDER", Before_Authorization);

同样的,如果要对权限控制进行修改,开发业务经理对订单管理的权限,那么也只需要修改Before_Authorization()方法:

private void Before_Authorization(IMessage callMsg)

{

IMethodCallMessage call = callMsg as IMethodCallMessage;

if (call == null)

{

return;

}

if (!(permissions.Verify(Permission.ADMIN)|| permissions.Verify(Permission.MANAGER)))

{

throw UnauthorizedException();

}

}

四、进一步完善                                                   

由于我们的委托列表m_BeforeHandles和m_AfterHandles为SortedList类型,因此作为key的methodName必须是唯一的。如果系统要求添加其他权限控制的逻辑,例如增加认证功能,就不能再在AuthorizationAOPSink类的AddAllBeforeAOPHandles()方法中增加方法名与认证功能的before逻辑之间的映射了。

private void Before_Authentication(IMessage callMsg){……}

protected override void AddAllBeforeAOPHandles()

{

……

AddBeforeAOPHandle("ADDORDER", new BeforeAOPHandle(Before_ Authentication));

AddBeforeAOPHandle("DELETEORDER", new BeforeAOPHandle(Before_ Authentication));

}

如果在AuthorizationAOPSink类中添加上面的代码,由于新增的“ADDORDER”key与前面重复,故执行程序时,是找不到相应的委托Before_Authentication的。

解决的办法就是为认证功能新定义一个aspect。由于在本方案中,实现AOP功能的不仅仅是实现了IMessageSink接口的AOPSink类,同时该类还与Property、Attribute有关。也就是说,如果我们新定义一个AuthenticationAOPSink,那么还要定义与之对应的AuthenticationAOPProperty类。为便于扩展,我采用了Template Method模式,为所有的property定义了抽象类AOPProperty,其中的抽象方法或虚方法,则留待其子类来实现。

public abstract class AOPProperty : IContextProperty, IContributeObjectSink

{

protected abstract IMessageSink CreateSink(IMessageSink nextSink);

protected virtual string GetName()

{

return "AOP";

}

protected virtual void FreezeImpl(Context newContext)

{

return;

}

protected virtual bool CheckNewContext(Context newCtx)

{

return true;

}

#region IContributeObjectSink Members

public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)

{

return CreateSink(nextSink);

}

#endregion

#region IContextProperty Members

public void Freeze(Context newContext)

{

FreezeImpl(newContext);

}

public bool IsNewContextOK(Context newCtx)

{

return CheckNewContext(newCtx);

}

public string Name

{

get { return GetName(); }

}

#endregion

}

与原来的AOPProperty类相比,IContextProperty,IContributeObjectSink接口的方法与属性,都没有直接实现,而是在其内部调用了相关的抽象方法和虚方法。包括:抽象方法CreateSink(),虚方法FreezeImpl(),CheckNewContext()以及GetName()。对于其子类而言,需要override的,主要是抽象方法CreateSink()和GetName()(因为Property的Name必须是唯一的),至于其他虚方法,可以根据需要选择是否override。例如,自定义权限控制的属性类AuthorizationAOPProperty:

public class AuthorizationAOPProperty :AOPProperty

{

protected override IMessageSink CreateSink(IMessageSink nextSink)

{

return new AuthorizationAOPSink(nextSink);

}

protected override string GetName()

{

return "AuthorizationAOP";

}

}

在该类中,我们override了CreateSink()方法,创建了一个AuthorizationAOPSink对象。同时override了虚方法GetName,返回了自己的一个名字“AuthorizationAOP”。

关于Attribute类,观察其方法GetPropertiesForNewContext(),其实现是在IConstructionCallMessage消息的上下文property中添加自定义property。这些property组成了一个链,它是可以静态添加的。鉴于此,我们可以采取两种策略:

1、 所有的aspect都使用同一个Attribute。其实现如下:

[AttributeUsage(AttributeTargets.Class)]

public class AOPAttribute:ContextAttribute

{

public AOPAttribute()

: base("AOP")

{

}

public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)

{

ctorMsg.ContextProperties.Add(new AuthorizationAOPProperty());

ctorMsg.ContextProperties.Add(new AuthenticationAOPProperty());

}

}

在方法GetPropertiesForNewContext()中,添加多个自定义Property。在添加Property时,需要注意添加Property的顺序。

2、 不同的aspect使用不同的Attribute。此时可以为这些Attribute定义一个共同的抽象基类AOPAttribute:

[AttributeUsage(AttributeTargets.Class)]

public abstract class AOPAttribute:ContextAttribute

{

public AOPAttribute()

: base("AOP")

{

}

public sealed override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)

{

ctorMsg.ContextProperties.Add(GetAOPProperty());

}

protected abstract AOPProperty GetAOPProperty();

}

注:我将GetPropertiesForNewContext()方法sealed,目的是不需要其子类在重写该方法。

继承AOPAttribute类的子类只需要重写GetAOPProperty()方法即可。但在为OrderManager类定义Attribute的时候,需注意其顺序。如以下的顺序:

[AuthorizationAOP]

[AuthenticationAOP]

public class OrderManager{}

此时,AuthorizationAOPAttribute在前,AuthenticationAOPAttribute在后。如果以Decorator的角度来看,对被装饰的方法,AuthorizationAOPAttribute在内,AuthenticationAOPAttribute在外。

考虑到aspect的应用,有的方法需要多个aspect,有的则只需要单个aspect,所以,第二个方案更佳。

五、AOP实例                                                   

接下来,我通过一个实例,介绍AOP的具体实现。假定我们要设计一个计算器,它能提供加法和减法功能。我们希望,在计算过程中,能够通过日志记录整个计算过程及其结果,同时需要监测其运算性能。该例中,核心业务是加法和减法,而公共的业务则是日志与监测功能。根据前面对AOP的分析,这两个功能应为我们整个系统需要剥离出来的“方面”。

我们已经拥有了一个AOP实现机制,以及核心的类库,包括AOPSink、AOPProperty、AOPAttribute三个抽象基类。现在,我们分别为日志aspect和监测aspect,定义相应的Sink、Property、Attribute。

首先是日志aspect:

LogAOPSink.cs:

using System;

using System.Runtime.Remoting.Messaging;

using Wayfarer.AOP;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for LogAOPSink.

/// </summary>

public class LogAOPSink:AOPSink

{

public LogAOPSink(IMessageSink nextSink):base(nextSink)

{

}

protected override void AddAllBeforeAOPHandles()

{

AddBeforeAOPHandle("ADD",new BeforeAOPHandle(Before_Log));

AddBeforeAOPHandle("SUBSTRACT",new BeforeAOPHandle(Before_Log));

}

protected override void AddAllAfterAOPHandles()

{

AddAfterAOPHandle("ADD",new AfterAOPHandle(After_Log));

AddAfterAOPHandle("SUBSTRACT",new AfterAOPHandle(After_Log));

}

private void Before_Log(IMethodCallMessage callMsg)

{

if (callMsg == null)

{

return;

}

Console.WriteLine("{0}({1},{2})",callMsg.MethodName,callMsg.GetArg(0),callMsg.GetArg(1));

}

private void After_Log(IMethodReturnMessage replyMsg)

{

if (replyMsg == null)

{

return;

}

Console.WriteLine("Result is {0}",replyMsg.ReturnValue);

}

}

}

LogAOPProperty.cs

using System;

using Wayfarer.AOP;

using System.Runtime.Remoting.Messaging;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for LogAOPProperty.

/// </summary>

public class LogAOPProperty:AOPProperty

{

protected override IMessageSink CreateSink(IMessageSink nextSink)

{

return new LogAOPSink(nextSink);

}

protected override string GetName()

{

return "LogAOP";

}

}

}

LogAOPAttribute.cs:

using System;

using System.Runtime.Remoting.Activation;

using System.Runtime.Remoting.Contexts;

using Wayfarer.AOP;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for LogAOPAttribute.

/// </summary>

[AttributeUsage(AttributeTargets.Class)]

public class LogAOPAttribute:AOPAttribute

{

protected override AOPProperty GetAOPProperty()

{

return new LogAOPProperty();

}

}

}

然后再定义监测aspect:

MonitorAOPSink.cs:

using System;

using System.Runtime.Remoting.Messaging;

using Wayfarer.AOP;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for MonitorAOPSink.

/// </summary>

public class MonitorAOPSink:AOPSink

{

public MonitorAOPSink(IMessageSink nextSink):base(nextSink)

{

}

protected override void AddAllBeforeAOPHandles()

{

AddBeforeAOPHandle("ADD",new BeforeAOPHandle(Before_Monitor));

AddBeforeAOPHandle("SUBSTRACT",new BeforeAOPHandle(Before_Monitor));

}

protected override void AddAllAfterAOPHandles()

{

AddAfterAOPHandle("ADD",new AfterAOPHandle(After_Monitor));

AddAfterAOPHandle("SUBSTRACT",new AfterAOPHandle(After_Monitor));

}

private void Before_Monitor(IMethodCallMessage callMsg)

{

if (callMsg == null)

{

return;

}

Console.WriteLine("Before {0} at {1}",callMsg.MethodName,DateTime.Now);

}

private void After_Monitor(IMethodReturnMessage replyMsg)

{

if (replyMsg == null)

{

return;

}

Console.WriteLine("After {0} at {1}",replyMsg.MethodName,DateTime.Now);

}

}

}

MonitorAOPProperty.cs:

using System;

using Wayfarer.AOP;

using System.Runtime.Remoting.Messaging;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for MonitorAOPProperty.

/// </summary>

public class MonitorAOPProperty:AOPProperty

{

public MonitorAOPProperty()

{

//

// TODO: Add constructor logic here

//

}

protected override IMessageSink CreateSink(IMessageSink nextSink)

{

return new MonitorAOPSink(nextSink);

}

protected override string GetName()

{

return "MonitorAOP";

}

}

}

MonitorAOPAttribute.cs:

using System;

using System.Runtime.Remoting.Activation;

using System.Runtime.Remoting.Contexts;

using Wayfarer.AOP;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for MonitorAOPAttribute.

/// </summary>

[AttributeUsage(AttributeTargets.Class)]

public class MonitorAOPAttribute:AOPAttribute

{

protected override AOPProperty GetAOPProperty()

{

return new MonitorAOPProperty();

}

}

}

注意在这两个方面中,各自的Property的Name必须是唯一的。

现在,可以定义计算器类。

Calculator.cs:

using System;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for Calculator.

/// </summary>

[MonitorAOP]

[LogAOP]

public class Calculator:ContextBoundObject

{

public int Add(int x,int y)

{

return x + y;

}

public int Substract(int x,int y)

{

return x - y;

}

}

}

需要注意的是Calculator类必须继承ContextBoundObject类。

最后,我们写一个控制台程序来执行Calculator:

Program.cs:

using System;

namespace Wayfarer.AOPSample

{

/// <summary>

/// Summary description for Class1.

/// </summary>

class Program

{

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void                            Main                           (string[] args)

{

Calculator cal = new Calculator();

cal.Add(3,5);

cal.Substract(3,5);

Console.ReadLine();

}

}

}

运行结果如下:

六、结论

在.Net平台下采用动态代理技术实现AOP,其原理并不复杂,而.Net Framework也提供了足够的技术来实现它。如果再结合好的设计模式,提供一个基本的AOP框架,将大大地简化开发人员处理“aspect”的工作。当然,本文虽然提供了实现AOP的实例,但其架构的设计还远远不能达到企业级的要求,如在稳定性、可扩展性上还需经过进一步的测试与改善。例如我们可以通过配置文件的形式,来配置方法与方面之间的映射。同时,由于采用了动态代理,在性能上还期待改进。

使用动态代理技术实现AOP,对实现AOP的类有一个限制,就是必须派生于ContextBoundObject类,这对于单继承语言来说,确实是一个比较致命的缺陷。所谓“仁者见仁,智者见智”,这就需要根据项目的情况,做出正确的抉择了。

参考:

1、 JGTM,《A Taste of AOP from Solving Problems with OOP and Design Patterns

2、 NiWalker,《Attribute在.Net编程的应用》

3、板桥里人,《AOP与权限控制实现》

在《在.Net中关于AOP的实现》我通过动态代理的技术,基本上实现了AOP的几个技术要素,包括aspect,advice,pointcut。在文末我提到采用配置文件方式,来获取advice和pointcut之间的映射,从而使得构建aspect具有扩展性。

细细思考这个问题,我发现使用delegate来构建advice,似乎并非一个明智的选择。我在建立映射关系时,是将要拦截的方法名和拦截需要实现的aspect逻辑建立一个对应关系,而该aspect逻辑确实可以通过delegate,使其指向一族方法签名与该委托完全匹配的方法。这使得advice能够抽象化,以便于具体实现的扩展。然而,委托其实现毕竟是面向过程的范畴,虽然在.Net下,delegate本身仍是一个类对象,然而在创建具体的委托实例时,仍然很难通过配置文件和反射技术来获得。

考虑到委托具有的接口抽象的本质,也许采用接口的方式来取代委托更为可行。在之前的实现方案中,我为advice定义了两个委托:

public delegate void BeforeAOPHandle(IMethodCallMessage callMsg);

public delegate void AfterAOPHandle(IMethodReturnMessage replyMsg);

我可以定义两个接口IBeforeAction和IAfterAction,分别与这两个委托相对应:

public interface IBeforeAdvice

{

void BeforeAdvice(IMethodCallMessage callMsg);

}

public interface IAfterAdvice

{

void AfterAdvice(IMethodReturnMessage returnMsg);

}

通过定义的接口,可以将Advice与Aspect分离开来,这也完全符合OO思想中的“责任分离”原则。

(注:为什么要为Advice定义两个接口?这是考虑到有些Aspect只需要提供Before或After两个逻辑之一,如权限控制,就只需要before Action。)

那么当类库使用者,要定义自己的Aspect时,就可以定义具体的Advice类,来实现这两个接口,以及具体的Advice逻辑了。例如,之前提到的日志Aspect:

public class LogAdvice:IAfterAdvice,IBeforeAdvice

{

#region IBeforeAdvice Members

public void BeforeAdvice(IMethodCallMessage callMsg)

{

if (callMsg == null)

{

return;

}

Console.WriteLine("{0}({1},{2})", callMsg.MethodName, callMsg.GetArg(0), callMsg.GetArg(1));

}

#endregion

#region IAfterAdvice Members

public void AfterAdvice(IMethodReturnMessage returnMsg)

{

if (returnMsg == null)

{

return;

}

Console.WriteLine("Result is {0}", returnMsg.ReturnValue);

}

#endregion

}

而在AOPSink类的派生类中,添加方法名与Advice映射关系(此映射关系,我们即可理解为AOP的pointcut)时,就可以添加实现了Advice接口的类对象,如:

public override void AddAllBeforeAdvices()

{

AddBeforeAdvice("ADD",new LogAdvice());

AddBeforeAdvice("SUBSTRACT", new LogAdvice());

}

public override void AddAllAfterAdvices()

{

AddAfterAdvice("ADD",new LogAdvice());

AddAfterAdvice("SUBSTRACT", new LogAdvice());

}

由于LogAdvice类实现了接口IBeforeAdvice和IAfterAdvice,因此诸如new LogAdvice的操作均可以通过反射来创建该实例,如:

IBeforeAdvice beforeAdvice =

(IBeforeAdvice)Activator.CreateInstance("Wayfarer.AOPSample","Wayfarer.AOPSample.LogAdvice").Unwrap();

而CreateInstance()方法的参数值,是完全可以通过配置文件来配置的:

<aop>

<aspect value ="LOG">

<advice type="before" assembly="Wayfarer.AOPSample" class="Wayfarer.AOPSample.LogAdvice">

<pointcut>ADD</pointcut>

<pointcut>SUBSTRACT</pointcut>

</advice>

<advice type="after" assembly="Wayfarer.AOPSample" class="Wayfarer.AOPSample.LogAdvice">

<pointcut>ADD</pointcut>

<pointcut>SUBSTRACT</pointcut>

</advice>

</aspect>

</aop>

这无疑改善了AOP实现的扩展性。

《在.Net中关于AOP的实现》实现AOP的方案,要求包含被拦截方法的类必须继承ContextBoundObject。这是一个比较大的限制。不仅如此,ContextBoundObject对程序的性能也有极大的影响。我们可以做一个小测试。定义两个类,其中一个类继承ContextBoundObject。它们都实现了一个累加的操作:

class NormalObject

{

public void Sum(int n)

{

int sum = 0;

for (int i = 1; i <= n; i++)

{

sum += i;

}

Console.WriteLine("The result is {0}",sum);

Thread.Sleep(10);

}

}

class MarshalObject:ContextBoundObject

{

public void Sum(int n)

{

int sum = 0;

for (int i = 1; i <= n; i++)

{

sum += i;

}

Console.WriteLine("The result is {0}", sum);

Thread.Sleep(10);

}

}

然后执行这两个类的Sum()方法,测试其性能:

class Program

{

static void                            Main                           (string[] args)

{

long normalObjMs, marshalObjMs;

Stopwatch watch = new Stopwatch();

NormalObject no = new NormalObject();

MarshalObject mo = new MarshalObject();

watch.Start();

no.Sum(1000000);

watch.Stop();

normalObjMs = watch.ElapsedMilliseconds;

watch.Reset();

watch.Start();

mo.Sum(1000000);

watch.Stop();

marshalObjMs = watch.ElapsedMilliseconds;

watch.Reset();

Console.WriteLine("The normal object consume {0} milliseconds.",normalObjMs);

Console.WriteLine("The contextbound object consume {0} milliseconds.",marshalObjMs);

Console.ReadLine();

}

}

得到的结果如下:

从性能的差异看,两者之间的差距是比较大的。如果将其应用在企业级的复杂逻辑上,这种区别就非常明显了,对系统带来的影响也是非常巨大的。

另外,在《在.Net中关于AOP的实现》文章后,有朋友发表了很多中肯的意见。其中有人提到了AOPAttribute继承ContextAttribute的问题。评论中提及微软在以后的版本中,不再提供ContextAttribute。如果真是如此,确有必要放弃继承ContextAttribute的形式。不过,在.Net中,除了ContextAttribute之外,还提供有一个接口IContextAttribute,该接口的定义为:

public interface IContextAttribute

{

void GetPropertiesForNewContext(IConstructionCallMessage msg);

bool IsContextOK(Context ctx, IConstructionCallMessage msg);

}

此时只需要将原来的AOPAttribute实现该接口即可:

public abstract class AOPAttribute:Attribute,IContextAttribute//ContextAttribute

{

#region IContextAttribute Members

public void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)

{

AOPProperty property = GetAOPProperty();

property.AspectXml = m_AspectXml;

property.AspectXmlFlag = m_AspectXmlFlag;

ctorMsg.ContextProperties.Add(property);

}

public bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)

{

return false;

}

#endregion

}

不知道,IContextAttribute似乎也会在未来的版本中被取消呢?

然而,从总体来看,这种使用ContextBoundObject的方式是不太理想的,也许它只能停留在实验室阶段,或许期待微软在未来的版本中得到更好的解决!?

当然,如果采用Castle的DynamicProxy技术,可以突破必须继承CotextBoundObject的局限,但随着而来的局限却是AOP拦截的方法,要求必须是virtual的。坦白说,这样的限制,不过与前者乃“五十步笑百步”的区别而已。我还是期待有更好的解决方案。

说到AOP的几大要素,在这里可以补充说说,它主要包括:

1、Cross-cutting concern

  在OO模型中,虽然大部份的类只有单一的、特定的功能,但它们通常会与其他类有着共同的第二需求。例如,当线程进入或离开某个方法时,我们可能既要在数据访问层的类中记录日志,又要在UI层的类中记录日志。虽然每个类的基本功能极然不同,但用来满足第二需求的代码却基本相同。

2、Advice

  它是指想要应用到现有模型的附加代码。例如在《在.Net中关于AOP的实现》的例子中,是指关于打印日志的逻辑代码。

3、Point-cut

  这个术语是指应用程序中的一个执行点,在这个执行点上需要采用前面的cross-cutting concern。如例子中,执行Add()方法时出现一个Point-cut,当方法执行完毕,离开方法时又出现另一个Point-cut。

4、Aspect

Point-cut和advice结合在一起就叫做aspect。如例子中的Log和Monitor。在对本例的重构中,我已经AOPSink更名为Aspect,相应的LogAOPSink、MonitorAOPSink也更名为LogAspect,MonitorAspect。

以上提到的PointCut和Advice在AOP技术中,通常称为动态横切技术。与之相对应的,是较少被提及的静态横切。它与动态横切的区别在于它并不修改一个给定对象的执行行为,相反,它允许通过引入附加的方法属性和字段来修改对象固有的结构。在很多AOP实现中,将静态横切称为introduce或者mixin。

在开发应用系统时,如果需要在不修改原有代码的前提下,引入第三方产品和API库,静态横切技术是有很大的用武之地的。从这一点来看,它有点类似于设计模式中提到的Adapter模式需要达到的目标。不过,看起来静态横切技术应比Adapter模式更加灵活和功能强大。

例如,一个已经实现了收发邮件的类Mail。然而它并没有实现地址验证的功能。现在第三方提供了验证功能的接口IValidatable:

public interface IValidatable

{

bool ValidateAddress();

}

如果没有AOP,采用设计模式的方式,在不改变Mail类的前提下,可以通过Adapter模式,引入MailAdater,继承Mail类,同时实现IValidatable接口。采用introduce技术,却更容易实现该功能的扩展,我们只需要定义aspect:(注:java代码,使用了AspectJ)

import com.acme.validate.Validatable;

public aspect EmailValidateAspect

{

declare parents: Email implements IValidatable;

public boolean Email.validateAddress(){

if(this.getToAddress() != null){

return true;

}else{

return false;

}

}

}

从上可以看到,通过EmailValidateAspect方面,为Email类introduce了新的方法ValidateAddress()。非常容易的就完成了Email的扩展。

我们可以比较一下,如果采用Adapter模式,原有的Email类是不能被显示转换为IValidatable接口的,也即是说如下的代码是不可行的:

Email mail = new Email();

IValidatable validate = ((IValidatable)mail).ValidateAddress();

要调用ValidateAddress()方法,必须通过EmailAdapter类。然而通过静态横切技术,上面的代码就完全可行了。

静态横切的技术在企业应用上还需要进一步验证和测试,不过遗憾的是,《在.Net中关于AOP的实现》一文采用的动态代理技术,是无法完成实现静态横切的目标的。

【转】在.Net中关于AOP的实现的更多相关文章

  1. .Net中的AOP系列之构建一个汽车租赁应用

    返回<.Net中的AOP>系列学习总目录 本篇目录 开始一个新项目 没有AOP的生活 变更的代价 使用AOP重构 本系列的源码本人已托管于Coding上:点击查看. 本系列的实验环境:VS ...

  2. .Net中的AOP读书笔记系列之AOP介绍

    返回<.Net中的AOP>系列学习总目录 本篇目录 AOP是什么? Hello,World! 小结 本系列的源码本人已托管于Coding上:点击查看,想要注册Coding的可以点击该连接注 ...

  3. .Net中的AOP系列之《单元测试切面》

    返回<.Net中的AOP>系列学习总目录 本篇目录 使用NUnit编写测试 编写和运行NUnit测试 切面的测试策略 Castle DynamicProxy测试 测试一个拦截器 注入依赖 ...

  4. .Net中的AOP系列之《拦截位置》

    返回<.Net中的AOP>系列学习总目录 本篇目录 位置拦截 .Net中的字段和属性 PostSharp位置拦截 真实案例--懒加载 .Net中的懒加载 使用AOP实现懒加载 如何懒加载字 ...

  5. .Net中的AOP系列之《方法执行前后——边界切面》

    返回<.Net中的AOP>系列学习总目录 本篇目录 边界切面 PostSharp方法边界 方法边界 VS 方法拦截 ASP.NET HttpModule边界 真实案例--检查是否为移动端用 ...

  6. .Net中的AOP系列之《间接调用——拦截方法》

    返回<.Net中的AOP>系列学习总目录 本篇目录 方法拦截 PostSharp方法拦截 Castle DynamicProxy方法拦截 现实案例--数据事务 现实案例--线程 .Net线 ...

  7. 转-Spring Framework中的AOP之around通知

    Spring Framework中的AOP之around通知 http://blog.csdn.net/xiaoliang_xie/article/details/7049183 标签: spring ...

  8. Net中的AOP

    .Net中的AOP系列之<单元测试切面>   返回<.Net中的AOP>系列学习总目录 本篇目录 使用NUnit编写测试 编写和运行NUnit测试 切面的测试策略 Castle ...

  9. Spring中的AOP

    什么是AOP? (以下内容来自百度百科) 面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种 ...

随机推荐

  1. DOS 循环读取txt每一行内容

    在命令行窗口中输入: for /f %i in (f:\mydata.txt) do echo %i 如果要是写成批处理文件run.bat for /f %%i in (f:\mydata.txt) ...

  2. Android Handler leak 分析及解决办法

    In Android, Handler classes should be static or leaks might occur, Messages enqueued on the applicat ...

  3. Update startup files更新安装文件

    The service request did not complete because access to the service configuration manager was not gra ...

  4. ASP.NET之AreaRegistration

    ASP.NETMVC的AreaRegistration是用来干什么呢? 它是用来提供一个很好的接口让我们可以将Controller定义在其他的Library项目中,这主要可以用来解决模块化开发:一般情 ...

  5. sdut 2162:The Android University ACM Team Selection Contest(第二届山东省省赛原题,模拟题)

    The Android University ACM Team Selection Contest Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里 ...

  6. 【bzoj1051】 [HAOI2006]受欢迎的牛 tarjan缩点判出度算点数

    [bzoj1051] [HAOI2006]受欢迎的牛 2014年1月8日7450 Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B ...

  7. SQL2008的数据更新跟踪测试 (监控数据表变化,可用于同步)

    POC过程如下: 这里我们建立一个测试环境,模拟数据在 Insert , Update 和 Delete 情况下的跟踪效果.1 .测试脚本的准备,下面脚本建立一个新的数据库环境,并作相应的跟踪配置后向 ...

  8. JavaScript案例三:动态显示时间

    用JavaScript实现在页面上动态的显示时间 <!DOCTYPE html> <html> <head> <title>JavaScript动态显示 ...

  9. Myeclipse 60.激活

    Myeclipse 用来开发java web应用是十分的方便的,不过如果没有激活的话,用起来感觉会非常不爽. 当然,个人来说还是非常支持正版的,也鼓励大家支持正版. 好了,下面介绍一下怎么破解Myec ...

  10. node连接--MongoDB

    简介: 传统关系类型(ORM:Object-Relational Mapper),MongoDB(ODM:Object Document Mapper); MongoDB是一个面向文档,schme无关 ...