摘要
DI容器的一个责任是管理他创建的对象的生命周期。他应该决定什么时候创建一个给定类型的对象,什么时候使用已经存在的对象。他还需要在对象不需要的时候处理对象。Ninject在不同的情况下管理对象的生命周期提供了强大的支持。在我们定义一个绑定的时候,定义创建对象的范围。在那个范围内,对象将被重用,每次绑定只存在一次。注意,对象不允许依赖于生命周期短自己小的对象。
1、暂时范围
在暂时态范围内,对象生命周期不被Ninject进行管理。任何时候请求一个类型的对象,都将创建一新对象。Ninject不管理保持创建的对象或者在范围内处理他。这是Ninject默认的范围。如果不指定范围,默认是暂时态。
1 namespace Ninject 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 IKernel kernel = new StandardKernel(); 8 9 kernel.Bind<ILogger>().To<ConsoleLogger>(); 10 11 ILogger logger = kernel.Get<ILogger>(); 12 13 logger.Log("Console log"); 14 15 Console.ReadKey(); 16 } 17 } 18 19 interface ILogger 20 { 21 void Log(string message); 22 } 23 24 25 class ConsoleLogger : ILogger 26 { 27 public void Log(string message) 28 { 29 Console.WriteLine("{0}: {1}", DateTime.Now, message); 30 } 31 } 32 }
2、单例范围
有时候我们不想每次需要的时候都创建一个新的对象,这时候使用单例。有两种方法创建单例。一种是使用单例模式。一种是使用Ninject方法InSingletonScope。
1)使用单例模式:
namespace Ninject { class Program { static void Main(string[] args) { IKernel kernel = new StandardKernel(); kernel.Bind<ILogger>().ToConstant(ConsoleLogger.Instance); ILogger logger = kernel.Get<ILogger>(); logger.Log("Console log"); Console.ReadKey(); } } interface ILogger { void Log(string message); } class ConsoleLogger : ILogger { public static readonly ConsoleLogger Instance = new ConsoleLogger(); private ConsoleLogger() { } public void Log(string message) { Console.WriteLine("{0}: {1}", DateTime.Now, message); } } }
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);