ABP vNext系列文章03---依赖注入
一、依赖注入的类型注册
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,但不能注入TaxCalculator或ICalculator到你的应用程序中.
依照约定公开的服务
如果你未指定要公开的服务,则ABP依照约定公开服务.以上面定义的TaxCalculator为例:
- 默认情况下,类本身是公开的.这意味着你可以按
TaxCalculator类注入它. - 默认情况下,默认接口是公开的.默认接口是由命名约定确定.在这个例子中,
ICalculator和ITaxCalculator是TaxCalculator的默认接口,但ICanCalculate不是.
只要有意义,特性和接口是可以组合在一起使用的.
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator : ITaxCalculator, ITransientDependency
{ }
4、固定的注册类型
一些特定类型会默认注册到依赖注入.例子:
- 模块类注册为singleton.
- MVC控制器(继承
Controller或AbpController)被注册为transient. - MVC页面模型(继承
PageModel或AbpPageModel)被注册为transient. - MVC视图组件(继承
ViewComponent或AbpViewComponent)被注册为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---依赖注入的更多相关文章
- 2019 年起如何开始学习 ABP 框架系列文章-开篇有益
2019 年起如何开始学习 ABP 框架系列文章-开篇有益 [[TOC]] 本系列文章推荐阅读地址为:52ABP 开发文档 https://www.52abp.com/Wiki/52abp/lates ...
- ABP官方文档翻译 2.1 依赖注入
依赖注入 什么是依赖注入 传统方式的问题 解决方案 构造函数注入模式 属性注入模式 依赖注入框架 ABP依赖注入基础设施 注册依赖注入 传统注册 帮助接口 自定义/直接注册 使用IocManager ...
- 一步一步学习ABP项目系列文章目录
1.概述 基于DDD的.NET开发框架 - ABP初探 基于DDD的.NET开发框架 - ABP分层设计 基于DDD的.NET开发框架 - ABP模块设计 基于DDD的.NET开发框架 - ABP启动 ...
- Java Web系列:Spring依赖注入基础
一.Spring简介 1.Spring简化Java开发 Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构.基础设施和常用功能性组件,而是 ...
- NetCoreMvc系列文章02---依赖注入
.netCore自带依赖注入,支持构造函数注入,如不了解IOC 和DI 思想的请看我其它文章中关于这主面的介绍.如Startup.cs类中的Configure方法其中IApplicationBuild ...
- 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程
一.依赖注入DI 学习目标1)搞清楚构造参数依赖注入的过程及类2)搞清楚注解方式的属性依赖注入在哪里完成的.学习思路1)思考我们手写时是如何做的2)读 spring 源码对比看它的实现3)Spring ...
- 03 依赖注入--01控制反转、IoC模式
控制反转Inversion of Control DI和IoC几乎都是成对出现的,我们在理解依赖注入之前首先要弄明白什么是IoC,也就是控制反转,体现的就是控制权的转移,即控制权原来在A中,现在需要B ...
- Spring系列4:依赖注入的2种方式
本文内容 基于构造器的依赖注入 基于setter的依赖注入 基于构造器的依赖注入 案例 定义2个简单的bean类,BeanOne 和 BeanTwo,前者依赖后者. package com.crab. ...
- net core天马行空系列-可用于依赖注入的,数据库表和c#实体类互相转换的接口实现
1.前言 hi,大家好,我是三合.作为一名程序猿,日常开发中,我们在接到需求以后,一般都会先构思一个模型,然后根据模型写实体类,写完实体类后在数据库里建表,接着进行增删改查, 也有第二种情况,就是有些 ...
随机推荐
- UniqueMergeTree:支持实时更新删除的 ClickHouse 表引擎
UniqueMergeTree 开发的业务背景 首先,我们看一下哪些场景需要用到实时更新. 我们总结了三类场景: 第一类是业务需要对它的交易类数据进行实时分析,需要把数据流同步到 ClickHouse ...
- IDEA找不到类但实际存在的问题解决
不知道某天开始Idea就开始抽风了. 现象: 一个service的接口类,就在同一个包下,但总是找不到,编辑器一直标红 编译可以通过 说明类本身应该是没什么问题的.问题是怎么重新编译重新reload ...
- SQL中的数字、字母和汉字
知识点001 当变量的数据类型为VARCHAR时,变量赋值后,变量中的字符所占字节数,数字和字母是1个bytes,汉字是2个bytes; 当变量的数据类型为NVARCHAR时,变量赋值后,变量中的字符 ...
- 开发工具-SQL Server官方下载地址
更新记录 2022年6月10日 完善标题. https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 相关链接: SSMS下载地址 ...
- WPF开发随笔收录-心电图曲线绘制
一.前言 项目中之前涉及到胎儿心率图曲线的绘制,最近项目中还需要添加心电曲线和血样曲线的绘制功能.今天就来分享一下心电曲线的绘制方式: 二.正文 1.胎儿心率曲线的绘制是通过DrawingVisual ...
- 03 转换css元素的类别
03 转换css元素的类别 通过设置display属性 属性 作用 block 块级 inline 行内 inline-block 行内块级 接来下 就跟着小demo来学习吧! 不懂可以看看!!!什么 ...
- UiPath存在图像Image Exists的介绍和使用
一.Image Exists的介绍 检查是否在指定的UI元素中找到图像,输出的是一个布尔值 二.Image Exists在UiPath中的使用 1. 打开设计器,在设计库中新建一个Sequence,为 ...
- python——进行年龄和性别检测
年龄和性别检测 使用Python编程语言带你完成使用机器学习进行年龄和性别检测的任务. 首先需要编写用于检测人脸的代码,因为如果没有人脸检测,我们将无法进一步完成年龄和性别预测的任务. 下一步是预测图 ...
- Python递归函数的定义和几个小例子
递归函数 (1)什么是递归函数? 我们都知道,一个函数可以调用其他函数.如果这个函数在内部调用它自己,那么这个函数就叫递归函数. (2)递归函数的作用 举个例子,我们来计算阶乘 n! = 1 * 2 ...
- js--js实现基础排序算法
前言 文本来总结常见的排序算法,通过 JvavScript 来实现 正文 1.冒泡排序 算法思想:比较相邻两个元素的大小,如果第一个比第二个大,就交换它们.从头遍历到尾部,当一轮遍历完后,数组最后一 ...