在框架设计中,调用拦截是非常有用的技术,主要用来实现AOP框架。Unity中提供了拦截功能,该功能可以以扩展的方式加入Unity中,也可以单独使用。EL中的帮助文档非常简陋,完全不适合学习,甚至作为查询文档都不合格。网上对其描述的文章也非常少,而且也都是非常简单的描述。所有这些都让我非常失望,我决定自己读Unity的代码来学习一下如何使用Unity中的Interception功能。
在Microsoft.Practices.Unity.Interception.dll中有一个Intercept静态类,这个类提供了一个很好的学习切入点。下面是这个类两个最主要的方法,其他的几个方法在内部都调用了以下两个方法:
public static class Intercept
{
public static object NewInstanceWithAdditionalInterfaces(
Type type,
ITypeInterceptor interceptor,
IEnumerable<IInterceptionBehavior> interceptionBehaviors,
IEnumerable<Type> additionalInterfaces,
params object[] constructorParameters);
public static objectThroughProxyWithAdditionalInterfaces(
Type interceptedType,
object target,
IInstanceInterceptor interceptor,
IEnumerable<IInterceptionBehavior> interceptionBehaviors, IEnumerable<Type> additionalInterfaces);
}
可以看到这两个方法最主要的区别是interceptor参数类型不同,一个是ITypeInterceptor,一个是IInstanceInterceptor。很容易可以猜出,前者为指定类型添加拦截功能,并且在方法内部创建配置好的类型的实例并返回。后者对已经由客户创建好的对象进行配置以实现拦截功能。
下面描述一下NewInstanceWithAdditionalInterfaces方法的用法:
1. type参数用来指示你要拦截的类型,不是所有的类型都可以被拦截,要知道一个类型是否可被拦截,要通过IInterceptor.CanIntercept(Typet)来判断。
2. 第二个参数interceptor正是实现了IInterceptor接口。所以,第一个参数会用到该参数来验证。
3. 第三个参数interceptionBehaviors,代表一个实现了IInterceptionBehavior接口的对象列表。这正是关键点所在,因为拦截调用的根本目的是为了调用我们自己的逻辑,从而实现AOP。
4. 第四个参数additionalInterfaces,该参数是一个Type对象集合,用来指示返回结果可以转换为该集合中的任何一个类型。不过Type只能为接口类型,否则就等着抛出异常吧。
5. 最后一个参数constructorParameters,将会作为构造函数参数,用以创建被拦截对象,其实就是第一个参数指定的类型的某一个构造函数的参数列表。
对于ThroughProxyWithAdditionalInterfaces方法,大部分参数都一个前一个方法相同,不同的是,多了一个target参数,并且少了constructorParameters参数。这是因为该方法是已有对象设置拦截,所以不需要再创建对象,所以就不需要constructorParameters参数了。
接下来需要详细讨论的就是IInterceptor接口,上面的两个方法分别需要一个ITypeInterceptor参数和IInstanceInterceptor,而这两个类型其实都继承自IInterceptor。实际的继承层次如下:
IInterceptor
IInstanceInterceptor
InterfaceInterceptor
TransparentProxyInterceptor
ITypeInterceptor
VirtualMethodInterceptor
上面已经提到了实例拦截和类型拦截的区别。那么InterfaceInterceptor和TransparentProxyInterceptor之间的区别有是什么呢?如果了解透明代理,那么就会知道,透明代理既可以实现接口类型拦截。实验如下:
var o = Intercept.ThroughProxyWithAdditionalInterfaces(
typeof(ICommand),
new RoutedCommand(),
new InterfaceInterceptor(),
new[] { new MyBehavior() },
Type.EmptyTypes);
//DynamicModule.ns.Wrapped_ICommand_da166496392c4afcb922aec25c494e15
var type = o.GetType().FullName;
//false
var b = RemotingServices.IsTransparentProxy(o);
下面使用了TransparentProxyInterceptor
var o = Intercept.ThroughProxyWithAdditionalInterfaces(
typeof(ICommand),
new RoutedCommand(),
new TransparentProxyInterceptor(),
new[] { new MyBehavior() },
Type.EmptyTypes);
//DynamicModule.ns.Wrapped_ICommand_da166496392c4afcb922aec25c494e15
//vartype = o.GetType().FullName;
//true
var b = RemotingServices.IsTransparentProxy(o);
我为什么把获取类型全名称的的代码给注释掉了呢?因为对于透明代理拦截,连GetType这样的方法也会被拦截,从而调用你的AOP代码,因为我们还没有开始写AOP代码,所以暂时不能执行这段代码。(这又是接口拦截和透明代理拦截的一个重要区别)
从上面的两个例子可以看出,InterfaceInterceptor拦截其实是使用了EmitIL的方法,也就是动态产生代码的方法来实现拦截,这种方法比透明代理拦截在性能上有很大优势。具体有多大优势,需要实验才能知道。对于类型拦截,VirtualMethodInterceptor用的又是什么方法呢?通过如下代码就可以知道:
var o = Intercept.NewInstanceWithAdditionalInterfaces(
typeof(Window),
new VirtualMethodInterceptor(),
new[] { new MyBehavior() },
Type.EmptyTypes);
//DynamicModule.ns.Wrapped_Window_c2671b9cca6a4c059618834964a35e79
var type = o.GetType().FullName;
//false
var b = RemotingServices.IsTransparentProxy(o);
最后一个问题:所有的类型都是可拦截的吗?当然不是,什么类型可以拦截要看CanIntercept方法的实现逻辑,如下:
public interface IInterceptor
{
boolCanIntercept(Type t);
}
对于InterfaceInterceptor,只有接口类型才能被拦截。对于VirtualMethodInterceptor,因为只有虚拟方法才能被拦截,所以可被拦截的类型是具体类,而且该类是public并且没有声明为sealed,因为如果一个类被声明为sealed,就不能可能被作为基类型使用,也就不可能创建派生类来实现方法拦截。最后对于TransparentProxyInterceptor,只有接口类型或者MarshalByRefObject的子类型才能被拦截。上面提到透明代理的性能会比Emit IL差,所以应该尽量不要使用TransparentProxyInterceptor拦截器。
对于IInterceptor已经差不多了解了,再来看看interceptionBehaviors参数。改参数是一个IInterceptionBehavior接口实例的集合。这正是AOP中关键环节,我们应该创建自己的类来实现IInterceptionBehavior,举例如下:
class MyBehavior : IInterceptionBehavior
{
public IEnumerable<Type>GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
public IMethodReturn Invoke(IMethodInvocationinput, GetNextInterceptionBehaviorDelegategetNext)
{
Console.WriteLine("Before"+ input.MethodBase.Name + " is called");
varret = getNext().Invoke(input, getNext);
Console.WriteLine("After" + input.MethodBase.Name + " is called");
returnret;
}
public bool WillExecute
{
get { return true; }
}
}
先看最重要的部分,Invoke方法。我们最终的目的是拦截调用,现在我们的目的达到了,在外部调用任何被拦截的方法的时候,MyBehavior类的Invoke方法将会被调用,这样我们就可以在最终的方法执行之前和之后做一些操作,比如写日志之类的。因为我们可能传递多个实现了IInterceptionBehavior的对象,所以需要调用getNext来获取下一个Behavior对象并调用之,getNext代理本身其实引用了一个实例方法,而这个实例的类型就是InterceptionBehaviorPipeline,这个类型的实现非常简单,内部引用一个计数,每次调用getNext就会让计数加1,下一次调用就会得到下一个Behavior对象,当所有的Behavior对象都已执行完毕,方法的真正实现会被调用,并返回。
WillExecute属性非常简单,它表示当前的行为对象会不会参与调用链的执行。利用这个属性,可以在每次调用时做一些动态配置,从而只对可拦截方法里面的一部分进行拦截。
GetRequiredInterfaces方法返回一个接口列表,该接口列表表示当前行为需要这个接口列表的支持。举例来说,如果该行为类的目的是拦截INotifyPropertyChanged上的方法,就应该像下面这样实现:
public IEnumerable<Type>GetRequiredInterfaces()
{
yield return typeof(INotifyPropertyChanged);
}
假如你确定被拦截类型已经实现了INotifyPropertyChanged,那么就可以简单的返回一个空类型数组,如下:
public IEnumerable<Type>GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
最后需要解释的就是additionalInterfaces参数了,这个参数是一个接口类型集合。如果其中有类型不是接口类型,系统就会抛出异常。该参数的目的是为了让可拦截类型实现所有在此参数中指定的接口类型,这跟GetRequiredInterfaces方法返回的接口类型集合类似。实际上,内部的逻辑是:合并additionalInterfaces集合和GetRequiredInterfaces返回的集合,让被拦截类型实现所有这些接口。(这里的说法可能有误,因为对于透明代理拦截来说,不存在实现不实现的问题,因为透明代理对象可以转换为任何接口和任何从MarshalByRefObject派生的类型。)