一、依赖注入的类型注册

ABP的依赖注入系统是基于Microsoft的依赖注入扩展库(Microsoft.Extensions.DependencyInjection nuget包)开发的.因此,它的文档在ABP中也是有效的.

也就是说我们在ABP中要想向IOC容器中注入类有两种方式:

一是可以使用.netcore自带的注入方法

public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//在此处注入依赖项
context.Services.AddTransient<IMyCurrentUser, MyCurrentUser>();
}
}

二是依照ABP约定的规则注册

1、依赖接口

如果实现这些接口,则会自动将类注册到依赖注入:

  • ITransientDependency 注册为transient生命周期.
  • ISingletonDependency 注册为singleton生命周期.
  • IScopedDependency 注册为scoped生命周期.
    public class MyClass: ITransientDependency
    {
    }

    MyClass因为实现了ITransientDependency,所以它会自动注册为transient生命周期.同理,其它的也是一样

2、Dependency 特性

我们也可以给某个类打上特性标签的方法来确定要注入的类

配置依赖注入服务的另一种方法是使用DependencyAttribute.它具有以下属性:

  • Lifetime: 注册的生命周期:Singleton,Transient或Scoped.
  • TryRegister: 设置true则只注册以前未注册的服务.使用IServiceCollection的TryAdd ... 扩展方法.
  • ReplaceServices: 设置true则替换之前已经注册过的服务.使用IServiceCollection的Replace扩展方法.

示例:

[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
public class MyClass
{ }

如果定义了Lifetime属性,则Dependency特性具有比其他依赖接口更高的优先级.

3、ExposeServices 特性

ExposeServicesAttribute用于控制相关类提供了什么服务.例:
[ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
{ }

TaxCalculator类只公开ITaxCalculator接口.这意味着你只能注入ITaxCalculator,但不能注入TaxCalculatorICalculator到你的应用程序中.

依照约定公开的服务

如果你未指定要公开的服务,则ABP依照约定公开服务.以上面定义的TaxCalculator为例:

  • 默认情况下,类本身是公开的.这意味着你可以按TaxCalculator类注入它.
  • 默认情况下,默认接口是公开的.默认接口是由命名约定确定.在这个例子中,ICalculatorITaxCalculatorTaxCalculator的默认接口,但ICanCalculate不是.

只要有意义,特性和接口是可以组合在一起使用的.

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator : ITaxCalculator, ITransientDependency
{ }

4、固定的注册类型

一些特定类型会默认注册到依赖注入.例子:

  • 模块类注册为singleton.
  • MVC控制器(继承ControllerAbpController)被注册为transient.
  • MVC页面模型(继承PageModelAbpPageModel)被注册为transient.
  • MVC视图组件(继承ViewComponentAbpViewComponent)被注册为transient.
  • 应用程序服务(实现IApplicationService接口或继承ApplicationService类)注册为transient.
  • 存储库(实现IRepository接口)注册为transient.
  • 域服务(实现IDomainService接口)注册为transient.

示例:

public class BlogPostAppService : ApplicationService
{
}

BlogPostAppService 由于它是从已知的基类派生的,因此会自动注册为transient生命周期.

如何选择?

如果使用的是ABP框架,使用自带的规则注入IOC是比较方便的,以下情况可以考虑手动注册

在某些情况下,你可能需要向IServiceCollection手动注册服务,尤其是在需要使用自定义工厂方法或singleton实例时.在这种情况下,你可以像Microsoft文档描述的那样直接添加服务.例:

public class BlogModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//注册一个singleton实例
context.Services.AddSingleton<TaxCalculator>(new TaxCalculator(taxRatio: 0.18)); //注册一个从IServiceProvider解析得来的工厂方法
context.Services.AddScoped<ITaxCalculator>(sp => sp.GetRequiredService<TaxCalculator>());
}
}

二、依赖注入的使用

使用已注入的服务有三种方法

1、构造函数注入

