摘要
DI容器的一個責任是管理他創建的對象的生命周期。他應該決定什么時候創建一個給定類型的對象,什么時候使用已經存在的對象。他還需要在對象不需要的時候處理對象。Ninject在不同的情況下管理對象的生命周期提供了強大的支持。在我們定義一個綁定的時候,定義創建對象的范圍。在那個范圍內,對象將被重用,每次綁定只存在一次。注意,對象不允許依賴於生命周期短自己小的對象。
1、暫時范圍
在暫時態范圍內,對象生命周期不被Ninject進行管理。任何時候請求一個類型的對象,都將創建一新對象。Ninject不管理保持創建的對象或者在范圍內處理他。這是Ninject默認的范圍。如果不指定范圍,默認是暫時態。在上一篇文章里,ConsoleLogger和MailServer對象都是暫時態,因為沒有指定他的范圍。
2、單例范圍
有時候我們不想每次需要的時候都創建一個新的對象,這時候使用單例。有兩種方法創建單例。一種是使用單例模式。一種是使用Ninject方法InSingletonScope。
1)使用單例模式:
1 class ConsoleLogger:ILogger 2 { 3 public static readonly ConsoleLogger Instance = new ConsoleLogger(); 4 private static ConsoleLogger() 5 { 6 // Hiding constructor 7 } 8 public void Log(string message) 9 { 10 Console.WriteLine("{0}: {1}", DateTime.Now, message); 11 } 12 }
然后在Bind方法后調用ToConstant方法指定靜態只讀對象ConsoleLogger.Instance為常量對象。
kernel.Bind<ILogger>().ToConstant(ConsoleLogger.Instance);
2)使用方法InSingletonScope:
kernel.Bind<ILogger>().To<ConsoleLogger>().InSingletonScope();
如果要給MailServerConfig類對象設置單例,則先調用ToSelf方法將他綁定自身,然后再調用方法InSingletonScope。
kernel.Bind<MailServerConfig>().ToSelf().InSingletonScope();
3、線程范圍
如果定義在線程范圍內,每一個線程將只創建一個給定類型的對象。對象的生命周期跟對象所在的線程一樣長。
調用方法InThreadScope創建線程范圍:
kernel.Bind<object>().ToSelf().InThreadScope();
創建兩個Test方法測試線程范圍。
1 using Ninject; 2 using NUnit.Framework; 3 using System.Threading; 4 5 namespace Demo.Ninject 6 { 7 [TestFixture] 8 class NinjectTest 9 { 10 [Test] 11 public void ReturnsTheSameInstancesInOneThread() 12 { 13 using (var kernel = new StandardKernel()) 14 { 15 kernel.Bind<object>().ToSelf().InThreadScope(); 16 var instance1 = kernel.Get<object>(); 17 var instance2 = kernel.Get<object>(); 18 Assert.AreEqual(instance1, instance2); 19 } 20 } 21 22 [Test] 23 public void ReturnsDifferentInstancesInDifferentThreads() 24 { 25 var kernel = new StandardKernel(); 26 kernel.Bind<object>().ToSelf().InThreadScope(); 27 var instance1 = kernel.Get<object>(); 28 new Thread(() => 29 { 30 var instance2 = kernel.Get<object>(); 31 Assert.AreNotEqual(instance1, instance2); 32 kernel.Dispose(); 33 }).Start(); 34 } 35 } 36 }
第一個方法在同一個線程內請求了兩個object對象,他們是相同的實例。第二個方法先在主線程內請求一個object實例,然后開啟另一個線程請求另一個實例,他們不是相同的實例。
需要添加NUnit和NUnit.Console才能測試上面的方法。我使用的是NUnit 2.6.4和NUnit.Console 2.0.0。
4、請求范圍
請求范圍在web應用程序里非常有用。可以在相同的請求范圍內得到一個單例的對象。一旦一個請求被處理,另一個請求到來,Ninject創建新的對象實例,並保持他直到請求結束。
調用方法InRequestScope設置請求范圍,例如:
kernel.Bind<MailServerConfig>().ToSelf().InRequestScope();
需要添加Ninject.Web.Common引用才能夠調用InRequestScope方法。
5、自定義范圍
自定義范圍讓我們定義我們自己的范圍,在這個范圍內保持一類型的唯一對象。只要提供的回調方法返回的對象引用是一樣的,Ninject在這個范圍內返回相同的實例。只要返回的對象引用變了,將創建一新的指定類型的對象。創建的對象實例將一直保存在緩存里,直到返回的范圍對象被垃圾回收器回收。一旦范圍對象被垃圾回收器回收,Ninject創建的所有的對象實例將被從緩存中釋放和處理。
調用InScope方法傳入Lamda表達式定義自定義返回。例如:
kernel.Bind<object>().ToSelf().InScope(ctx => User.Current);
用例子來介紹自定義范圍:
1)創建User類。
1 class User 2 { 3 public string Name { get; set; } 4 public static User Current { get; set; } 5 }
2)創建函數ReturnsTheSameInstancesForAUser。
1 [Test] 2 public void ReturnsTheSameInstancesForAUser() 3 { 4 using (var kernel = new StandardKernel()) 5 { 6 kernel.Bind<object>().ToSelf().InScope(ctx => User.Current); 7 User.Current = new User(); 8 var instance1 = kernel.Get<object>(); 9 User.Current.Name = "Foo"; 10 var instance2 = kernel.Get<object>(); 11 Assert.AreEqual(instance1, instance2); 12 } 13 }
雖然User.Current.Name的值變了,但是User.Current的引用沒變。因此,兩次請求返回的對象是同一個對象。
3)創建函數ReturnsDifferentInstancesForDifferentUsers。
1 [Test] 2 public void ReturnsDifferentInstancesForDifferentUsers() 3 { 4 using (var kernel = new StandardKernel()) 5 { 6 kernel.Bind<object>().ToSelf().InScope(ctx => User.Current); 7 User.Current = new User(); 8 var instance1 = kernel.Get<object>(); 9 User.Current = new User(); 10 var instance2 = kernel.Get<object>(); 11 Assert.AreNotEqual(instance1, instance2); 12 } 13 }
因為改變了User.Current對象引用,因此,兩次請求返回的對象是不同的對象。
你可能注意到了回調函數提供了一個名字是ctx的IContext參數。這個對象提供了綁定的用來創建范圍對象的上下文環境。自定義范圍是最靈活最有用的范圍。其實其他范圍都可以用自定義范圍來實現。
線程范圍:
kernel.Bind<object>().ToSelf().InScope(ctx=>Thread.CurrentThread);
請求范圍:
kernel.Bind<object>().ToSelf().InScope(ctx=>HttpContext.Current);
可以使用Release方法強制釋放創建的對象實例。
1 var myObject = kernel.Get<MyService>(); 2 .. 3 kernel.Release(myObject);