一、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中,又定义了两个抽象方法,负责添加所有的映射关系: