.NET 4 实践 - 使用dynamic和MEF实现轻量级的AOP组件 (2)
偷梁换柱
上一篇我们初试了DynamicAspect这把小刀,如果你已经下了源代码,你可以看看它在后台究竟做了什么手脚。如果你接触过一些动态的AOP组件,你也许已经发现大部分的实现都是在运行时通过反射机制重新创建一个代理对象或者装饰对象和适配器对象,其目的只有一个,把对目标方法的掉用转移到一个新建的方法上,所以DynamicAspect也不例外,那既然这样,你会问为什么还要重新发明车轮,其理由是目前动态编织的Aspect都有一个软肋,要么被编织的类不能是sealed的,也就是可以从它派生一个新的子类,要么需要为这个类定义一个接口,原因无非就是在开发代码的时候,类似下面的代码可以通过编译:
Bank bank = Factory.Create<Bank>();
bank.Deposit(100);
或者:
IBank bank = Factory.Create<Bank>();
bank.Deposit(100);
感谢dynamic,在.NET 4中这个障碍突然之间说消失就消失了。第二个问题是取消这个限制真的很重要吗?很难回答,但个人认为如果任何实体都可以被编织(静态类除外)总是要比受这个限制那个限制要爽多多。其实,自从.NET4发布以来,我一直在看是否有人利用它的动态特性实现一个AOP,我没有看到,想来肯定是有人想到了,但还没有去做或者正在做也未必。
基于笔者多年对AOP的关注,所以决定尝试着开发一个,于是就有了DynamicAspect现在的原型。做一个动态的Aspect,基本上要解决下面的几个问题:
1、如何使调用对象方法的代码通过编译?
2、如何拦截对目标方法的调用?
3、哪些对象需要被编织?
4、对象的哪些方法被执行的时候需要被拦截?
5、如何定义方法中拦截点(Joinpoint)?
6、Aspect必须具备什么样的行为特征,以及它们如何匹配要编制的目标对象?
7、Aspect对象生命期的管理。
在探讨上面的几个问题之前,我想大致介绍一下dynamic这个大功臣,之所以这么说是因为如果没有dynamic也就没有DynamicAspect,甚至你把DynamicAspect看成是Dynamic技术和MEF技术二者结合的结晶一点也不为过,之所以大致介绍,因为完全的解释dynamic需要很大的篇幅,而且也超出了这个系列的主题范围,大家如果有兴趣可以自己阅读msdn文档相关部分和其他网友写的文章(笔者推荐Using the Dynamic Keyword in C# 4.0)。
近年来,动态语言火热流行,使得开发人员对生在静态语言家族的C#也有了支持动态的需求,C#3.0 引入Lambda 表达式,开始向支持动态迈进了一步,C#4.0引入dynamic又向前迈出了一大步,在这之前,.NET支持动态的表现是通过反射(相信有人使用反射来实现类似COM的迟后绑定),那么到底dynamic是什么?使用dynamic有什么好处?还是让代码来告诉我们吧,请看下面的代码:
dynamic result = DoSomething(1, 2);
result = DoSomething("Hello,", "Dynamic!");
DoSomething的方法如下:
static dynamic DoSomething(dynamic d1, dynamic d2)
{
return d1 + d2;
}
结果第一个result返回:3
第二个result返回:Hello, Dynamic!
从上面的表达式看dynamic是一个CLR类型,所以几乎所有可以使用类型的地方都可以使用dynamic,但你不能通过new dynamic(); 来创建一个dynamic对象实例。也许你觉得dynamic像var关键字,可是var关键字只能出现在局部变量的声明中,其实dynamic更像object,和object不同的是dynamic可以支持任何操作,也就是在编写代码的时候你可以一个dynamic类型对象可以调用任何方法而不会产生编译错误,但这并不意味着dynamic可以逃脱类型检测,在运行时后,它还是要被检测,也就是说它的类型以及所执行的操作在运行的时候必须存在,正所谓逃了和尚逃不了庙!有的朋友可能会说DoSomething方法可以使用泛型,我很认真的告诉你在上面的例子中,泛型无能为力,不信你试试?所以基本上你可以把dynamic看成一个占位对象,该占位对象可以绕过编译器的检查。
至于使用dynamic的好处,就要看在什么场合下使用了,好吃的东西也不宜多吃,否则会适得其反的。下面我们通过dynamic的一个兄弟ExpandoObject类来看dynamic在动态编程中的用法:
dynamic user = new ExpandoObject();
user.Name = "user";
user.Password = "p@ssw0rd";
user.Validate = (Func<string,string,bool>)
(( userid, password) => userid == "user" && password == "p@ssw0rd" );
user.Validate(user.Name, user.Password);
user是ExpandoObject类型的动态对象,类似一个匿名类型,我们在代码中动态定义了两个属性Name和Password,并对它们赋值,然后我们为user对象动态定义了一个Validate方法,最后,我们调用这个定义的方法,读出前面定义的Name和Password的属性值作为方法的参数。
如果你需要的功能ExpandoObject不能满足的话,这时有两个定制方法可以使用,方法1:实现
IDynamicMetaObjectProvider接口;方法2:从DynamicObject类派生。
我们先来看看从DynamicObject类中重载来的几个方法的定义(其他方法参见MSDN类库文档):
public class DynamicObject : IDynamicMetaObjectProvider
{
//提供对获取成员值操作的实现,重载该方法可以实现获取对象属性值操作时行为。
public virtual bool TryGetMember(GetMemberBinder binder, out object result);
//提供调用成员操作的实现。
public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result);
//提供对设置成员值得实现。
public virtual bool TrySetMember(SetMemberBinder binder, object value);
}
有了上面dynamic的知识,我们的第一个问题得到了解决。在DynamicAspect中,我们设计了一个名为WeavableObject的类,它从DynamicObject派生,通过对TryInvokeMember方法的重载,我们得到拦截方法调用的机会;而通过对TryGetMember和TrySetMember方法的重载,于是就可以拦截对属性的get和set的调用。WeavableObject的部分代码如下所示:
public sealed class WeavableObject<T> : DynamicObject,
IPartImportsSatisfiedNotification
where T: class
{
T source;
public WeavableObject()
{
source = Activator.CreateInstance<T>();
}
public WeavableObject(T source)
{
this.source = source;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
// 代码暂略
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// 代码暂略
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// 代码暂略
}
我们的第二个问题也就解决了。
当我们决定一个对象要被编织的时候,我们就可以在实例化该对象的是时候把它替换成WeavableObject,比如上一篇的Bank对象,我们就可以把代码写成:
dynamic bank = new WeavableObject<Bank>();
WeavableObject的无参数构造器将根据对象的类型通过反射来创建一个对象实例。为了减少反射带来的性能开销,我们也设计了一个带参构造器,所以上面的代码就可以写成:
dynamic bank = new WeavalbeObject<Bank>(new Bank());
为了使代码看上去更自然,我们编写了一个扩展方法AsDynamic<T>(),于是最终的代码就变成了:
dynamic bank = new Bank().AsDynamic();
自此,梁柱就这样被换掉了。下一篇将继续探讨其他未解决的问题。
(未完待续)