Enterprises Library中Unity中的Interception
在框架設計中,調用攔截是非常有用的技術,主要用來實現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派生的類型。)