从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之九 || 依赖注入IoC学习 + AOP界面编程初探

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 更新 1、感谢@dongfo博友的提醒,目前是vue-cli脚手架是3.0.1,vue的版本还是2.5.17,下文已改,感谢纠错! 2、代码已经同步到码云https://gitee.com/laozhangIsPhi/Blog.

更新

1、感谢@dongfo博友的提醒,目前是vue-cli脚手架是3.0.1,vue的版本还是2.5.17,下文已改,感谢纠错!

2、代码已经同步到码云https://gitee.com/laozhangIsPhi/Blog.Core,欢迎纠错

 

代码已上传Github+Gitee,文末有地址

  说接上文,上回说到了《从壹开始前后端分离【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之八 || API项目整体搭建 6.3 异步泛型+依赖注入初探》,后来的标题中,我把仓储两个字给去掉了,因为好像大家对这个模式很有不同的看法,嗯~可能还是我学艺不精,没有说到其中的好处,现在在学DDD领域驱动设计相关资料,有了好的灵感再给大家分享吧。

  到目前为止我们的项目已经有了基本的雏形,后端其实已经可以搭建自己的接口列表了,框架已经有了规模,原本应该说vue了,但是呢,又听说近来Vue-cli已经从2.0升级到了3.0了,还变化挺大,前端大佬们,都不停歇呀。当然我还在学习当中,我也需要了解下有关3.0的特性,希望给没有接触到,或者刚刚接触到的朋友们,有一些帮助,当然我这个不是随波逐流,只是在众多的博文中,给大家一个入门参考,届时说3.0的时候,还是会说2.0的相关问题的。

  虽然项目整体可以运行了,但是我还有几个小知识点要说下,主要是1、依赖注入和AOP相关知识;2、跨域代理等问题(因为Vue是基于Node开发的,与后台API接口不在同一个地址);3、实体类的DTO相关小问题;4、Redis缓存等;5、部署服务器中的各种坑;虽然都是很小的知识点,我还是都下给大家说下的,好啦,开始今天的讲解;

零、今天完成的绿色部分

 

 

一、依赖注入的理解和思考

说到依赖,我就想到了网上有一个例子,依赖注入和工厂模式中的相似和不同:

(1)原始社会里,没有社会分工。须要斧子的人(调用者)仅仅能自己去磨一把斧子(被调用者)。相应的情形为:Java程序里的调用者自己创建被调用者。

(2)进入工业社会,工厂出现。斧子不再由普通人完毕,而在工厂里被生产出来,此时须要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。相应Java程序的简单工厂的设计模式。

(3)进入“按需分配”社会,须要斧子的人不须要找到工厂,坐在家里发出一个简单指令:须要斧子。斧子就自然出如今他面前。相应Spring的依赖注入

在上篇文章中,我们已经了解到了,什么是依赖倒置、控制反转(IOC),什么是依赖注入(DI),网上这个有很多很多的讲解,我这里就不说明了,其实主要是见到这样的,就是存在依赖

public class A : D
{

    public A(B b)
    {
        // do something   
    }
    C c = new C();
}

 

就比如我们的项目中的BlogController,只要是通过new 实例化的,都是存在依赖

public async Task<List<Advertisement>> Get(int id)
{
    IAdvertisementServices advertisementServices = new AdvertisementServices();
    return await advertisementServices.Query(d => d.Id == id);
}

 

使用依赖注入呢,有以下优点:

  • 传统的代码,每个对象负责管理与自己需要依赖的对象,导致如果需要切换依赖对象的实现类时,需要修改多处地方。同时,过度耦合也使得对象难以进行单元测试。
  • 依赖注入把对象的创造交给外部去管理,很好的解决了代码紧耦合(tight couple)的问题,是一种让代码实现松耦合(loose couple)的机制。
  • 松耦合让代码更具灵活性,能更好地应对需求变动,以及方便单元测试。

举个栗子,就是关于日志记录的

