ABP VNext系列(二)-详解ABP的依赖注入

ABP VNext系列(二)-详解ABP的依赖注入

上一篇 : ABP VNext系列(一)-第一个ABP VNext

一、什么是依赖注入

依赖注入、控制反转、ioc容器,这三个词相信大家都不陌生,并且这三个词都是同时出现的,那么到底什么是依赖
	注入?什么是控制反转?什么是ioc容器呢?

1.1 控制反转

首先我们来看一个案例: 基于职责单一原则下的 --->指挥小动物叫
//什么叫做控制
//小狗
pulic class Dog
{
	public void Scream()
	{
		Console.WriteLine("小狗叫");
	}
}
public class Program
{
	//业务类
	//此时如果Dog类的构造发生了变化
	//比如变成了有参构造,那么Program就需要改代码来适应了
	//如果一旦Dog在无数个地方被new,那么不堪设想
	static void Main(string[] args)
	{
		Dog dog = new Dog();
		dog.Scream();
	}
}
  1. Dog类作为被调用方,是需要调用方Program类自身来创建的
  2. 那么Dog类和Program就紧紧的耦合在一起了
  3. 那么我们可以这么理解,Dog类的创建控制权在Program类上
  4. 这个就叫做控制
  5. 那么什么叫做反转?
//什么叫做反转?
//小狗
pulic class Dog
{
	public void Scream()
	{
		Console.WriteLine("小狗叫");
	}
}
public class InstanceFactory
{
	public static Dog CreateDog()
	{
		return new Dog();
	}
}
public class Program
{
	//业务类
	//此时,Dog的创建并不是靠Program来完成了
	//创建控制权移交到了InstanceFactory上
	//就算构造变了,也只需要改InstanceFactory,而其他地方不需要动
	//松耦合了
	static void Main(string[] args)
	{
		Dog dog = InstanceFactory.CreateDog();
		dog.Scream();
	}
}
  1. 创建控制权移交到了InstanceFactory上
  2. 那么Dog类和Program就松耦合了
  3. 这个就叫做反转
  4. 总结来说,就是将被调用方的创建控制权从调用方转移到第三方,那么这个就叫做控制反转
    其中Dog类是被调用方,Program是调用方, InstanceFactory是第三方,掌握对象的创建权

1.2 IOC容器

public class InstanceFactory
{
	public static Dog CreateDog()
	{
		return new Dog();
	}
}
1. 借用一下上面的例子,虽然 InstanceFactory 控制着对象的创建,但是只是把耦合度转移到了 
InstanceFactory 上,并没有真正的实现松耦合
2. 假如我们有无数个类,猫、老鼠、鸟等等,都需要在InstanceFactory进行创建和维护,也很麻烦
3. 更别说如果这些类中有深层次的依赖关系,比如下面实例
pulic class Dog
{
	public Dog(Cat cat, Bird bird){}
	public void Scream()
	{
		Console.WriteLine("小狗叫");
	}
}
pulic class Cat
{
	public Cat(Bird bird)
	public void Scream()
	{
		Console.WriteLine("小猫叫");
	}
}
pulic class Bird
{
	public void Scream()
	{
		Console.WriteLine("小鸟叫");
	}
}
public class InstanceFactory
{
	public static Dog CreateDog()
	{
		var bird = new Bird();//首先实例化底层Bird
		var cat = new Cat(bird);//再实例化Cat
		return new Dog(cat, bird);//返回Dog
	}
}
1. 上面例子就展示了一个有参构造的依赖关系
2. 那么此时我们如果要创建一个Dog对象,那么我们的代码应该是上面的样子
3. 这样的话就很不丝滑,那么如果这个对象的依赖关系再复杂一点,无疑是很难维护的
4. 这样的情况下,我们就需要一个第三方能帮助我们更方便的创建和管理对象,包括对象的依赖关系
5. 这个第三方就叫做IOC容器
6. 这么说很抽象,因为ioc容器代码比较多,我抽象的写点伪代码来帮助理解,我只简单说一下**构造注入**
public class IocContainer
{
	//既然叫容器,那么我们得有一个东西来存放对象信息,这里我们用个字典来存
	private static Dictionary<string, Type> container = new Dictionary<string, Type>();
	//既然有容器,我们得有东西装对吧,装得是啥?就是对象得Type
	public static void RegisterInstance<TDestination>()
    {//提供一个方法让别人能进入到容器里
       this.container.Add(typeof(TDestination).FullName, typeof(TDestination));
    }
    public static TSource GetInstance<TSource>()
    {//提供一个方法让别人能获取对象实例
       return (TSource)CreateInstance(typeof(TSource));
    }
    private static object CreateInstance(Type source)
    {//用于内部创建对象,那么这个方法我们要做什么呢?
    	//1.首先根据Type类型,我们要到容器字典里面取相应得对象信息,没有就抛异常
    	var sourceType = this.container[source.FullName];
    	//2.找到构造方法,这里我们取构造函数参数最多的一个
		var constructor = sourceType.GetConstructors()
									.OrderByDescending(x => x.GetParameters().Length)
									.FirstOrDefault();
		