这是将服务注入类的最常用方法.例如:

public class TaxAppService : ApplicationService
{
private readonly ITaxCalculator _taxCalculator; public TaxAppService(ITaxCalculator taxCalculator)
{
_taxCalculator = taxCalculator;
} public void DoSomething()
{
//...使用 _taxCalculator...
}
}

TaxAppService在构造方法中得到ITaxCalculator.依赖注入系统在运行时自动提供所请求的服务.

构造方法注入是将依赖项注入类的首选方式.这样,除非提供了所有构造方法注入的依赖项,否则无法构造类.因此,该类明确的声明了它必需的服务.

2、属性注入

Microsoft依赖注入库不支持属性注入.但是,ABP可以与第三方DI提供商(例如Autofac)集成,以实现属性注入.例:

public class MyService : ITransientDependency
{
public ILogger<MyService> Logger { get; set; } public MyService()
{
Logger = NullLogger<MyService>.Instance;
} public void DoSomething()
{
//...使用 Logger 写日志...
}
}

对于属性注入依赖项,使用公开的setter声明公共属性.这允许DI框架在创建类之后设置它.

属性注入依赖项通常被视为可选依赖项.这意味着没有它们,服务也可以正常工作.Logger就是这样的依赖项,MyService可以继续工作而无需日志记录.

为了使依赖项成为可选的,我们通常会为依赖项设置默认/后备(fallback)值.在此示例中,NullLogger用作后备.因此,如果DI框架或你在创建MyService后未设置Logger属性,则MyService依然可以工作但不写日志.

属性注入的一个限制是你不能在构造函数中使用依赖项,因为它是在对象构造之后设置的.

当你想要设计一个默认注入了一些公共服务的基类时,属性注入也很有用.如果你打算使用构造方法注入,那么所有派生类也应该将依赖的服务注入到它们自己的构造方法中,这使得开发更加困难.但是,对于非可选服务使用属性注入要非常小心,因为它使得类的要求难以清楚地看到.

3、从IServiceProvider解析服务

你可能希望直接从IServiceProvider解析服务.在这种情况下,你可以将IServiceProvider注入到你的类并使用GetService方法,如下所示:

public class MyService : ITransientDependency
{
private readonly IServiceProvider _serviceProvider; public MyService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
} public void DoSomething()
{
var taxCalculator = _serviceProvider.GetService<ITaxCalculator>();
//...
}
}

 重点:给服务注册回调方法

IServiceCollection.OnRegistred 事件

你可能想在注册到依赖注入的每个服务上执行一个操作, 在你的模块的 PreConfigureServices 方法中, 使用 OnRegistred 方法注册一个回调(callback) , 如下所示:

public class AppModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(ctx =>
{
var type = ctx.ImplementationType;
//...
});
}
}

ImplementationType 提供了服务类型. 该回调(callback)通常用于向服务添加拦截器. 例如:

public class AppModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(ctx =>
{
if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true))
{
ctx.Interceptors.TryAdd<MyLogInterceptor>();
}
});
}
}

这个示例判断一个服务类是否具有 MyLogAttribute 特性, 如果有的话就添加一个 MyLogInterceptor 到拦截器集合中.

注意, 如果服务类公开了多于一个服务或接口, OnRegistred 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 Interceptors.TryAdd 方法而不是 Interceptors.Add 方法.

三、依赖注入源码解析

在接口注册时你可以看到

ABP vNext 仍然在其 Core 库为我们提供了三种接口,即 ISingletonDependency 和 ITransientDependency 、IScopedDependency 接口,方便我们的类型/组件自动注册,这三种接口分别对应了对象的 单例瞬时范围 生命周期。只要任何类型/接口实现了以上任意接口,ABP vNext 就会在系统启动时候,将这些对象注册到 IoC 容器当中。

