Unity Ioc 介紹:
Unity是微軟團隊開發的一個輕量級,可擴展的依賴注入容器,為松散耦合應用程序提供了很好的解決方案,支持構造器注入,屬性注入,方法注入。同時因為把對象交給容器創建,有沒有可能在創建的時候做些手腳和功能呢?答案是肯定的。
目前Unity中提供兩個Lifetime Manager類可供我們直接使用,當然你也可以實現自己的Lifetime Manager類。
1. ContainerControlledLifetimeManager
Unity保存一個指向對象實例的引用。通過Unity容器為同一個類型或對象獲取對象實例時,每次獲取到的都是同一個實例。也就是說實現了對象單例模式。默認情況下,RegisterInstance方法使用該Lifetime Manager。
2. ExternallyControlledLifetimeManager
Unity僅保存一個指向對象實例的弱引用。通過Unity容器為同一個類型或對象獲取對象實例時,每次獲取到的都是同一個實例。但是由於當對象創建完之后,容器沒有對該對象的強引用,所以就可能出現當其他地方沒有去強引用它時候,會被GC回收掉。
先看看一個接口和類,下面會用到
{
void Play();
}
public class Mp3Player : IPlayer
{
public void Play()
{
Console.WriteLine( " Playing Mp3 " );
}
}
接下來通過在RegisterType和RegisterInstance時指定相應的Lifetime Manager來介紹Lifetime Manager的應用場景。
1. RegisterType
當用RegisterType注冊映射關系時,如果沒有指定LifetimeManager,默認是使用一個瞬態的Lifetime Manager。即每次通過Unity容器獲取對象的實例時都會重新創建一個該實例,也就是說Unity容器不存在一個到該對象的引用。
看一個例子:
container.RegisterType < IPlayer, Mp3Player > ();
IPlayer player1 = container.Resolve < IPlayer > ();
Console.WriteLine( string .Format( " Player1 HashCode: {0} " ,player1.GetHashCode()));
IPlayer player2 = container.Resolve < IPlayer > ();
Console.WriteLine( string .Format( " Player2 HashCode: {0} " ,player2.GetHashCode()));
輸出結果:
通過輸出的player1和player2對象的HashCode值可以看出,player1和player2分別是Mp3Player類的不同實例。
那怎樣實現單例模式呢?
要實現單例模式,容器需要保存一個指向對象實例的引用。通過在RegisterType時為它指定相應的Lifetime Manager可以實現單例模式,從上面對ContainerControlledLifetimeManager和ExternallyControlledLifetimeManager的介紹可以知道,這兩個Lifetime Manager都可以支持單例模式。
修改上面的代碼為:
// 這里指定使用ContainerControlledLifetimeManager對象
container.RegisterType < IPlayer, Mp3Player > ( new ContainerControlledLifetimeManager());
IPlayer player1 = container.Resolve < IPlayer > ();
Console.WriteLine( string .Format( " Player1 HashCode: {0} " ,player1.GetHashCode()));
IPlayer player2 = container.Resolve < IPlayer > ();
Console.WriteLine( string .Format( " Player2 HashCode: {0} " ,player2.GetHashCode()));
看看輸出:
通過輸出結果可以看出,player1和player2對象為Mp3Player類的同一實例,指向同一內存地址。
2. RegisterInstance
當用RegisterInstance注冊映射關系時,如果沒有指定Lifetime Manager,默認是使用ContainerControlledLifetimeManager,即支持單例模式。
看個例子:
IPlayer mp3Player = new Mp3Player();
container.RegisterInstance < IPlayer > (mp3Player);
IPlayer player1 = container.Resolve < IPlayer > ();
Console.WriteLine( string .Format( " Player1 HashCode: {0} " , player1.GetHashCode()));
IPlayer player2 = container.Resolve < IPlayer > ();
Console.WriteLine( string .Format( " Player2 HashCode: {0} " , player2.GetHashCode()));
看看輸出:
通過輸出結果可以看出,player1和player2對象為Mp3Player類的同一實例,指向同一內存地址。
Unity是微軟P&P部門開發的一個輕量級IoC框架,通過Interception機制可以實現基於三種攔截機制的AOP。不過Unity僅僅提供“顯式”攔截機制,以致我們為了注冊可被攔截的類型會多寫很多代碼和配置。本篇文章通過UnityContainer的擴展提供了一種“自動”攔截機制。
一、顯式攔截
我們通過一個簡單的實例演示Unity原生支持的顯式攔截機制和我們通過擴展實現的自動攔截機制。我們定了如下一個簡單的SimpleCallHandler,在Invoke方法中通過在控制台打印一段文字用以證明應用在某個類型上的CallHandler被執行了。
1: public class SimpleCallHandler : ICallHandler
2: {
3: public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
4: {
5: Console.WriteLine("The CallHandler applied to \"{0}\" is invoked.", input.Target.GetType().Name);
6: return getNext()(input, getNext);
7: }
8: public int Order { get; set; }
9: }
10: public class SimpleCallHandlerAttribute : HandlerAttribute
11: {
12: public override ICallHandler CreateHandler(IUnityContainer container)
13: {
14: return new SimpleCallHandler { Order = this.Order };
15: }
16: }
然后我們創建了如下所示的一個接口IFoo和三個類Foo、Bar和Baz。其中Foo實現了接口IFoo,而Foo依賴於Bar,Bar依賴於Baz。我們以構造器注入的方式定義Foo和Bar。SimpleCallHandler被同時應用到了Foo、Bar和Baz的DoSth方法上。
1: public interface IFoo
2: {
3: void DoSth();
4: }
5:
6: public class Foo : IFoo
7: {
8: public Bar Bar { get; private set; }
9: public Foo(Bar bar)
10: {
11: this.Bar = bar;
12: }
13: [SimpleCallHandler]
14: public virtual void DoSth()
15: {
16: this.Bar.DoSth();
17: }
18: }
19: public class Bar : MarshalByRefObject
20: {
21: public Baz Baz { get; private set; }
22: public Bar(Baz baz)
23: {
24: this.Baz = baz;
25: }
26: [SimpleCallHandler]
27: public virtual void DoSth()
28: {
29: this.Baz.DoSth();
30: }
31: }
32: public class Baz : MarshalByRefObject
33: {
34: [SimpleCallHandler]
35: public void DoSth()
36: {
37: Console.WriteLine("Done...");
38: }
39: }
所謂顯式攔截就是說:如果某個類型需要被攔截處理,比如將其顯式地注冊為“可被攔截的類型”,並且需要顯式地注冊攔截器(決定攔截機制)和攔截行為。對於本實例來說,為了上應用在Foo、Bar和Baz上的CallHandler能夠起作用,我們需要通過如下的方式對這三個類型進行顯式地攔截注冊。
1: IUnityContainer container = new UnityContainer();
2: container.AddNewExtension<Interception>()
3: .RegisterType<IFoo, Foo>(new Interceptor<TransparentProxyInterceptor>(), new InterceptionBehavior<PolicyInjectionBehavior>())
4: .RegisterType<Bar>(new Interceptor<TransparentProxyInterceptor>(), new InterceptionBehavior<PolicyInjectionBehavior>())
5: .RegisterType<Baz>(new Interceptor<TransparentProxyInterceptor>(), new InterceptionBehavior<PolicyInjectionBehavior>());
6:
7: IFoo foo = container.Resolve<IFoo>();
8: foo.DoSth();
運行結果:
1: The CallHandler applied to "Foo" is invoked.
2: The CallHandler applied to "Bar" is invoked.
3: The CallHandler applied to "Baz" is invoked.
4: Done...
二、自動攔截
如果通過我們自定義的UnityContainer擴展AutoInterception,你就無須對需要被攔截的類型進行顯式注冊。而相關的代碼將會變得簡單,運行如下一段代碼,你依然會得到同上面一樣的結果。
1: IUnityContainer container = new UnityContainer();
2: container.AddNewExtension<AutoInterception>()
3: .AddNewExtension<Interception>()
4: .RegisterType<IFoo, Foo>();
5:
6: IFoo foo = container.Resolve<IFoo>();
7: foo.DoSth();
三、應用不同的攔截機制
在默認的情況下,AutoInterception采用的攔截器為TransparentProxyInterceptor。我們通過通過配置AutoInterception的方式來應用其它兩種攔截器,即InterfaceInterceptor和VirtualMethodInterceptor。由於在下面的代碼中采用了InterfaceInterceptor,所有只有實現了IFoo接口的Foo對象才會被攔截。
1: IUnityContainer container = new UnityContainer();
2: container.AddNewExtension<AutoInterception>()
3: .AddNewExtension<Interception>()
4: .Configure < AutoInterception>().RegisterInterceptor(new InterfaceInterceptor())
5: .RegisterType<IFoo, Foo>();
6:
7: IFoo foo = container.Resolve<IFoo>();
8: foo.DoSth();
執行結果:
1: The CallHandler applied to "Foo" is invoked.
2: Done...
如果我們采用VirtualMethodInterceptor的話,只有定義在需方法的Foo和Bar的DoSth方法才會被攔截。
1: IUnityContainer container = new UnityContainer();
2: container.AddNewExtension<AutoInterception>()
3: .AddNewExtension<Interception>()
4: .Configure < AutoInterception>().RegisterInterceptor(new VirtualMethodInterceptor())
5: .RegisterType<IFoo, Foo>();
6:
7: IFoo foo = container.Resolve<IFoo>();
8: foo.DoSth();
輸出結果:
1: The CallHandler applied to "Wrapped_Foo_6c22528df1b64d3886e9955cd8961ca7" is invoked.
2: The CallHandler applied to "Wrapped_Bar_c10e3640a27d469c8872ec4193303897" is invoked.
3: Done...
四、支持配置
AutoInterception不僅僅支持Unity提供的Policy Injection配置,還可以通過配置指定采用的攔截器類型。現在我們將應用在Foo、Bar和Baz上的SimpleCallHandlerAttribute特性全部刪除,通過如下的配置將該CallHandler應用到所有的DoSth方法上。這個配置還指定了采用的攔截器類型為VirtualMethodInterceptor。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <configSections>
4: <section name="unity"
5: type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
6: </configSections>
7: <unity>
8: <alias alias="SimpleCallHandler" type="Artech.UnityExtensions.SimpleCallHandler, Artech.UnityExtensions" />
9: <alias alias="IFoo" type="Artech.UnityExtensions.IFoo, Artech.UnityExtensions" />
10: <alias alias="Foo" type="Artech.UnityExtensions.Foo, Artech.UnityExtensions" />
11: <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
12: <sectionExtension type="Artech.UnityExtensions.Configuration.AutoInterceptionConfigurationExtension, Artech.UnityExtensions" />
13: <container>
14: <extension type="AutoInterception"/>
15: <extension type="Interception"/>
16: <register type="IFoo" mapTo="Foo"/>
17: <interception>
18: <policy name="service">
19: <matchingRule name="MemberNameMatchingRule" type="MemberNameMatchingRule">
20: <constructor>
21: <param name="nameToMatch" value="DoSth"/>
22: </constructor>
23: </matchingRule>
24: <callHandler name="SimpleCallHandler" type="SimpleCallHandler"/>
25: </policy>
26: </interception>
27: <autoInterception>
28: <interceptor type="VirtualMethodInterceptor"/>
29: </autoInterception>
30: </container>
31: </unity>
32: </configuration>
我們通過如下的代碼,通過加載配置的方式來配置創建的UnityContainer。最終直接通過解析接口IFoo得到Foo對象,並調用其DoSth方法。
1: IUnityContainer container = new UnityContainer();
2: UnityConfigurationSection unitySettings = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName);
3: unitySettings.Configure(container);
4: IFoo foo = container.Resolve<IFoo>();
5: foo.DoSth();
由於我們采用的是VirtualMethodInterceptor,所有只有Foo和Bar中定義的需方法才能被攔截,這可以通過如下的輸出結果得到證實:
1: The CallHandler applied to "Wrapped_Foo_53c9f355fbac4acdaf405b2a92d0bd7a" is invoked.
2: The CallHandler applied to "Wrapped_Bar_8cdbf768e96c434da36ed1f181c2d6cd" is invoked.
3: Done...
雖然AutoInterception實現的邏輯並不復雜,但是對於不了解Unity設計的人來說也不是那么容易理解的。所以我並不打算介紹其內部原理,又興趣的讀者可以從
/Files/scottpei/AutoInterception.rar
下載源代碼。