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,大家好,我是三合.作为一名程序猿,日常开发中,我们在接到需求以后,一般都会先构思一个模型,然后根据模型写实体类,写完实体类后在数据库里建表,接着进行增删改查, 也有第二种情况,就是有些 ...
随机推荐
- 【JSOI2016】最佳团体
思路:二分答案+动态规划(结合dfs序) 类型:选/不选:最大比值 代码: #include<stdio.h> #include<bits/stdc++.h> using na ...
- MySQL - 锁的分类
MySQL - 锁的分类 1. 加锁机制 乐观锁 悲观锁 2. 兼容性 共享锁 排他锁 3. 锁粒度 表锁 页锁 行锁 4. 锁模式 记录锁(record-lock) 间隙锁(gap-lock) ne ...
- 图解MySQL逻辑备份的实现流程
1. 摘要 数据作为一家公司的重要资产,其重要程度不言而喻.数据库为数据提供存取服务,担任着重要的角色,如果因数据误删.服务器故障.病毒入侵等原因导致数据丢失或服务不可用,会对公司造成重大损失,所以数 ...
- Google搜索为什么不能无限分页?
这是一个很有意思却很少有人注意的问题. 当我用Google搜索MySQL这个关键词的时候,Google只提供了13页的搜索结果,我通过修改url的分页参数试图搜索第14页数据,结果出现了以下的错误提示 ...
- mybatis踩过的坑
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "- ...
- go程序添加远程调用tcpdump功能
最近开发的telemetry采集系统上线了.听起来高大上,简单来说就是一个grpc/udp服务端,用户的机器(路由器.交换机)将它们的各种统计数据上报采集.整理后交后端的各类AI分析系统分析.目前华为 ...
- 一篇文章带你使用Typescript封装一个Vue组件
一.搭建项目以及初始化配置 vue create ts_vue_btn 这里使用了vue CLI3自定义选择的服务,我选择了ts.stylus等工具.然后创建完项目之后,进入项目.使用快捷命令code ...
- 无语——真的好用到不行的7个Python小技巧
本文总结了我几个我在学习python过程中,用到的几个超好用的操作,这里分享给大家,我相信你们也会非常喜欢,目录如下.这里提前索要再看,记得点一点再看哦.这只是其中一些技巧,以后会慢慢和大家分享. 1 ...
- 强化学习-学习笔记4 | Actor-Critic
Actor-Critic 是价值学习和策略学习的结合.Actor 是策略网络,用来控制agent运动,可以看做是运动员.Critic 是价值网络,用来给动作打分,像是裁判. 4. Actor-Crit ...
- 【Nim 游戏】 学习笔记
前言 没脑子选手随便一道博弈论都不会 -- 正文 Nim 游戏引入 这里给出最简单的 \(Nim\) 游戏的题目描述: \(Nim\) 游戏 有两个顶尖聪明的人在玩游戏,游戏规则是这样的: 有\(n\ ...