那么究竟是在什么时候呢?回顾上一章的文章ABP vNext系列文章01---模块化 - zhengwei_cq - 博客园 (cnblogs.com),在模块系统调用模块的 ConfigureService() 的时候,就会有一个 services.AddAssembly(module.Type.Assembly) ,他会将模块的所属的程序集传入。

那我们就从AbpApplicationBase类这个ConfigureService()方法开始分析源码吧

继续进入ConfigureService()方法,我们知道在这个方法中主要是拿出之前加载的所有的模块,分别调用模块中的各种生命周期的方法

其中有一如下一段非常重要的代码

SkipAutoServiceRegistration是默认的值为false,核心的代码为 Services.AddAssembly(assembly);

进入这个扩展方法

发现里有一个比较核心的扩展方法GetConventionalRegistrars(),,主要是/获得所有规约注册器,然后调用规约注册器的 AddAssmbly 方法注册类型。

那这个规约器到底是什么呢,继续进入

在这个方法中可以看到,如果没有获取到规约器就是默认的DefaultConventionalRegistrar对象

那就从这个类开始研究了,进入此类,发现他的基类是ConventionalRegistrarBase,基类又实现了接口IConventionalRegistrar,看看此接口的方标准:

该接口定义了三个方法,支持传入程序集、类型数组、具体类型

再将回到抽像基类ConventionalRegistrarBase,最终调用的方法实现就是该类的扩展方法AddAssembly

先是获取了程序集中的所有类,去掉了抽像类和泛型类,再调用AddTypes方法 来将类型注册到 IServiceCollection 当中的。

查看这个方法,发现这个设计比较巧了,抽象类中这个方法也是抽象的,此方法被延迟到了子类中去实现了,我们就只能回到默认的规约类中看看:

该方法中主要是根据类的注入规则注入到服务中的,除了对三种生命周期接口处理之外,如果类型使用了 DependencyAttribute 特性,也会根据该特性的参数配置进行不同的注册逻辑。

但是从这个方法中我们只知道是否确定要不要把这个类注入到容器中,到底是怎么确定他注入的的生命周期的呢?

此时我们注意到方法GetLifeTimeOrNull(),在此方法前看到还获取了此类的特性GetDependencyAttributeOrNull(type),可能,发特性标签要优先于接口处理的

进入GetLifeTimeOrNull(type, dependencyAttribute);方法

这里补充一个小知道点 IsAssignableFrom方法,主要是用于判断两个类是否兼容,也就是说后者如果前者的实现类或者子类或者相同的类,则返回true.

那么到为此,我们就知道了,为什么我们在定义自己的类如果实现了指定的接口 或者将类上打上指定的特性后会将自己定义的类注入到容器了。

 

ABP vNext系列文章03---依赖注入的更多相关文章

  1. 2019 年起如何开始学习 ABP 框架系列文章-开篇有益

    2019 年起如何开始学习 ABP 框架系列文章-开篇有益 [[TOC]] 本系列文章推荐阅读地址为:52ABP 开发文档 https://www.52abp.com/Wiki/52abp/lates ...

  2. ABP官方文档翻译 2.1 依赖注入

    依赖注入 什么是依赖注入 传统方式的问题 解决方案 构造函数注入模式 属性注入模式 依赖注入框架 ABP依赖注入基础设施 注册依赖注入 传统注册 帮助接口 自定义/直接注册 使用IocManager ...

  3. 一步一步学习ABP项目系列文章目录

    1.概述 基于DDD的.NET开发框架 - ABP初探 基于DDD的.NET开发框架 - ABP分层设计 基于DDD的.NET开发框架 - ABP模块设计 基于DDD的.NET开发框架 - ABP启动 ...

  4. Java Web系列:Spring依赖注入基础

    一.Spring简介 1.Spring简化Java开发 Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构.基础设施和常用功能性组件,而是 ...

  5. NetCoreMvc系列文章02---依赖注入

    .netCore自带依赖注入,支持构造函数注入,如不了解IOC 和DI 思想的请看我其它文章中关于这主面的介绍.如Startup.cs类中的Configure方法其中IApplicationBuild ...

  6. 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程

    一.依赖注入DI 学习目标1)搞清楚构造参数依赖注入的过程及类2)搞清楚注解方式的属性依赖注入在哪里完成的.学习思路1)思考我们手写时是如何做的2)读 spring 源码对比看它的实现3)Spring ...

  7. 03 依赖注入--01控制反转、IoC模式

    控制反转Inversion of Control DI和IoC几乎都是成对出现的,我们在理解依赖注入之前首先要弄明白什么是IoC,也就是控制反转,体现的就是控制权的转移,即控制权原来在A中,现在需要B ...

  8. Spring系列4:依赖注入的2种方式

    本文内容 基于构造器的依赖注入 基于setter的依赖注入 基于构造器的依赖注入 案例 定义2个简单的bean类,BeanOne 和 BeanTwo,前者依赖后者. package com.crab. ...

  9. net core天马行空系列-可用于依赖注入的,数据库表和c#实体类互相转换的接口实现

    1.前言 hi,大家好,我是三合.作为一名程序猿,日常开发中,我们在接到需求以后,一般都会先构思一个模型,然后根据模型写实体类,写完实体类后在数据库里建表,接着进行增删改查, 也有第二种情况,就是有些 ...