日志记录:有时需要调试分析,需要记录日志信息,这时可以采用输出到控制台、文件、数据库、远程服务器等;假设最初采用输出到控制台,直接在程序中实例化ILogger logger = new ConsoleLogger(),但有时又需要输出到别的文件中,也许关闭日志输出,就需要更改程序,把ConsoleLogger改成FileLogger或者NoLogger, new FileLogger()或者new SqlLogger() ,此时不断的更改代码,就显得心里不好了,如果采用依赖注入,就显得特别舒畅。

我有一个个人的理解,不知道恰当与否,比如我们平时食堂吃饭,都是食堂自己炒的菜,就算是配方都一样,控制者(厨师)还是会有些变化,或者是出错,但是肯德基这种,由总店提供的,店面就失去了控制,就出现了第三方(总店或者工厂等),这就是实现了控制反转,我们只需要说一句,奥尔良鸡翅,嗯就拿出来一个,而且全部分店的都一样,我们不用管是否改配方,不管是否依赖哪些调理食材,哈哈。

二、常见的IoC框架有哪些

Autofac:貌似目前net下用的最多吧
Ninject:目前好像没多少人用了
Unity:也是较为常见

其实.Net Core 有自己的轻量级的IoC框架,

ASP.NET Core本身已经集成了一个轻量级的IOC容器,开发者只需要定义好接口后,在Startup.cs的ConfigureServices方法里使用对应生命周期的绑定方法即可,常见方法如下

services.AddTransient<IApplicationService,ApplicationService>//服务在每次请求时被创建,它最好被用于轻量级无状态服务(如我们的Repository和ApplicationService服务)

services.AddScoped<IApplicationService,ApplicationService>//服务在每次请求时被创建,生命周期横贯整次请求

services.AddSingleton<IApplicationService,ApplicationService>//Singleton(单例) 服务在第一次请求时被创建(或者当我们在ConfigureServices中指定创建某一实例并运行方法),其后的每次请求将沿用已创建服务。如果开发者的应用需要单例服务情景,请设计成允许服务容器来对服务生命周期进行操作,而不是手动实现单例设计模式然后由开发者在自定义类中进行操作。

 当然.Net Core自身的容器还是比较简单,如果想要更多的功能和扩展,还是需要使用上边上个框架。

 

三、较好用的IoC框架使用——Autofac

首先呢,我们要明白,我们注入是要注入到哪里——Controller API层。然后呢,我们看到了在接口调用的时候,如果需要其中的方法,需要using两个命名空间

     [HttpGet("{id}", Name = "Get")]
        public async Task<List<Advertisement>> Get(int id)
        {
            IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用两个命名空间Blog.Core.IServices;Blog.Core.Services;

            return await advertisementServices.Query(d => d.Id == id);
        }

 

接下来我们就需要做处理:

 

1、在Nuget中引入两个:Autofac.Extras.DynamicProxy(Autofac的动态代理,它依赖Autofac,所以可以不用单独引入Autofac)、Autofac.Extensions.DependencyInjection(Autofac的扩展)

 

 

