(给DotNet加星标,提升.Net技能)

转自:在7楼

cnblogs.com/RayWang/p/11128554.html

简介

该系列共5篇文章,旨在以实战模式,在.NET下的

  • 控制台程序

  • Framework Mvc程序

  • Framework WebApi程序

  • Core Api程序

分别实现依赖注入。

其中.NET  Framework框架主要以如何引入AutoFac作为容器以及如何运用AuotoFac为主,.NET Core框架除了研究引入AutoFac的两种方式,同时也运用反射技巧对其自带的DI框架进行了初步封装,实现了相同的依赖注入效果。

项目架构如下图:

GitHub源码地址:https://github.com/WangRui321/Ray.EssayNotes.AutoFac

适用对象

该项目主要实战为主,理论部分我会结合例子和代码,深入浅出地阐述,如果你是:

  • 从来没听过IoC、DI这些劳什子

  • 了解一些依赖注入的理论知识但是缺乏实战

  • 在.Net Framework下已熟练运用依赖注入,但在.NET Core还比较陌生

只要你花上半个小时认真读完每一句话,我有信心这篇文章一定会对你有所帮助。

如果你是:

  • 发量比我还少的秒天秒地的大牛

那么也欢迎阅读,虽然可能对你帮助并不大,但是欢迎提供宝贵的意见,有写的不好的地方可以互相交流~

理论基础

依赖

依赖,简单说就是,当一个类需要另一个类协作来完成工作的时候就产生了依赖。这也是耦合的一种形式。

举个例子,比如标准的三层架构模式

数据访问层(DAL)代码:

/// /// 学生仓储/// public class StudentRepository{public string GetName(long id){return "学生张三";//造个假数据返回    }}

业务层(BLL)代码:

/// /// 学生逻辑处理/// public class StudentService{private readonly StudentRepository _studentRepository;public StudentService(){        _studentRepository = new StudentRepository();    }public string GetStuName(long id){var stu = _studentRepository.Get(id);return stu.Name;    }}

其中,StudentService的实现,就必须要依赖于StudentRepository。而且这是一种紧耦合,一旦StudentRepository有任何更改,必然导致StudentService的代码同样也需要更改,这种情况是程序员们不愿意看到的。

接口驱动

接口驱动是为了实现一个设计原则:要依赖于抽象,而不是具体的实现。

还拿上面的例子说明,现在我添加一个DAL的接口层,IStudentRepository,抽象出所需方法:

/// /// 学生仓储interface/// public interface IStudentRepository{string GetName(long id);}

然后让StudentRepository去实现这个接口:

/// /// 学生仓储/// public class StudentRepository : IStudentRepository{public string GetName(long id){return "学生张三";//造个假数据返回    }}

然后在StudentService里只依赖于IStudentRepository,以后的增删改查都通过IStudentRepository这个抽象来做:

/// /// 学生逻辑处理/// public class StudentService{private readonly IStudentRepository _studentRepository;public StudentService(){       _studentRepository = new StudentRepository();    }public string GetStuName(long id){var stu = _studentRepository.Get(id);return stu.Name;    }}

这样做的好处有两个,一个是低耦合,一个是职责清晰。如果对此还有怀疑的话,我们可以想象一个情景,就是负责写StudentService的是程序员A,负责写StudentRepository的是另一个程序员B,那么:

  • 针对程序员A

我(程序员A)只需要关注业务逻辑层面,如果我需要从仓储层拿数据库的数据,比如我需要根据Id获取学生实体,那么我只需要去IStudentRepository找Get(long id)函数就可以了,至于实现它的仓储怎么实现这个方法我完全不用管,你怎么从数据库拿数据不是我该关心的事情。

  • 针对程序员B

我(程序员B)的工作就是实现IStudentRepository接口的所有方法就行了,简单而明确,至于谁来调用我,我不用管。IStudentRepository里有根据Id获取学生姓名的方法,我实现了就行,至于业务逻辑层拿这个名字干啥,那不是我要关心的事情。

这样看的话是不是彼此的职责就清晰多了,更进一步再举个极端的例子:

比如程序员B是个实习生,整天划水摸鱼,技术停留在上个世纪,结果他写的仓储层读取数据库全部用的手写sql语句的方式,极难维护,后来被领导发现领了盒饭,公司安排了另一个程序员C来重写仓储层,C这时不需要动其他代码,只需要新建一个仓储StudentNewRepository,然后实现之前的IStudentRepository,C使用Dapper或者EF,写完新的仓储层之后,剩下的只需要在StudentService里改一个地方就行了:

public StudentService(){    _studentRepository = new StudentNewRepository();}

是不是很清晰,耦合不会像以前那么重。

其实对于这个小例子来说,接口驱动的优势还不太明显,但是在系统层面优势就会被放大。比如上面换仓储的例子,虽然职责是清晰了,但是项目里有几个Service就需要改几个地方,还是很麻烦。

原因就是上面讲的,这是一种依赖关系,Service要依赖Repository,有没有一种方法可以让这种控制关系反转过来呢?当Service需要使用Repository,有没有办法让我需要的Repository自己注入到我这里来?

当然有,这就是我们将要实现的依赖注入。使用依赖注入后你会发现,当C写完新的仓储后,业务逻辑层(StudentService)是不需要改任何代码的,所有的Service都不需要一个一个去改,直接在注入的时候修改规则,不要注入以前老的直接注入新的仓储就可以了。

面向接口后的架构:

什么是IoC

IoC,全称Inversion of Control,即“控制反转”,是一种设计原则,最早由Martin Fowler提出,因为其理论提出时间和成熟时间相对较晚,所以并没有被包含在GoF的《设计模式》中。

什么是DI

DI,全称Dependency Injection,即依赖注入,是实现IoC的其中一种设计方法。

其特征是通过一些技巧,将依赖的对象注入到调用者当中。(比如把Repository注入到Service当中)

这里说的技巧目前主要指的就是引入容器,先把所有会产生依赖的对象统一添加到容器当中,比如StudentRepository和StudentService,把分配权限交给容器,当StudentService内部需要使用StudentRepository时,这时不应该让它自己new出来一个,而是通过容器,把StudentRepository注入到StudentService当中。

这就是名称“依赖注入”的由来。

DI和IoC有什么区别

这是个老生常谈的问题了,而且这两个名字经常在各种大牛和伪大牛的吹逼现场频繁出现 ,听的新手云里雾里,莫名感到神圣不可侵犯。那么DI和IoC是同一个东西吗?如果不是,它们又有什么区别呢?

回答很简单:不是一个东西。

区别也很简单,一句话概括就是:IoC是一种很宽泛的理念,DI是实现了IoC的其中一种方法。

说到这里我已经感觉到屏幕后的你性感地添了一下嘴唇,囤积好口水,准备开始喷我了。

先别慌,我有证据,我们先来看下微软怎么说:

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

地址:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2

翻译过来就是“ASP.NET Core支持依赖注入(DI)的软件设计模式,该模式是一种在类和它依赖的对象之间实现了控制反转(IoC)的技术”。

如果有人觉得辣鸡微软不够权威,那我们去看下IoC以及DI这两个概念的发明人——Martin Fowler怎么说:

几位轻量级容器的作者曾骄傲地对我说:这些容器非常有用,因为它们实现了控制反转。这样的说辞让我深感迷惑:控制反转是框架所共有的特征,如果仅仅因为使用了控制反转就认为这些轻量级容器与众不同,就好象在说我的轿车是与众不同的,因为它有四个轮子。

因此,我想我们需要给这个模式起一个更能说明其特点的名字——”控制反转”这个名字太泛了,常常让人有些迷惑。经与多位IoC 爱好者讨论之后,我们决定将这个模式叫做”依赖注入”(Dependency Injection)。

地址:http://insights.thoughtworkers.org/injection/

Martin Fowler说的比较委婉,其实说白了就是建议我们,不要乱用IoC装逼,IoC是一种设计理念,很宽泛,你把程序里的一个写死的变量改成从配置文件里读取也是一种控制反转(由程序控制反转为由框架控制),你把这个配置改成用户UI界面的一个输入文本框由用户输入也是一种控制反转(由框架控制反转为由用户自己控制)。

所以,如果确定讨论的模式是DI,那么就表述为DI,还是尽量少用IoC这种宽泛的表达。

AutoFac

AutoFac是一个开源的轻量级的DI容器,也是.net下最受大家欢迎的实现依赖注入的工具之一,通过AutoFac我们可以很方便的实现一些DI的骚操作。

实战控制台程序依赖注入

目标很简单,就是控制台程序启动后,将学生姓名打印出来。

程序启动流程是,控制台主程序调用Service层,Service层调用Repository层获取数据(示例项目的仓储层没有连接数据库,只是直接造个假数据返回)。