随机推荐

  1. python将test01文件夹中的文件剪切到test02文件夹中

    将test01文件夹中的文件剪切到test02文件夹中 import shutil import os def remove_file(old_path, new_path): print(old_p ...

  2. 使用 vim 快速对当前文件夹下的文件批量重命名

    前言 我们在使用 Linux 的时候,有很多种方法可以对文件进行重命名,例如 命令行 下的 mv 命令,或者是使用像 dolphin 这样的图形文件管理器.但是有时候需要对一个文件夹下的所有文件批量重 ...

  3. 抽象类与接口——JavaSE基础

    抽象类与接口 抽象类 抽象类既包含规范又包含具体实现 抽象类可以包含实现的方法 和 未实现的用abstract修饰的抽象方法 抽象类不可以有实例化(不能使用new实例化),只能通过子类继承,然后对子类 ...

  4. conda和pip加速参考

    conda install和创建虚拟环境下载慢,可以修改/root/.condarc文件: vim /root/.condarc 各系统都可以通过修改用户目录下的 .condarc 文件.Window ...

  5. C语言 - 基础数据结构和算法 - 单向链表

    听黑马程序员教程<基础数据结构和算法 (C版本)>,照着老师所讲抄的, 视频地址https://www.bilibili.com/video/BV1vE411f7Jh?p=1 喜欢的朋友可 ...

  6. c++可视化性能测试

    阅读前注意 本文所有代码贴出来的目的是帮助大家理解,并非是要引导大家跟写,许多环境问题文件问题没有详细说明,代码也并不全面,达不到跟做的效果.建议直接阅读全文即可,我在最后会给出详细代码地址,对源代码 ...

  7. 封装环形加载进度条(Vue插件版和原生js版)

    1.效果预览 2.用到的知识 主要利用SVG的stroke-dasharray和stroke-dashoffset这两个属性. 在看下面文章之前,你需要了解 <!DOCTYPE html> ...

  8. Windows启动谷歌浏览器Chrome失败(应用程序无法启动,因为应用程序的并行配置不正确)解决方法

    目录 一.系统环境 二.问题描述 三.解决方法 一.系统环境 Windows版本 系统类型 浏览器Chrome版本 Windows 10 专业版 64 位操作系统, 基于 x64 的处理器 版本 10 ...

  9. jQuery获取市、区县、乡镇、村

    效果图: 首先根据自己方法把地区树状结构json字符串拿到 html下拉框和js写法如下: <select class="form-control" style=" ...

  10. 常用Linux音译

    su:Swith user 切换用户,切换到root用户 cat: Concatenate 串联 uname: Unix name 系统名称 df: Disk free 空余硬盘 du: Disk u ...