2、让Autofac接管Starup中的ConfigureServices方法,记得修改返回类型IServiceProvider

 

     public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            #region 配置信息
            //Blog.Core.Repository.BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:SqlServerConnection").Value;
            #endregion

            #region Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info
                {
                    Version = "v0.1.0",
                    Title = "Blog.Core API",
                    Description = "框架说明文档",
                    TermsOfService = "None",
                    Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" }
                });

                //就是这里

                #region 读取xml信息
                var basePath = PlatformServices.Default.Application.ApplicationBasePath;
                var xmlPath = Path.Combine(basePath, "Blog.Core.xml");//这个就是刚刚配置的xml文件名
                var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");//这个就是Model层的xml文件名
                c.IncludeXmlComments(xmlPath, true);//默认的第二个参数是false,这个是controller的注释,记得修改
                c.IncludeXmlComments(xmlModelPath);
                #endregion

                #region Token绑定到ConfigureServices
                //添加header验证信息
                //c.OperationFilter<SwaggerHeader>();
                var security = new Dictionary<string, IEnumerable<string>> { { "Blog.Core", new string[] { } }, };
                c.AddSecurityRequirement(security);
                //方案名称“Blog.Core”可自定义,上下一致即可
                c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入{token}\"",
                    Name = "Authorization",//jwt默认的参数名称
                    In = "header",//jwt默认存放Authorization信息的位置(请求头中)
                    Type = "apiKey"
                }); 
                #endregion


            });
            #endregion

            #region Token服务注册
            services.AddSingleton<IMemoryCache>(factory =>
             {
                 var cache = new MemoryCache(new MemoryCacheOptions());
                 return cache;
             });
            services.AddAuthorization(options =>
            {
                options.AddPolicy("Admin", policy => policy.RequireClaim("AdminType").Build());//注册权限管理,可以自定义多个
            });
            #endregion

            #region AutoFac
            
            //实例化 AutoFac  容器   
            var builder = new ContainerBuilder();

            //注册要通过反射创建的组件
            builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();

            //将services填充到Autofac容器生成器中
            builder.Populate(services);

            //使用已进行的组件登记创建新容器
            var ApplicationContainer = builder.Build();

            #endregion

            return new AutofacServiceProvider(ApplicationContainer);//第三方IOC接管 core内置DI容器
        }

 

这个时候我们就把AdvertisementServices的new 实例化过程注入到了Autofac容器中

 

3、通过依赖注入的三种方式(构造方法注入、setter方法注入和接口方式注入)中的构造函数方式实现注入

在BlogController中,添加构造函数,并在Get方法中,去掉实例化过程;

      IAdvertisementServices advertisementServices;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="advertisementServices"></param>
        public BlogController(IAdvertisementServices advertisementServices)
        {
            this.advertisementServices = advertisementServices;
        }

    [HttpGet(
"{id}", Name = "Get")] public async Task<List<Advertisement>> Get(int id) { //IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用两个命名空间Blog.Core.IServices;Blog.Core.Services; return await advertisementServices.Query(d => d.Id == id); }

 

 

4、然后运行调试,发现在断点刚进入的时候,接口已经被实例化了,达到了注入的目的。

 

5、这个时候,我们发现已经成功的注入了,Advertisement实体类到接口中,但是项目中有那么多的类,都要一个个手动添加么,答案当然不是滴~

 

四、通过反射将Blog.Core.Services和Blog.Core.Repository两个程序集的全部方法注入

修改如下代码,注意这个时候需要在项目依赖中,右键,添加引用Blog.Core.Services到项目,或者把dll文件拷贝到Blog.Core的bin文件夹中,否则反射会找不到。

       var builder = new ContainerBuilder();

            //注册要通过反射创建的组件
            //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();

            var assemblysServices = Assembly.Load("Blog.Core.Services");
            builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。
            var assemblysRepository = Assembly.Load("Blog.Core.Repository");
            builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces();

            //将services填充到Autofac容器生成器中
            builder.Populate(services);

            //使用已进行的组件登记创建新容器
            var ApplicationContainer = builder.Build();

 

其他不变,运行项目,一切正常,换其他接口也可以

 

 


 

到这里,Autofac依赖注入已经完成,基本的操作就是这样,别忙!现在还没有真正的完成哟!现在只是把Service和API层解耦了,Service和Repository还没有!

 

 

注意:文中和Git代码中,因为为了说明的方便,没有把Api层的Service 层 和 Repository 层给去掉,大家手动去掉,我已经更新到git上了。

1、最终的效果是这样的:工程只是依赖接口层

2、把service.dll 和 Repository.dll 两个文件拷贝到项目 的 bin \ debug \netcoreapp2.1 下

3、然后在Autofac依赖注入的时候,出现加载程序集失败的情况,可以修改如下:

        var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//获取项目路径
            var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");//获取注入项目绝对路径
            var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加载文件的方法

 

 

还记得Blog.Core.Services中的BaseServices.cs么,它还是通过new 实例化的方式在创建,仿照contrller,修改BaseServices并在全部子类的构造函数中注入:

   public class BaseServices<TEntity> : IBaseServices<TEntity> where TEntity : class, new()
    {
        //public IBaseRepository<TEntity> baseDal = new BaseRepository<TEntity>();
        public IBaseRepository<TEntity> baseDal;//通过在子类的构造函数中注入,这里是基类,不用构造函数
      //...  
   }


    public class AdvertisementServices : BaseServices<Advertisement>, IAdvertisementServices
    {
        IAdvertisementRepository dal;
        public AdvertisementServices(IAdvertisementRepository dal)
        {
            this.dal = dal;
            base.baseDal = dal;
        }       
    }

 

 

好啦,现在整个项目已经完成了相互直接解耦的功能,以后就算是Repository和Service如何变化,接口层都不用修改,因为已经完成了注入,第三方Autofac会做实例化的过程。

 

 

五、 当然,你也可以直接将一个类进行注入,而不一定要继承接口的方式;

//1. 定义一个服务,包含一个方法
public class PeopleService
{
    public string Run(string m) { return m; }
}
//2. 写一个扩展方法用来注入服务,与直接在ConfigureServices返回是一个道理
namespace Haos.Develop.CoreTest
{
    public static class Extension
    {
        public static IServiceCollection AddTestService(this IServiceCollection service)
        {
            return service.AddScoped(factory => new PeopleService());
        }
    }
}

//3. 回到Startup类中找到ConfigureServices方法添加如下代码
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(); 
services.AddTestService();//将上边的方法注入 }
//4.我们可以采用构造函数方式来注入和直接获取 public PeopleService People; public HomeController(PeopleService people)//构造函数 { People = people; } public JsonResult Test() { People.Run("111");
return Json(""); }

