一、依赖注入的类型注册

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. 带你学习MindSpore中算子使用方法

    摘要:本文分享下MindSpore中算子的使用和遇到问题时的解决方法. 本文分享自华为云社区<[MindSpore易点通]算子使用问题与解决方法>,作者:chengxiaoli. 简介 算 ...

  2. redisson之分布式锁实现原理(三)

    官网:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95 一.什么是分布式锁 1.1.什么是分布式锁 分布式锁,即分布式系统中的锁 ...

  3. .Net CLR GC动态获取函数头地址,C++的骚操作(慎入)

    前言: 太懒了,从没有在这里正儿八经的写过文章.看到一些人的高产,真是惭愧.决定稍微变得不那么懒.如有疏漏,请指正. .net的GC都谈的很多了,本篇主要是剑走偏锋,聊聊一些个人认为较为核心的细节方面 ...

  4. React中render Props模式

    React组件复用 React组件复用的方式有两种: 1.render Props模式 2.高阶组件HOC 上面说的这两种方式并不是新的APi. 而是利用Raect自身的编码特点,演化而来的固定编码写 ...

  5. java中的方法重载(overload)

    什么时候方法重载:当两个方法的功能是相似的,可以考虑使用方法重载.若两个方法根本没有关系,无必要使用方法重载. 什么时候代码会发生方法重载:三个条件:1,在同一个类中.2,方法名相同.3,参数列表相同 ...

  6. JS:eval

    定义和用法: eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行.eval()函数并不会创建一个新的作用域,并且它的作用域就是它所在的作用域. 如果参数是一个表达式,eva ...

  7. 你是否有一个梦想?用JavaScript[vue.js、react.js......]开发一款自定义配置视频播放器

    前言沉寂了一周了,打算把这几天的结果呈现给大家.这几天抽空就一直在搞一个自定义视频播放器,为什么会有如此想法?是因为之前看一些学习视频网站时,看到它们做的视频播放器非常Nice!于是,就打算抽空开发一 ...

  8. k8s之有状态服务部署基石(基础知识)

    PV&PVC&HeadlessService 4.1.什么是无状态/有状态服务? 无状态服务: 1.没有实时的数据需要存储 (即使有,也是静态数据) 2.服务集群网络中,拿掉一个服务后 ...

  9. Tomcat深入浅出——Servlet(三)

    零.HttpServletRequest 上一篇已经介绍了这个接口,现在补充些内容 首先介绍一下作用域: jakarta.servlet.jsp.PageContext pageContext 页面作 ...

  10. Oracle查看所有用户及其权限

    Oracle查看所有用户及其权限:Oracle数据字典视图的种类分别为:USER,ALL 和 DBA. USER_*:有关用户所拥有的对象信息,即用户自己创建的对象信息 ALL_*:有关用户可以访问的 ...