• IOC框架Ninject实践总结


    一、控制反转和依赖注入

    Ninject是一个轻量级的基于.Net平台的依赖注入(IOC)框架。所谓的IOC,即控制反转(Inversion of Control),它是一个经典的面向对象编程法则,它的作用主要是用来帮助应用程序解耦,并把程序分离成一个个松耦合高内聚的模块。控制反转还有一个名字叫依赖注入(Dependency Injection),简称DI。

    二、快速无xml配置注入

    1、定义应用程序Module

    ServiceModuleusing LogService;
    using LogService.Impl;
    using Ninject.Modules;
    using NinjectApp.Warrior;
    using NinjectApp.Weapon;
    
    namespace NinjectApp
    {
        internal class ServiceModule : NinjectModule
        {
            public override void Load()
            {
                Bind<ILogService>().To<DbLogService>();
    
                Bind<IWeapon>().To<Sword>().InSingletonScope();
                //Bind<IWeapon>().To<Shuriken>();
    
                Bind<Shuriken>().ToSelf().WhenInjectedInto<IWeapon>();
    
                Bind<IWarrior>().To<FootSoldier>();
                //Bind<IWarrior>().To<Samurai>();
    
            }
        }
    }

    2、手动调用服务

    InjectManual        /// <summary>
            /// 手动注入
            /// </summary>
            static void InjectManual()
            {
                using (var kernel = new StandardKernel(module))
                {
                    var dbLogger = kernel.Get<ILogService>();
                    dbLogger.AppendLog("hello world");
    
                    var weapon = kernel.Get<IWeapon>();
                    Console.WriteLine(weapon.GetType());
                    Console.WriteLine(weapon.Name);
    
                    //weapon = kernel.Get<Shuriken>();
                    //Console.WriteLine(weapon.GetType());
                    //Console.WriteLine(weapon.Name);
    
                    var weapon1 = kernel.Get<IWeapon>();
                    Console.WriteLine(weapon1.GetType());
                    Console.WriteLine(weapon1.Name);
    
                    Console.WriteLine(object.ReferenceEquals(weapon, weapon1));
    
                    var warrior = kernel.Get<IWarrior>();
                    Console.WriteLine(warrior.GetType());
                }
    
            }
    
    

    注:Ninject的绑定对象作用域有多种,本文的demo中有具体的单元测试,具体可以直接查看源码或者参考官方文档。。

    三、配置文件注入

    通过Ninject的xml扩展,可以实现传统的类似于Spring.net、Unity等IOC容器的注入方式。

    1、配置文件

    复制代码
    <?xml version="1.0" encoding="utf-8" ?>
    <module name="ServiceModule">
      <bind name="Txtlog" service="LogService.ILogService,LogService" to="LogService.Impl.TxtLogService,LogService"/>
      <!--<bind name="Dblog" service="LogService.ILogService,LogService" to="LogService.Impl.DbLogService,LogService"/>-->
      <bind name="Sword" service="NinjectApp.Weapon.IWeapon,NinjectApp" to="NinjectApp.Weapon.Sword,NinjectApp"/>
      <bind name="FootSoldier" service="NinjectApp.Warrior.IWarrior,NinjectApp" to="NinjectApp.Warrior.FootSoldier,NinjectApp"/>
    </module>
    
     
    复制代码

    2、利用扩展加载服务

    XmlServiceModuleusing System.Collections.Generic;
    using System.Xml.Linq;
    using Ninject;
    using Ninject.Extensions.Xml;
    using Ninject.Extensions.Xml.Handlers;
    
    namespace NinjectApp
    {
        public class XmlModuleContext
        {
            protected readonly IKernel kernel;
            protected readonly IDictionary<string, IXmlElementHandler> elementHandlers;
    
            public XmlModuleContext()
            {
                kernel = new StandardKernel();
                elementHandlers = new Dictionary<string, IXmlElementHandler> { { "bind", new BindElementHandler(kernel) } };
            }
        }
    
        public class XmlServiceModule : XmlModuleContext
        {
            private static readonly XmlServiceModule instance = new XmlServiceModule();
    
            protected readonly XmlModule module = null;
    
            public XmlServiceModule()
            {
                var document = XDocument.Load("Config/NinjectServiceModule.config");
                module = new XmlModule(document.Element("module"), elementHandlers);
                module.OnLoad(kernel);
            }
    
            public static IKernel GetKernel()
            {
                return instance.kernel;
            }
        }
    }

    3、调用服务

    InjectByConfig      /// <summary>
            /// 通过xml配置注入
            /// </summary>
            static void InjectByConfig()
            {
                var kernel = XmlServiceModule.GetKernel();
                var logger = kernel.Get<ILogService>();
                Console.WriteLine(logger.GetType());
                logger.AppendLog("hello world");
               
                var weapon = kernel.Get<IWeapon>();
                Console.WriteLine(weapon.GetType());
                Console.WriteLine(weapon.Name);
    
                var warrior = kernel.Get<IWarrior>();
                Console.WriteLine(warrior.GetType());
            }
    

    虽然配置注入看上去更容易扩展应对外部变化,但是项目庞大臃肿之后,配置文件并不好管理。固然有一些可视化的工具,但是仍然容易出现偏差。Ninject最擅长的基本注入功能就是无配置简单快速注入,达到free yourself from xml的目的,对于一般的中小型应用程序完全可以零配置。

    四、MVC项目的依赖注入

    通过Ninject的MVC扩展可以轻松实现MVC项目的依赖注入。

    1、NinjectHttpApplication

    在Global.asax.cs文件里重新定义MvcApplication继承自NinjectHttpApplication,重写OnApplicationStarted事件和CreateKernel方法:

    MvcApplication
            protected override void OnApplicationStarted()
            {
                base.OnApplicationStarted();
                AreaRegistration.RegisterAllAreas();
                RegisterGlobalFilters(GlobalFilters.Filters);
                RegisterRoutes(RouteTable.Routes);
            }
    
            protected override IKernel CreateKernel()
            {
                var kernel = new StandardKernel();
                kernel.Bind<IUserService>().To<UserInfoService>();
                kernel.Bind<ILogService>().To<TxtLogService>();
                //kernel.Bind<ILogService>().To<TxtLogService>().InSingletonScope();//单例
                return kernel;
            }
    

    2、通过构造函数或者属性或者Module实现注入

    (1)、Controller实现注入

    a、构造函数注入

    Account/Index        private readonly IUserService userService = null;
    
            public AccountController(IUserService userService)
            {
                this.userService = userService;
                //var userInfo = userService.GetCurrentUser();//do sth
            }
    

    b、属性注入

    定义属性,加上Inject特性即可实现注入。

           [Inject]
            public IUserService CurrentUserService { get; set; }
    
    

    c、module注入

    定义Module:

    MvcServiceModuleusing LogService;
    using LogService.Impl;
    using Ninject.Modules;
    using UserService;
    using UserService.Impl;
    
    namespace MVCApp.Helper
    {
        internal class ServiceModule : NinjectModule
        {
            public override void Load()
            {
                Bind<ILogService>().To<TxtLogService>();
                Bind<IUserService>().To<UserInfoService>();
            }
        }
    }
    

    接着调用即可:

    MvcServiceModule   using (var kernel = new StandardKernel(new ServiceModule()))
                    {
                        var logger = kernel.Get<ILogService>();
                    }
    

    (2)、自定义Attribute实现注入

    ExceptionHandleAttributeusing System.Web.Mvc;
    using LogService;
    using Ninject;
    
    namespace MVCApp.Helper
    {
        /// <summary>
        /// 异常处理特性 
        /// </summary>
        public class ExceptionHandleAttribute : HandleErrorAttribute
        {
            [Inject]
            public ILogService Logger { get; set; }
    
            public override void OnException(ExceptionContext filterContext)
            {
                Logger.AppendLog(filterContext.Exception.ToString());//记录日志
            }
        }
    }
    

    和Controller非常相似,示例使用属性加上Inject特性的方式实现注入,其他注入方式略过。

    到这里,你应该已经可以看到,这可以算是web应用程序中非常干净利落的注入方式,简单的令人发指。

    五、组合还是继承

    通过IOC框架实现服务依赖注入本来不难,但是这里或多或少会牵扯到一个问题:注入服务调用是使用组合还是继承?

    举例来说,最基础的用户服务(或者日志服务),一般的web应用程序几乎每个控制器(或者页面)都或多或少和用户有关系。

    问题来了,不同的控制器或者页面要调用用户服务该怎么做?

    下面以MVC项目为例来说明一下通常的注入方法。

    如你所知,通常的做法,所谓组合优于继承(继承被认为是一种强耦合),我们只要在需要调用服务的控制器中定义一个服务变量或者属性,通过构造函数注入,类似下面这样:

    controllerconstructor      private readonly IUserService userService = null;
    
            public AccountController(IUserService userService)
            {
                this.userService = userService;
            }
    

    然后在对应的Action中就可以调用用户服务了。

    如果你的Controller很少,这种方式当然可以接受。但是,实际项目中控制器真的比较多的时候,有一些几乎每个控制器必然用到的公共服务,我们是不是不得不哼哧哼哧写很多构造函数实现依赖注入呢?

    到这里,你一定想到,是啊,都调用一样的服务,几乎都类似的代码,重构吧!

    最简单的方式,利用继承,集中在一个地方(通常就叫BaseController吧)写一次,

    BaseControllerusing System.Web.Mvc;
    using LogService;
    using Ninject;
    using UserService;
    using UserService.Model;
    
    namespace MVCApp.Helper
    {
        [ExceptionHandle]
        public class BaseController : Controller
        {
            /// <summary>
            /// 构造函数
            /// </summary>
            public BaseController()
            {
                if (CurrentLogService==null)
                {
                    using (var kernel = new StandardKernel(new ServiceModule()))
                    {
                        var logger = kernel.Get<ILogService>();
                        logger.AppendLog("As you see,in constructor log service is not initilized by inject attribute.");
                    }
                }
            }
    
            protected UserInfo CurrentUser
            {
                get { return CurrentUserService.CurrentUser; }
            }
    
            #region 服务
    
            [Inject]
            public IUserService CurrentUserService { get; set; }
    
            [Inject]
            public ILogService CurrentLogService { get; set; }
    
            #endregion
        }
    }
    
    

    然后,在相应的Controller下this点服务属性名调用一下,多么优雅干净简洁。但是这种方式有一点需要特别需要注意,在Controller的构造函数里调用服务初始化一些数据可能不能让你那么随心所欲,因为在构造函数内,服务还没有初始化。

    如上代码所示,通过Inject特性实现服务注入,通过继承实现公共服务调用,不管哪种表现形式的应用程序都可以使用,有AOP和继承的世界看上去是多么美好啊。当然了,具体使用哪种方式好每个人肯定都有自己的看法,实际项目中,我们通常选择组合和继承相结合的方式,这样就可以兼顾两者的优点实现注入。

    最后还有几个困扰人的问题需要思考:如何划分服务?服务和服务之间是否应该依赖注入?如果仅仅是在表现层实现依赖注入,难道不觉得IOC的作用有点太酱油了吗?

    参考:http://www.ninject.org/wiki.html

    https://github.com/ninject/

    http://ninject.codeplex.com/

    http://www.cnblogs.com/cnmaxu/archive/2011/10/25/2224375.html

    http://martinfowler.com/articles/injection.html

  • 相关阅读:
    zw字王《中华大字库》2018版升级项目正式启动
    字王大藏经体v0.1概念版
    zw-clay字王胶泥体系列
    中文字库的造与创
    【转】Delphi+Halcon实战一:两行代码识别QR二维码
    zw.delphi不同版本程序运行速度测试
    《zw版·Halcon入门教程与内置demo》
    字王·百字工程·第一阶段纪念
    黑天鹅算法与大数据的四个层次
    zw版足彩大数据&报价
  • 原文地址:https://www.cnblogs.com/xlhblogs/p/3322730.html
Copyright © 2020-2023  润新知