zoukankan      html  css  js  c++  java
  • C# Note3:大话Ninject

    前言

      之所以研究Ninject,是因为初入职在开发XX项目的ComponentService部分时用到了它,一下子发现了它的强大。渐渐地发现在项目中,有时会用到优秀的第三方开源库,这些都是前人智慧的结晶,值得学习和应用。

    1.简介

    Ninject(官网:http://www.ninject.org/)是一个快如闪电,超轻量级的基于的.Net平台的依赖注入框架。它能够帮助你把应用程序分离成一个个松耦合,高内聚的模块,然后用一种灵活的方式组装起来。通过使用Ninject配套你的软件架构,那么代码将会变得更加容易编写,重用性强,易于测试和修改。

    Ninject is a lightning-fast, ultra-lightweight dependency injector for .NET applications. It helps you split your application into a collection of loosely-coupled, highly-cohesive pieces, and then glue them back together in a flexible manner. By using Ninject to support your software's architecture, your code will become easier to write, reuse, test, and modify.

    2.特性

    (1)“聚焦”:很多现有的依赖注入项目牺牲了特性的可用性,这通常是不必要的。Ninject每添加一个功能,其相较于增加复杂性的好处在于它提高了日常使用。我们的目标是保持知识的门槛(即使用Ninject所需的基本知识水平)尽可能地低。Ninject有很多高级特性,但是理解他们不需要使用基本功能。

    Focused. Too many existing dependency injection projects sacrifice usability for features that aren't often necessary. Each time a feature is added to Ninject, its benefit is weighed against the complexity it adds to everyday use. Our goal is to keep the barrier to entry - the baseline level of knowledge required to use Ninject - as low as possible. Ninject has many advanced features, but understanding them is not required to use the basic features.

    (2)”光滑“:框架膨胀是一些项目的主要关注点,这样,Ninject的所有核心功能是在单一程序集中对.NET基类库没有外部依赖。当编译为release版本时单个程序集的脚本大约85KB。

    Sleek. Framework bloat is a major concern for some projects, and as such, all of Ninject's core functionality is in a single assembly with no dependencies outside the .NET base class library. This single assembly's footprint is approximately 85KB when compiled for release.

    (3)“快速”。Ninject在CLR中利用轻量级代码生成的优点,而不是依靠反射调用。这在许多解决方案中能导致性能上8-50x的戏剧性提升。

    Fast. Instead of relying on reflection for invocation, Ninject takes advantage of lightweight code generation in the CLR. This can result in a dramatic (8-50x) improvement in performance in many situations.

    (4)“精确“:Ninject有助于开发人员在第一时间将事情做正确。Ninject并非依赖XML映射文件和字符串标识符来连接组件,而是提供了一个健壮的领域特定语言。这意味着,Ninject利用了语言(如类型安全)和IDE(如智能感知和代码自动完成)的功能优势。

    Precise. Ninject helps developers get things right the first time around. Rather than relying on XML mapping files and string identifiers to wire up components, Ninject provides a robust domain-specific language. This means that Ninject takes advantage of the capabilities of the language (like type-safety) and the IDE (like IntelliSense and code completion).

    (5)“敏捷“:Ninject是围绕基于组件的架构而设计,定制和演化。系统的许多方面可以增强或修改以适应每个项目的要求。

    Agile. Ninject is designed around a component-based architecture, with customization and evolution in mind. Many facets of the system can be augmented or modified to fit the requirements of each project.

    (6)“隐形”:Ninject不会侵犯你的代码。在你的项目中,可以很容易地分离Ninject对单一程序集的依赖。

    Stealthy. Ninject will not invade your code. You can easily isolate the dependency on Ninject to a single assembly in your project.

    (7)“强大”:Ninject包括许多高级功能。例如,Ninject是第一个支持上下文绑定的依赖注入容器,其中一个服务的不同具体实现根据请求的上下文而被注入。

    Powerful. Ninject includes many advanced features. For example, Ninject is the first dependency injector to support contextual binding, in which a different concrete implementation of a service may be injected depending on the context in which it is requested.

    3.应用总结及案例 

    综上所述:Ninjec可以理解成使程序脱离耦合的Ioc(控制反转)容器(推荐文章:深入理解DIP、IoC、DI以及IoC容器)。其设计初衷在于:

    (1)某些框架过于依赖配置文件,配置较为复杂和冗长,需要提供assembly-qualified名称来进行定义,应用程序容易遭到破坏仅仅可能因为一个简单的拼写错误。而Ninject默认的做法是通过接口来进行类型绑定,如:将 ServiceImpl 的实现与接口 IService绑定

    Bind<IService>().To<ServiceImpl>();  
    

    (插播一下Entity Framework的code first模式,用代码控制的思想还是不错的。参考文章:EF之code first使用

    (图引自上文,扩展:ado .NET EF有三种设计模式:DBFirst,ModelFirst,CodeFirst

    (2)有些框架太重,通常需要在项目中添加非常多的组件引用,或依赖于各种框架组件。对于较小的项目,会导致代码“膨胀”。Ninject比较简洁明了。  

    (3)Ninject 中有一个非常有用的概念:上下文绑定(Contextual Binding),与基于字符串标识的绑定不同,它可以运行期间灵活地进行条件化的绑定 :

    Bind<IService>().To<RedImpl>().WhenTargetHas<RedAttribute>();  
    Bind<IService>().To<BlueImpl>().WhenTargetHas<BlueAttribute>();  
    class ConsumerA   
    {  
        public ConsumerA([Red] IService service)  
        {  
            //因为加上了"Red"定制特性,通过方法注入"IService"的实现将是RedImpl  
        }  
    }  
    class ConsumerB  
    {  
        public ConsumerB([Blue] IService service)  
        {  
            //因为加上了"Blue"定制特性,通过方法注入"IService"的实现将是BlueImpl  
        }  
    }  
    
    注:该部分代码示例引自:http://blog.csdn.net/simpkan/article/details/8169740

    推荐文章:Ninject的使用(下面摘录)

    DI容器的一个责任是管理他创建的对象的生命周期。他应该决定什么时候创建一个给定类型的对象,什么时候使用已经存在的对象。他还需要在对象不需要的时候处理对象。Ninject在不同的情况下管理对象的生命周期提供了强大的支持。在我们定义一个绑定的时候,定义创建对象的范围。在那个范围内,对象将被重用,每次绑定只存在一次。注意,对象不允许依赖于生命周期短自己小的对象。

    1、暂时范围

    在暂时态范围内,对象生命周期不被Ninject进行管理。任何时候请求一个类型的对象,都将创建一新对象。Ninject不管理保持创建的对象或者在范围内处理他。这是Ninject默认的范围。如果不指定范围,默认是暂时态。

    namespace Ninject
    {
        class Program
        {
            static void Main(string[] args)
            {
                IKernel kernel = new StandardKernel();
    
                kernel.Bind<ILogger>().To<ConsoleLogger>();
    
                ILogger logger = kernel.Get<ILogger>();
    
                logger.Log("Console log");
    
                Console.ReadKey();
            }
        }
    
        interface ILogger
        {
            void Log(string message);
        }
    
        class ConsoleLogger : ILogger
        {
            public void Log(string message)
            {
                Console.WriteLine("{0}: {1}", DateTime.Now, message);
            }
        }
    }

    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方法测试线程范围:

    using Ninject;
    using NUnit.Framework;
    using System.Threading;
    
    namespace Demo.Ninject
    {
        [TestFixture]
        class NinjectTest
        {
            [Test1]
            public void ReturnsTheSameInstancesInOneThread()
            {
                using (var kernel = new StandardKernel())
                {
                    kernel.Bind<object>().ToSelf().InThreadScope();
                    var instance1 = kernel.Get<object>();
                    var instance2 = kernel.Get<object>();
                    Assert.AreEqual(instance1, instance2);
                }
            }
    
            [Test2]
            public void ReturnsDifferentInstancesInDifferentThreads()
            {
                var kernel = new StandardKernel();
                kernel.Bind<object>().ToSelf().InThreadScope();
                var instance1 = kernel.Get<object>();
                new Thread(() =>
                {
                    var instance2 = kernel.Get<object>();
                    Assert.AreNotEqual(instance1, instance2);
                    kernel.Dispose();
                }).Start();
            }
        }
    }

    第一个方法在同一个线程内请求了两个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类:

    class User
    {
         public string Name { get; set; }
         public static User Current { get; set; }
    }

    2)创建函数ReturnsTheSameInstancesForAUser:

    [Test]
            public void ReturnsTheSameInstancesForAUser()
            {
                using (var kernel = new StandardKernel())
                {
                    kernel.Bind<object>().ToSelf().InScope(ctx => User.Current);
                    User.Current = new User();
                    var instance1 = kernel.Get<object>();
                    User.Current.Name = "Foo";
                    var instance2 = kernel.Get<object>();
                    Assert.AreEqual(instance1, instance2);
                }
            }

    虽然User.Current.Name的值变了,但是User.Current的引用没变。因此,两次请求返回的对象是同一个对象。

    3)创建函数ReturnsDifferentInstancesForDifferentUsers:

    [Test]
            public void ReturnsDifferentInstancesForDifferentUsers()
            {
                using (var kernel = new StandardKernel())
                {
                    kernel.Bind<object>().ToSelf().InScope(ctx => User.Current);
                    User.Current = new User();
                    var instance1 = kernel.Get<object>();
                    User.Current = new User();
                    var instance2 = kernel.Get<object>();
                    Assert.AreNotEqual(instance1, instance2);
                }
            }

    因为改变了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);
  • 相关阅读:
    el-select下拉框选项太多导致卡顿,使用下拉框分页来解决
    vue+elementui前端添加数字千位分割
    Failed to check/redeclare auto-delete queue(s)
    周末啦,做几道面试题放松放松吧!
    idea快捷键
    解决flink运行过程中报错Could not allocate enough slots within timeout of 300000 ms to run the job. Please make sure that the cluster has enough resources.
    用.net平台实现websocket server
    MQTT实战3
    Oracle 查看当前用户下库里所有的表、存储过程、触发器、视图
    idea从svn拉取项目不识别svn
  • 原文地址:https://www.cnblogs.com/carsonzhu/p/6864336.html
Copyright © 2011-2022 走看看