AspectNet功能介绍(二)
AspectNet是一个基于.Net Framework的方面编制器,它同时能实现静态编织和动态编织,是bobmazelin个人的研究性项目,现仍然处于开发阶段,有关AOP概念的介绍请参考:www.aspectJ.org以及IBM的专题.
在上一篇中我介绍了AspectNet的基本结构,从这篇开始我会具体介绍AspectNet在静态编织方面的功能.这次我给大家介绍call和execution两个pointcut.
在上篇中我提过pointcut捕获了需要织入的代码点,在AspectNet中存在着多种捕获这种代码点的方式,call和execution是其中比较基础的两种.
1. call pointcut: 捕获了调用某些方法的代码点;
2. execution pointcut:捕获了执行某些方法的代码点.
它们非常相似,我在下文会通过一个小例子来展示它们之间的区别.
在给出例子之前,我先说明一下它们的方法模式:
修饰符 返回值类型 [方法类型.]方法名称(参数类型列表)
1. 修饰符是public, protected,private,sealed,static等,它们支持!以及并操作(比如: !private static);
2. 返回值类型就是方法的返回值类型,它支持void,*, &&, ||, !,+操作符.void就是没有返回值;*是通配符,单独的*代表任意类型,也可以部分通配,比如:System.*,Customer*.Order*等等;&&, ||, !可以组合不同的类型;+是子类操作符,代表包括其所有子类,比如:System.Object+,就代表了所有的类;
3. 方法类型描述了方法声明了类型,它和返回值类型的模式类似;注:在AspectNet中它不能被忽略;
4. 方法名称只支持*和new操作符,new代表类的构造函数;
5. 参数类型列表:通过,来分割参数类型,它支持..操作符来表示任意多了参数,比如:System.Int,..,string就表述方法的第一个参数和最后一个参数的类型,中间不限制参数的个数和类型.
注:上面对AspectNet的方法模式的描述并不全面.
理论讲完了,来点实际的,由于还没有到参数的传递阶段,我选择了最为简单的log为实例,等到介绍参数时再给出业务相对复杂的例子,这个例子主要说明两个问题,第一: 通过+来捕获子类代码点;第二,call和execution的区别.
首先是需要织入代码的类:
namespace Mazelin.AspectNet.CaseOneProject
{
public abstract class Customer
{
protected IList orders = new ArrayList();
public abstract float GetCustomerOrderPrice();
public void AddOrder(Order order)
{
orders.Add(order);
}
}
}
namespace Mazelin.AspectNet.CaseOneProject
{
public class NormalCustomer : Customer
{
public override float GetCustomerOrderPrice()
{
Console.WriteLine("NormalCustomer");
float discount = 0.9f;
float totalPrice = 0f;
foreach (Order order in this.orders)
{
totalPrice += order.Price * discount;
}
return totalPrice;
}
}
}
namespace Mazelin.AspectNet.CaseOneProject
{
public class VIPCustomer : Customer
{
public override float GetCustomerOrderPrice()
{
Console.WriteLine("VIPCustomer");
float discount = 0.75f;
float totalPrice = 0f;
foreach (Order order in this.orders)
{
totalPrice += order.Price * discount;
}
return totalPrice;
}
}
}
namespace Mazelin.AspectNet.CaseOneProject
{
public class Order
{
private Guid id = Guid.NewGuid();
private float price = 0;
public float Price
{
get { return price; }
set { price = value; }
}
}
}
namespace Mazelin.AspectNet.CaseOneProject
{
public class Main
{
public void Start()
{
Order order1 = new Order();
order1.Price = 100f;
Order order2 = new Order();
order2.Price = 200f;
NormalCustomer normalCustomer = new NormalCustomer();
normalCustomer.AddOrder(order1);
normalCustomer.AddOrder(order2);
normalCustomer.GetCustomerOrderPrice();
VIPCustomer vipCustomer = new VIPCustomer();
vipCustomer.AddOrder(order1);
vipCustomer.AddOrder(order2);
vipCustomer.GetCustomerOrderPrice();
}
}
}
其中Order类没有什么实质的作用,Main类用于构建测试数据.Customer类中的GetCustomerOrderPrice方法就是我们需要关注的代码点.
namespace Mazelin.AspectNet.CaseOneProject
{
public class Log
{
public void LogMessage()
{
Console.WriteLine("call GetCustomerOrderPrice!");
}
public void LogExecutionMessage()
{
Console.WriteLine("execution GetCustomerOrderPrice!");
}
}
}
简单的Log类.
namespace Bob.Mazelin
{
aspect LogAspect
{
pointcut GetCustomerOrderPricePoint():
call(public float Mazelin.AspectNet.CaseOneProject.Customer+.GetCustomerOrderPrice());
pointcut GetCustomerOrderPriceexecutionPoint():
execution(public float Mazelin.AspectNet.CaseOneProject.Customer+.GetCustomerOrderPrice());
before():GetCustomerOrderPricePoint():LogMessage();
before():GetCustomerOrderPriceexecutionPoint():LogExecutionMessage();
storage LogMessage():
call(public void Mazelin.AspectNet.CaseOneProject.Log.LogMessage());
storage LogExecutionMessage():
call(public void Mazelin.AspectNet.CaseOneProject.Log.LogExecutionMessage());
}
}
pointcut GetCustomerOrderPricePoint捕获了调用Mazelin.AspectNet.CaseOneProject.Customer以及其子类的GetCustomerOrderPrice方法;GetCustomerOrderPriceexecutionPoint捕获了执行Mazelin.AspectNet.CaseOneProject.Customer以及其子类的GetCustomerOrderPrice方法.
storage LogMessage和LogExecutionMessage分别捕获了Log代码.两个before用于连接pointcut和storage.
[TestMethod]
public void TestGetCustomerOrderPrice()
{
Main main = new Main();
main.AddAspectAssembly(path + @"Mazelin.AspectNet\Mazelin.AspectNet.CaseOneProject\bin\Debug\Mazelin.AspectNet.CaseOneProject.dll");
main.AddAspectFile(path + @"CaseOneGetCustomerOrderPrice.aspect");
main.AddStorageAssembly(path + @"Mazelin.AspectNet\Mazelin.AspectNet.CaseOneProject\bin\Debug\Mazelin.AspectNet.CaseOneProject.dll");
main.Execute();
Assembly assembly = Assembly.LoadFrom(path + @"Mazelin.AspectNet\Mazelin.AspectNet.TestProject\bin\Debug\Aspect.Mazelin.AspectNet.CaseOneProject" + ".dll");
object obj = assembly.CreateInstance("Mazelin.AspectNet.CaseOneProject.Main");
obj.GetType().GetMethod("Start").Invoke(obj, new object[] { });
obj = assembly.CreateInstance("Mazelin.AspectNet.CaseOneProject.NormalCustomer");
obj.GetType().GetMethod("GetCustomerOrderPrice").Invoke(obj, new object[] { });
}
单元测试代码.
结果为:
call GetCustomerOrderPrice!
execution GetCustomerOrderPrice!
NormalCustomer
call GetCustomerOrderPrice!
execution GetCustomerOrderPrice!
VIPCustomer
execution GetCustomerOrderPrice!
NormalCustomer.
下面我解释一下:
1. 单元测试中:main.Execute(); 用于织入代码;
2. 我为了方便使用反射调用新的DLL,其实不需要,直接引用就可以了.
3. 测试中首先调用Main中的Start方法,通过织入:Start方法中调用(call)NormalCustomer 和VIPCustomer的GetCustomerOrderPrice方法之前都织入了LogMessage方法的调用;注:call的意义就在此,它捕获所有调用方法的代码点,如果没有调用就不会织入;
4. 测试最后单独调用了NormalCustomer的GetCustomerOrderPrice方法是为了证明execution捕获了方法体中的执行点,而不是调用点,就是说无论是否被调用都会织入,也就是这样,call 打印了2次而execution是3次,多的一次是从外部(单元测试中单独调用的).
5. 考虑如果增加Customer的子类,是否需要再添加log的新代码呢?不需要重新编译就可以了.(注:动态织入就不需要编译)
call和execution是AspectNet中常用的pointcut类型,我上面的例子只展示了一小部分功能,不清楚的地方也请大家包涵.
最后还是期待大家的意见和建议,下次我会介绍this和target pointcut.