IoC+AOP的簡單實現
對EnterLib有所了解的人應該知道,其中有一個名叫Policy Injection的AOP框架;而整個EnterLib完全建立在另一個叫作Unity的底層框架之上,我們可以將Unity看成是一個IoC的框架。對于一個企業應用來說說,AOP和IoC是我們進行邏輯分離和降低耦合度最主要的方式,而將兩者結合起來具有重要的現實意義。
一、基于IoC+AOP的編程
到底將IoC和AOP進行整合后,會對編程但來怎樣的影響,我寫了一個簡單的例子(你可以從這里下載該實例)。假設我現在有兩個模塊,分別稱為Foo和Bar,通過如下同名的類來表示。Foo和Bar具有各自的接口,分別為IFoo和IBar。簡單起見,我在兩個接口中定義了相同的方法:DoSomething。在Foo中,具有一個類型為IBar的只讀屬性,而DoSomething的實現就是通過調用該屬性的同名方法實現。
1: public interface IFoo
2: {
3: void DoSomething();
4: }
5:
6: public interface IBar
7: {
8: void DoSomething();
9: }
10: [FooCallHandler]
11: public class Foo : IFoo
12: {
13: public IBar Bar { get; private set; }
14: public Foo(IBar bar)
15: { this.Bar = bar; }
16: public void DoSomething()
17: {
18: this.Bar.DoSomething();
19: }
20: }
21: [BarCallHandler]
22: public class Bar : IBar
23: {
24: public void DoSomething()
25: {
26: Console.WriteLine("Do something...");
27: }
28: }
在類型Foo和Bar上分別應用了兩個自定義特性:FooCallHandlerAttribute和BarCallHandlerAttribute,熟悉PIAB的讀者對此應該不感到陌生——CallHandler承載著被分離出來的橫切關注點(Crosscutting Concern)的實現。由于在這里僅僅是一個簡單的演示,我定義了兩個最簡單的CallHandler:FooCallHandler和BarCallHandler。下面是這個兩個CallHandler的對應的HandlerAttribute的定義。
1: public class FooCallHandler : ICallHandler
2: {
3: public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
4: {
5: Console.WriteLine("Foo: Preoperation is executed.");
6: var methodReturn = getNext()(input, getNext);
7: Console.WriteLine("Foo: Postoperation is executed.");
8: return methodReturn;
9: }
10: public int Order { get; set; }
11: }
12: public class BarCallHandler : ICallHandler
13: {
14: public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
15: {
16: Console.WriteLine("Bar: Preoperation is executed.");
17: var methodReturn = getNext()(input, getNext);
18: Console.WriteLine("Bar: Postoperation is executed.");
19: return methodReturn;
20: }
21: public int Order { get; set; }
22: }
23: public class FooCallHandlerAttribute : HandlerAttribute
24: {
25: public override ICallHandler CreateHandler(IUnityContainer container)
26: {
27: return new FooCallHandler { Order = this.Order };
28: }
29: }
30: public class BarCallHandlerAttribute : HandlerAttribute
31: {
32: public override ICallHandler CreateHandler(IUnityContainer container)
33: {
34: return new BarCallHandler { Order = this.Order };
35: }
36: }
兩個CallHandler具有相似的實現:在目標方法執行前后打印相應的文字,代表在對方法調用進行攔截后,需要在方法執行前后實現的橫切關注點。目前為止,提供具體功能的組建定義完成,我們來編寫消費它們的客戶程序:
1: static void Main(string[] args)
2: {
3: var container = GetContainer();
4: var foo = container.Resolve<IFoo>();
5: foo.DoSomething();
6: }
從上面我們可以看出,我們最終的代碼僅僅只要三行。雖然簡單,我們不妨來也做一下分析:首先,客戶端對組件Foo的調用是基于接口(IFoo)而不是基于具體類型Foo,這樣能夠盡可能地降低對組件Foo的依賴;其次,Foo依賴于Bar,而這種依賴也是基于接口的,這在Foo的定義了看得出來;第三,雖然Foo依賴于Bar,但是這種依賴是和Foo的具體實現相關,和客戶程序無關,所以客戶程序未曾出現Bar和IBar的影子。
運行上面的程序,你會得到如下的輸出。從中我們可以看出:不但具體的業務功能(即定義在Bar的DoSomething方法中的邏輯)能夠正常執行,通過自定義特性的方式應用到兩個組件上動態注入的橫切關注點也得到正確地執行。
1: Foo: Preoperation is executed.
2: Bar: Preoperation is executed.
3: Do something...
4: Bar: Postoperation is executed.
5: Foo: Postoperation is executed.
那么,是什么導致了程序的完全按照我們希望的方式執行的呢?由于客戶端邏輯只有三句代碼,你肯定會猜的到:所有的一切都是由一開始創建的container對象完成的。如何你了解Unity的話,應該可以猜出這是一個UnityContainer。通過接口和類型的匹配關系的注冊,UnityContainer知道如何根據接口找到相應的實現類型(IFoo-〉Foo,IBar-〉Bar),這不難理解,這也不是本篇文章介紹的重點。我們關注點是:UnityContainer是如何讓通過自定義特性方式應用在Foo和Bar上的兩個CallHandler得到執行的?
二、CallHandler是如何被執行的
同所有的AOP實現一樣,PIAB也是采用方法攔截(Method Interception)機制。具體來說,PIAB又具有兩個不同的方式:實例攔截(Instance Interception)和類型攔截(Type Interception)。而前者具有兩種不同的實現:TransparentProxy/RealProxy的方式和Reflection Emit的方式。三種不同攔截方式的具體實現,不是本文的重點,對此有興趣的朋友可以參閱PIAB官方文檔。
總的來說,能夠在方法執行過程中被攔截的對象需要通過PIAB的方式進行創建,實際上PIAB是對目標對象進行相應的封裝,進而得到一個代理對象。由于方法調用請求是從代理對象發出,PIAB在將該請求發送給最終的目標對象之前將其攔截下來,從而使相應的CallHandler得以執行。那么,如果我們能夠讓UnityContainer在對象創建過程完成這一道工序,那么最終被創建出來的對象就具有了被攔截的能力。
如何將PIAB對實例的封裝操作注入到UnityContainer怎個對象創建流程中呢?這需要借助于UnityContainer提供的擴展機制。雖然Unity僅僅是一個輕量級的IoC框架,但是內部的實現其實是挺復雜的。個人曾經不止一次地分析過Unity的源代碼,但是沒過多久就忘得七七八八。不過,萬變不離其宗,UnityContainer最根本的就是其BuilderStrategy管道(可以參閱我的文章《你知道Unity IoC Container是如何創建對象的嗎?》)。
我們的解決方案就是將PIAB對實例的封裝寫在相應的BuilderStrategy種,然后通過UnityContainerExtension注冊到某個UnityContainer中。為此我創建了如下一個名稱為InterceptionStrategy的BuilderStrategy。
1: public class InterceptionStrategy : BuilderStrategy
2: {
3: public static IUnityContainer UnityContainer { get; private set; }
4: public static InjectionMember InterceptionMember { get; private set; }
5:
6: static InterceptionStrategy()
7: {
8: IUnityContainer container = new UnityContainer();
9: UnityContainerConfigurator configurator = new UnityContainerConfigurator(container);
10: EnterpriseLibraryContainer.ConfigureContainer(configurator, ConfigurationSourceFactory.Create());
11: UnityContainer = container;
12: InterceptionMember = new InstanceInterceptionPolicySettingInjectionMember(new TransparentProxyInterceptor());
13: }
14:
15: public override void PostBuildUp(IBuilderContext context)
16: {
17: if (null == context.Existing ||
18: context.Existing.GetType().FullName.StartsWith("Microsoft.Practices") ||
19: context.Existing is IInterceptingProxy)
20: {
21: return;
22: }
23: context.Existing = UnityContainer.Configure<TransientPolicyBuildUpExtension>().BuildUp
24: (context.OriginalBuildKey.Type, context.Existing, null, InterceptionMember);
25: }
26: }
如果你對Unity的內部機制有一定了解,理解上面的實現應該不成問題,但是你對此一無所知,我講解得再詳細你可能也弄不清楚。所以,對于上述的邏輯實現,在這里就不多作介紹了。
自定義的BuilderStrategy一般需要通過相應的UnityContainerExtension完成注冊。下面是我們創建的UnityContainerExtension:InterceptionExtension。我們將UnityContainerExtension對象添加到UnityContainer的BuilderStrategy管道之中,相應的BuilderStage設置為PreCreation。
1: public class InterceptionExtension: UnityContainerExtension
2: {
3: protected override void Initialize()
4: {
5: Context.Strategies.AddNew<InterceptionStrategy>(UnityBuildStage.PreCreation);
6: }
7: }
三、GetContainer()的實現…
最初例子得以正常運行的魔力來自于通過靜態方法GetContainer創建的UnityContainer對象,我們現在來看看該方法的實現。首先我們創建一個UnityContainer對象,然后對其進行初始化配置,最后將上面創建的InterceptionExtension擴展添加到該UnityContainer中。接口和實現類型的注冊被隨后執行,不過在真正的開發中,我們習慣通過配置文件進行注冊。這就是整個實現,沒有復雜的邏輯,卻能帶來很大的用處。
1: static IUnityContainer GetContainer()
2: {
3: IUnityContainer container = new UnityContainer();
4: UnityContainerConfigurator configurator = new UnityContainerConfigurator(container);
5: EnterpriseLibraryContainer.ConfigureContainer(configurator, ConfigurationSourceFactory.Create());
6: container.AddNewExtension<InterceptionExtension>();
7: container.RegisterType<IFoo, Foo>();
8: container.RegisterType<IBar, Bar>();
9: return container;
10: }
四、申明
文中提供解決方案只是為你提供一種思路,相關的邏輯實現基本來自于腦子中的靈光一現,并沒有進行深入的評估和性能測試。如果你希望在你自己的項目中使用,最好在此基礎上進行深入的思考,相信會發現其中存在的不足。此外,不知道讀者有沒有注意,上面的實現方式僅僅提供一種攔截方式,即基于TransparentProxy的方式,有興趣的讀者可以再作一些擴展,實現對攔截方式的定制。
留言列表