		//3.获取构造的参数
		var paras = constructor.GetParameters();
		//4.用于保存参数生成的构造实例
        List<object> parasList = new List<object>();
        foreach (var item in paras)
        {
            Type paraType = item.ParameterType;//获取参数的type
            //递归实现多层级的构造
            //为了应对new Dog(cat, bird)这种多层级依赖关系
            object ins = this.CreateInstance(paraType);
            parasList.Add(ins);//添加到实例列表
        }
        
        //创建对象返回
        return Activator.CreateInstance(sourceType, parasList.ToArray());
    }
}
1. 上面的代码我就很抽象的解释了一个Ioc容器长啥样,做了啥事儿
2. 当然这个不完善,因为代码太多,博主也不太可能全部都写完
3. 当然想要理解代码,需要有一定的 **反射**的知识
4. 重要的事情说三遍!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
5. 上面的是伪代码,讲述了IOC长啥样,做了啥事儿,但是不是生产真正能用的,只是为了帮助理解!!!!!!!!
6. 上面的是伪代码,讲述了IOC长啥样,做了啥事儿,但是不是生产真正能用的,只是为了帮助理解!!!!!!!!
7. 上面的是伪代码,讲述了IOC长啥样,做了啥事儿,但是不是生产真正能用的,只是为了帮助理解!!!!!!!!

1.3 依赖注入

1. 上面了解到,ioc翻译过来就是控制反转,这个只是一个设计原则,并不是啥具体的东西,是个思想
2. Ioc容器则是一个具体的东西,可以落地到代码看得到的一个容器,是实现控制反转的一个工具
3. 那么依赖注入是啥?
	3.1 依赖注入也是实现控制反转的一个方向,但是不是一个具体的工具,而是一个原则
	3.2 这么说有点抽象,大家理解成就是基于·控制反转·和·ioc容器·,
		我们怎么落地去创建和获取一个对象的方式
	3.3 而这个方式就叫做 ·依赖注入·

二、生命周期管理

2.1 单例

1. 单例的生命周期的意思是,一个对象从第一次被创建之后,任何地方获取到的这个对象都是同一个对象.
2. 对应的.net core 中的  ---> AddSingleton<xxx>();

2.2 瞬态

1. 瞬态的生命周期的意思是,这个类型的对象每次在获取的时候都是一个新对象,相当于每次都是new object()
2. 对应的.net core 中的  ---> AddTransient<xxx>()

2.3 Scope作用域

1. 作用域就有点抽象了,大概就是这类对象在创建的时候,容器会帮我们创建一个子容器.并且在此类对象在
这个子容器里面是单例的.例如一个webapi请求中,会有一个针对这个web请求的作用域的子容器.那么在这次
请求中注册了scope的对象是单例的.生命周期随着子容器被释放时释放.更简单来说就是在一个webapi调用
过程中的所有注入此类型的对象是单例.但是不同请求又是不同的对象.
3. 对应的.net core 中的  ---> AddScoped<xxx>()

三、ABP中怎么使用

1. 准备工作

  1. 准备一个控制台程序.标准的创建一个ABP应用. 如有不懂的请看博主前面一篇的介绍.
class Program
    {
        static void Main(string[] args)
        {
			using var application = AbpApplicationFactory.Create<DependencyModule>(options => options.UseAutofac());
			application.Initialize();
		}
    }
  1. 准备模块类
[DependsOn(typeof(AbpAutofacModule))]
public class DependencyModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
    }
}
  1. 准备接口
//单例接口
public interface ISingletonTest
{
    void RunSingleton(string name);
}

public class SingletonTest : ISingletonTest
{
    public void RunSingleton(string name)
    {
        Console.WriteLine($"{name}: 这里是单例生命周期....");
    }
}
//瞬态接口
public interface ITransientTest
{
    void RunTransient(string name);
}

public class TransientTest : ITransientTest
{
    public void RunTransient(string name)
    {
        Console.WriteLine($"{name}: 这里是瞬态生命周期.....");
    }
}
//scope接口
public interface IScopeTest
{
    public void RunScope(string name);
}

public class ScopeTest : IScopeTest
{
    public void RunScope(string name)
    {
        Console.WriteLine($"{name}: 这里是Scope生命周期....");
    }
}

2. ABP模块类的ConfigureServices

1. 看到上面的模块类,博主有重写一个方法 ConfigureServices.那么这是什么呢?
2. 这个方法等于-----> .net core-startup.cs 中的ConfigureServices方法.作用是一样的
3. 只不过参数由ConfigureServices(IServiceCollection services)-->
ConfigureServices(ServiceConfigurationContext context)
4. 但是ServiceConfigurationContext这个对象其实就是将IServiceCollection包了一层.
5. 我们可以通过这个对象来访问IServiceCollection

在这里插入图片描述