没有依赖注入的情况下,肯定是主程序会new一个StudentService,StudentService里会new一个StudentRepository,现在引入依赖注入后,就不应该这么new出来了,而是通过容器注入,也就是容器会把StudentRepository自动注入到StudentService当中。

架构

实体层

学生实体类StudentEntity:

namespace Ray.EssayNotes.AutoFac.Model{/// 学生实体public class StudentEntity    {/// 唯一标识public long Id { get; set; }/// 姓名public string Name { get; set; }/// 成绩public int Grade { get; set; }    }}

仓储层

IStudentRepository接口:

using Ray.EssayNotes.AutoFac.Model;namespace Ray.EssayNotes.AutoFac.Repository.IRepository{/// 学生仓储interfacepublic interface IStudentRepository    {string GetName(long id);    }}

StudentRepository仓储类:

using Ray.EssayNotes.AutoFac.Model;using Ray.EssayNotes.AutoFac.Repository.IRepository;

namespace Ray.EssayNotes.AutoFac.Repository.Repository{/// /// 学生仓储/// public class StudentRepository : IStudentRepository    {public string GetName(long id){return "学生张三";//造个假数据返回        }    }}

Service层

IStudentService接口

namespace Ray.EssayNotes.AutoFac.Service.IService{/// /// 学生逻辑处理interface/// public interface IStudentService    {string GetStuName(long id);    }}

StudentService类:

using Ray.EssayNotes.AutoFac.Repository.IRepository;using Ray.EssayNotes.AutoFac.Repository.Repository;using Ray.EssayNotes.AutoFac.Service.IService;namespace Ray.EssayNotes.AutoFac.Service.Service{/// /// 学生逻辑处理/// public class StudentService : IStudentService    {private readonly IStudentRepository _studentRepository;/// /// 构造注入/// /// public StudentService(IStudentRepository studentRepository){            _studentRepository = studentRepository;        }public string GetStuName(long id){var stu = _studentRepository.Get(id);return stu.Name;        }   }}

其中构造函数是一个有参的函数,参数是学生仓储,这个后面依赖注入时会用。

AutoFac容器

需要先通过Nuget导入Autofac包:

using System;using System.Reflection;//using Autofac;using Autofac.Core;//using Ray.EssayNotes.AutoFac.Repository.IRepository;using Ray.EssayNotes.AutoFac.Repository.Repository;using Ray.EssayNotes.AutoFac.Service.IService;using Ray.EssayNotes.AutoFac.Service.Service;

namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc{/// /// 控制台程序容器/// public static class Container    {/// /// 容器/// public static IContainer Instance;

/// /// 初始化容器/// /// public static void Init(){

//新建容器构建器,用于注册组件和服务var builder = new ContainerBuilder();//自定义注册            MyBuild(builder);//利用构建器创建容器            Instance = builder.Build();        }

/// /// 自定义注册/// /// public static void MyBuild(ContainerBuilder builder){            builder.RegisterType().As();            builder.RegisterType().As();        }    }}

其中:

  • public static IContainer Instance

    为单例容器

  • Init()方法

    用于初始化容器,即往容器中添加对象,我们把这个添加的过程称为注册(Register)。

    ContainerBuilder为AutoFac定义的容器构造器,我们通过使用它往容器内注册对象。

  • MyBuild(ContainerBuilder builder)方法

    我们具体注册的实现函数。RegisterType是AutoFac封装的一种最基本的注册方法,传入的泛型(StudentService)就是我们欲添加到容器的对象;As函数负责绑定注册对象的暴露类型,一般是以其实现的接口类型暴露,这个暴露类型是我们后面去容器内查找对象时使用的搜索标识,我们从容器外部只有通过暴露类型才能找到容器内的对象。

主程序

需要先Nuget导入AutoFac程序包:

using System;//using Autofac;//using Ray.EssayNotes.AutoFac.Infrastructure.Ioc;using Ray.EssayNotes.AutoFac.Service.IService;

namespace Ray.EssayNotes.AutoFac.ConsoleApp{class Program    {static void Main(string[] args){            Container.Init();//初始化容器,将需要用到的组件添加到容器中            PrintStudentName(10001);            Console.ReadKey();        }/// /// 输出学生姓名/// /// public static void PrintStudentName(long id){

//从容器中解析出对象            IStudentService stuService = Container.Instance.Resolve();string name = stuService.GetStuName(id);            Console.WriteLine(name);        }     } }

进入Main函数,先调用容器的初始化函数,该函数执行成功后,StudentRepository和StudentService就被注册到容器中了。

然后调用打印学生姓名的函数,其中Resolve()方法是AutoFac封装的容器的解析方法,传入的泛型就是之前注册时的暴露类型,下面可以详细看下这一步到底发生了哪些事情:

  • 容器根据暴露类型解析对象

也就是容器会根据暴露类型IStudentService去容器内部找到其对应类(即StudentService),找到后会试图实例化一个对象出来。

  • 实例化StudentService

AutoFac容器在解析StudentService的时候,会调用StudentService的构造函数进行实例化。

  • 构造注入

AutoFac容器发现StudentService的构造函数需要一个IStudnetRepository类型的参数,于是会自动去容器内寻找,根据这个暴露类型找到对应的StudnetRepository后,自动将其注入到了StudentService当中

经过这几步,一个简单的基于依赖注入的程序就完成了。

结果

我们将控制台程序设置为启动项目,点击运行,如图调用成功:

如果把调试断点加在容器初始化函数里,可以很清晰的看到哪些对象被注册到了容器里:

补充

使用控制台程序本来是为了突出容器的概念,但是容易造成一些误解,DI的最终形态可以参考源码里的Api项目和MVC项目,本来想循序渐进,先第一章控制台引入容器的概念,然后第二章讲批量注册、注入泛型、生命周期域管理,第三章讲Api和MVC项目,最后两章讲下.net core的DI,但是这里还是先说下吧:

误解1:每次添加Service和Repository都要去注册,不是更麻烦?

其实是不需要一个一个注册的,运用批量注册后容器内部的代码是这样的,可以直接批量注册所有的:

/// /// .net framework MVC程序容器/// public static class MvcContainer{public static IContainer Instance;

/// /// 初始化容器/// /// /// public static void Init(Func func = null){//新建容器构建器,用于注册组件和服务var builder = new ContainerBuilder();//注册组件        MyBuild(builder);         func?.Invoke(builder);//利用构建器创建容器        Instance = builder.Build();//将AutoFac设置为系统DI解析器        System.Web.Mvc.DependencyResolver.SetResolver(new AutofacDependencyResolver(Instance));    }public static void MyBuild(ContainerBuilder builder){        Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb();//批量注册所有仓储 && Service        builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类(concrete classes)            .Where(cc => cc.Name.EndsWith("Repository") |//筛选                         cc.Name.EndsWith("Service"))            .PublicOnly()//只要public访问权限的            .Where(cc => cc.IsClass)//只要class型(主要为了排除值和interface类型)            .AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)//注册泛型仓储      builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));

//注册Controller        Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkMvc"));        builder.RegisterControllers(mvcAssembly);    }}

误解2:每次使用都要解析下,还不如直接new

好吧,其实也是不需要自己去解析的,最终形态的Controller入口是这样的,直接在构造函数里写就行了:

public class StudentController : Controller{private readonly IStudentService _studentService;public StudentController(IStudentService studentService){        _studentService = studentService;    }/// /// 获取学生姓名/// /// /// public string GetStuNameById(long id){return _studentService.GetStuName(id);    }}

就是直接在构造函数里注入就可以了。

误解3:依赖注入是不是过度设计?

首先DI是一个设计模式(design pattern),其本身完全不存在过不过度的问题,这完全取决于用的人和怎么用。

另外,在.NET Core中,DI被提到了一个很重要的地位,如果想要了解.NET Core,理解DI是必不可少的。

推荐阅读

(点击标题可跳转阅读)

ASP.NET Core系列之Dependency injection(依赖注入)

.NET中扩展方法和Enumerable

.NET 架构开发 应知应会

看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能

好文章,我在看❤️

service 层注入不同的数据源_.NET 理论基础+实战控制台程序实现AutoFac注入相关推荐

  1. 【半小时大话.net依赖注入】(一)理论基础+实战控制台程序实现AutoFac注入

    第一章|理论基础+实战控制台程序实现AutoFac注入 第二章|AutoFac的常见使用套路 第三章|实战Asp.Net Framework Web程序实现AutoFac注入 第四章|实战Asp.Ne ...

  2. service层加需要加锁吗_面试官:了解乐观锁和悲观锁吗?

    为什么会使用到数据库级别的锁? 你可能会有这么一个疑问:现在的程序已经提供了很完善的锁机制来保证多线程的安全问题,还需要用到数据库级别的锁吗?我觉得还是需要的,为什么呢?理由很简单,我们再编程中使用的 ...

  3. mysql注入实例获取答案_本文实例讲述了MySQL解决SQL注入的另类方法。分享给大家供大家参考,具体如下:问题解读我觉得,这个问题每年带来的成本可以高达数十亿美元了。本文就来谈谈,...

    本文实例讲述了MySQL解决SQL注入的另类方法.分享给大家供大家参考,具体如下: 问题解读 我觉得,这个问题每年带来的成本可以高达数十亿美元了.本文就来谈谈,假定我们有如下 SQL 模板语句: se ...

  4. SpringCloud学习笔记027---SpringBoot集成MyBatis_实现多数据源_可以自定义数据库类型

    JAVA技术交流QQ群:170933152 1.可以静态配置数据库 2.也可以动态切换数据库 项目提交测试,趁着中当间的这个空档期,把springboot的多数据源配置学习一下,总体来说多数据源配置有 ...

  5. JAVA中Action层, Service层 ,model层 和 Dao层的功能区分

    首先这是现在最基本的分层方式,结合了SSH架构.model层就是对应的数据库表的实体类.Dao层是使用了Hibernate连接数据库.操作数据库(增删改查).Service层:引用对应的Dao数据库操 ...

  6. Service层控制事务

    1.Service业务逻辑层: 1.业务概述: 代表用户完成的一个业务功能,可以由一个或多个DAO的调用组成.(软件所提供的一个功能都叫业务) 2.Service开发流程 2.事务: 在JDBC中,在 ...

  7. 渗透测试-SQL注入之sqlmap的使用方法及实战案例

    SQL注入之sqlmap的使用方法及实战案例 文章目录 SQL注入之sqlmap的使用方法及实战案例 前言 一.sqlmap的使用方法 查库,表,列,以及相关的字段的信息 万能密码汇总 判断是否为字符 ...

  8. service 层 拼接的html 代码如何直接返回_软件系统的分层,有效降低层与层之间的依赖...

    在分解复杂的软件系统时,架构师和程序员用得最多的技术之一就是分层.个人学习开发的时候,软件系统大多数是三层架构,也就是大家非常熟悉的表现层.领域层(业务层).数据源层.随着互联网的发展,智能手机普及手 ...

  9. Spring Boot神操作-多个数据源Service层封装

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:@pdai pdai.tech/md/spring/spri ...

最新文章

  1. Android 之 Fagment 完全解析
  2. cpu高 thread vm_阿里大佬总结,Java高并发必读!
  3. Office Web Apps 2013 修改Excel在线查看文件大小限制
  4. 技能拓展笔记-React(一)
  5. 云服务器安全组配置(阿里云,ucloud云,华为云)
  6. 修改注册表解决SVN状态图标不显示问题
  7. PoEdu - Windows阶段班 【Po学校】Windows编程 Lesson004_003-2 文件操作
  8. 一款好看透明个人主页源码
  9. 一张图知道优秀项目经理应该具备的能力
  10. anchor和正负样本
  11. 软件集合贴:学软件测试必备软件,看这里足足够了
  12. 使用PS(Adobe Photoshop CC)设计Banner
  13. 51单片机-4G模块
  14. 《C Primer Plus》5.11 编程练习
  15. 机器学习之性能度量指标——决定系数R^2、PR曲线、ROC曲线、AUC值、以及准确率、查全率、召回率、f1_score
  16. datagrip mysql 驱动_Datagrip2020下载MySQL驱动失败的问题
  17. 计算机辅助设计2007试题,计算机辅助设计(AutoCAD平台)AutoCAD 2007试题汇编
  18. C# .NET 16进制转换,10进制转换,8进制转换,2进制转换
  19. 港科百创 | 极目生物宣布引进FDA批准干眼治疗器械大中华区、韩国及东盟十国独家权益...
  20. 14.Bootstrap

热门文章

  1. springboot Serving Web Content with Spring MVC
  2. Swift - 文本输入框内容改变时响应,并获取最新内容
  3. 通用类别目录Global Catalog[为企业维护windows server 2008系列五]
  4. 2007年度最具投资价值100强网站揭晓——博客园榜上有名
  5. 第九章 思科竞争谋略
  6. nyoj66分数拆分
  7. LAMP搭建Discuz论坛
  8. mysql create table 语法详解
  9. mormot orm rest注意事项
  10. Redis常用命令入门5:有序集合类型