上边这个方法采用的是单独注册一个扩展方法来注入服务,和我们的直接return是一样的,感兴趣的可以试一试。

 同时可以参考这个网友的:

https://www.cnblogs.com/stulzq/p/6880394.html

 

六、简单了解通过AOP切面实现日志记录

什么是AOP?引用百度百科:AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。实现AOP主要由两种方式,

一种是编译时静态植入,优点是效率高,缺点是缺乏灵活性,.net下postsharp为代表者(好像是付费了。。)。

另一种方式是动态代理,优点是灵活性强,但是会影响部分效率,动态为目标类型创建代理,通过代理调用实现拦截。

AOP能做什么,常见的用例是事务处理、日志记录等等。

常见的AOP都是配合在Ioc的基础上进行操作,上边咱们讲了Autofac这个简单强大的Ioc框架,下面就讲讲Autofac怎么实现AOP。Autofac的AOP是通过Castle(也是一个容器)项目的核心部分实现的,名为Autofac.Extras.DynamicProxy,顾名思义,其实现方式为动态代理。当然AOP并不一定要和依赖注入在一起使用,自身也可以单独使用。

网上有一个博友的图片,大概讲了AOP切面编程

 

 

 七、结语

  今天的文章呢,主要说了依赖注入IoC在项目中的使用,从上边的说明中,可以看到,最大的一个优点就是实现解耦的作用,最明显的就是,在Controller中,不用在实例服务层或者业务逻辑层了,不过还是有些缺点的,缺点之一就是会占用一定的时间资源,效率稍稍受影响,不过和整体框架相比,这个影响应该也是值得的。

  明天,我们继续将面向切面编程AOP中的,日志记录和面向AOP的缓存使用。

 

八、CODE

 https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core

 

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
12天前
|
Java 物联网 C#
C#/.NET/.NET Core学习路线集合,学习不迷路!
C#/.NET/.NET Core学习路线集合,学习不迷路!
|
2月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
64 1
|
23天前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
65 1
什么是AOP面向切面编程?怎么简单理解?
|
28天前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
56 5
|
18天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
31 0
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
134 9
|
1月前
|
开发框架 缓存 算法
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
|
1月前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
43 0
|
1月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
48 0
|
2月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
48 13
下一篇
无影云桌面