6. 这里我们就可以利用这个对象来像startup.cs中手动注册试试.
7. 所以我们的代码就变成了这个样子--->
//模块类的改造
[DependsOn(typeof(AbpAutofacModule))]
public class DependencyModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddSingleton<ISingletonTest, SingletonTest>();
        context.Services.AddTransient<ITransientTest, TransientTest>();
        context.Services.AddScoped<IScopeTest, ScopeTest>();
    }
}

3. 手动注册方式

1. 从下图我们可以看到,这种方式可以正常拿到我们服务接口的.
2. 并且这种方式和我们平常在.net core中手动注册服务的方式一样,只不过在注册的时候使用的是包了一层的对象

在这里插入图片描述

4. 自动注册

1. 上面手动注册的方式虽然可以使用,但是明显存在一个弊端
2. 当我们的项目接口类特别特别多,几十个的时候,那我们需要维护的一个注册量就太大了.
3. ABP框架为我们提供了一个按照约定的自动注册的方式,来帮我们规避这个问题
4. 此时我们需要改造一下
    4.1 将手动注册的代码注释
    4.2 将我们的三个实现类分别多继承一个接口
5. 修改完之后重新运行

在这里插入图片描述

//单例实现类继承 ISingletonDependency 接口
public class SingletonTest : ISingletonTest, ISingletonDependency

//transient实现类继承 ITransientDependency 接口
public class TransientTest : ITransientTest, ITransientDependency

//scope实现类继承 IScopedDependency 接口
public class ScopeTest : IScopeTest,  IScopedDependency 
6. 此时发现程序也正常运行,那么说明接口服务注册成功
7. 那么此时我们可以得出一个结论,就是:
    7.1 ISingletonDependency-->实现类继承次接口代表自动注册单例模式
    7.2 ITransientDependency-->实现类继承次接口代表自动注册瞬态模式
    7.3 IScopedDependency-->实现类继承次接口代表自动注册Scope作用域模式
8. 那么我们在开发过程中就不需要手动显示注册服务和接口,只需要继承接口即可.
9. 当然这约定接口不只这些,还有很多其他的,都帮我们规避了显示注册,只不过博主拿这三个来介绍

在这里插入图片描述

5. 自动注册原理

1. 那么自动的原理是什么呢?博主简单的写一点伪代码来介绍
// 1. 反射获取程序集的信息,当然也可以当做参数抽出一个方法
-->Assembly.GetExecutingAssembly()
//2. 获取程序集的types
-->var typs = Assembly.GetExecutingAssembly().GetTypes();
//3. 遍历types,找出继承ISingletonDependency的类
-->        
foreach (var item in types)
{
//1. 使用 IsAssignableFrom
bool needSingleton = typeof(ISingletonDependency).IsAssignableFrom(item)
//2. 使用 IsSubclassOf  这两个方式二选一
bool needSingleton = item.IsSubclassOf(typeof(ISingletonDependency));

//3. 光这些判断还不够,需要是class类型并且不是抽象类
if (needSingleton  && item.IsClass && !item.IsAbstract)
{
// 这里需要筛选掉  ISingletonDependency自身
// 并且找到这个实现类的本身接口并注册
var interfaceTypes = item.GetInterfaces().Where(p => !p.FullName.Contains("ISingletonDependency"));
foreach (var interfaceType in interfaceTypes)
{
	//博主代码写得有点抽象,这个services是咱们的 IServicesCollection对象,
	//这个方法肯定是要抽离出来作为一个公共方法的,
	//以IServicesCollection的扩展方法的形式
	//到这一步,其实
	// type-->就是SingletonTest.cs 类
	// interfaceType--->就是ISingletonTest.cs 接口
	// 然后我们注册一下
    services.AddTransient(interfaceType,type);
}
}
//至于另外两个,其实就是多两个foreach罢了,博主就不写了
2. 通过上面的小段伪代码就可以实现自动注册.

四、对象对比

4.1 单例

在这里插入图片描述

4.2 瞬态

在这里插入图片描述

4.3 Scope作用域

4.3.1 同一个作用域

1. 此时由于博主没有创建新的作用域,那么两个对象应该是在同一作用域下单例

在这里插入图片描述

4.3.2 不同作用域

1. 此时博主通过不同的作用域来获取IServiceProvider
2. 并且用不同作用域的IServiceProvider来获取接口服务

在这里插入图片描述

五、总结

1. 此测试实例代码放在 https://gitee.com/jerryow/abp-practice
2. 此文介绍的是ABP的ioc容器及接口服务注册方式,并且介绍了依赖注入、ioc容器、控制翻转相关的概念
和代码实现
3. 并且ABP框架帮我们实现了按照约定方式自动注册服务接口,帮我们省了很多事情.
4. 到目前为止,博主已经介绍清楚了最小化的ABP应用及基本使用,后续博主会按照模块的方式来解读ABP框架
5. 如有不对欢迎指正,希望能帮到大家.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值