动手造轮子:实现一个简单的依赖注入(零)
动手造轮子:实现一个简单的依赖注入(零)
Intro
依赖注入为我们写程序带来了诸多好处,在微软的 .net core 出来的同时也发布了微软开发的依赖注入框架 Microsoft.Extensions.DependencyInjection,大改传统 asp.net 的开发模式,asp.net core 的开发更加现代化,更加灵活,更加优美。
依赖注入介绍
要介绍依赖注入,首先来聊一下控制反转(IoC)
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
- 谁控制谁,控制什么:传统程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由 IoC 容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
- 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。
IoC 很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于 IoC 容器;
●为什么需要依赖:应用程序需要 IoC 容器来提供对象需要的外部资源;
●谁注入谁:很明显是 IoC 容器注入应用程序里依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源/依赖。
依赖注入明确描述了 “被注入对象依赖 IoC 容器配置依赖对象”,依赖注入是控制反转设计思想的一种实现。
依赖注入的好处:
- 对象的创建和销毁完全交给 ioc 容器去做,不再需要在应用中关心对象的创建的和销毁,这对于 C# 里的
IDisposable
对象来说尤为重要,自己去 new 的时候,对于一些新手来说可能会忘记使用using
或手动dispose
- 对象的复用,有时候很多对象没有必要每次用的时候就去创建一次,使用 ioc 可以控制在同一生命周期内的对象只被创建一次
- 依赖关系更清晰
- 更好的实现面向接口编程,替换实现只需要注入服务的时候换成另外一种实现就可以了
大概设计
大体使用类似于微软的依赖注入框架,但是比微软的依赖注入框架简单一些,性能也有待优化。
- 服务生命周期:服务的生命周期沿用微软的服务生命周期,分为
Singleton
/Scoped
/Transient
,默认值是Singleton
单例模式 - 服务注册方式:支持所有微软依赖注入的注册方式,实例注入/类型注入/接口-实现注入/func 注入
- 注入方式:目前仅支持依赖注入,构造方法注入,未来暂时也没有支持属性注入的打算(支持的话也不复杂,但是依赖关系就不清晰了,也不推荐用),构造方法注入支持直接注入
IEnumerable<T>
或IReadOnlyCollection<T>
或IReadOnlyList<T>
来支持获取一个接口多个实现的注入,支持泛型注入
DI 相关类图:
体验一下
可以参考单元测试:
using(IServiceConatiner container = new ServiceContainer())
{
container.AddSingleton<IConfiguration>(new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build()
);
container.AddScoped<IFly, MonkeyKing>();
container.AddScoped<IFly, Superman>();
container.AddScoped<HasDependencyTest>();
container.AddScoped<HasDependencyTest1>();
container.AddScoped<HasDependencyTest2>();
container.AddScoped<HasDependencyTest3>();
container.AddScoped(typeof(HasDependencyTest4<>));
container.AddTransient<WuKong>();
container.AddScoped<WuJing>(serviceProvider => new WuJing());
container.AddSingleton(typeof(GenericServiceTest<>));
var rootConfig = container.ResolveService<IConfiguration>();
Assert.Throws<InvalidOperationException>(() => container.ResolveService<IFly>());
Assert.Throws<InvalidOperationException>(() => container.ResolveRequiredService<IDependencyResolver>());
using (var scope = container.CreateScope())
{
var config = scope.ResolveService<IConfiguration>();
Assert.Equal(rootConfig, config);
var fly1 = scope.ResolveRequiredService<IFly>();
var fly2 = scope.ResolveRequiredService<IFly>();
Assert.Equal(fly1, fly2);
var wukong1 = scope.ResolveRequiredService<WuKong>();
var wukong2 = scope.ResolveRequiredService<WuKong>();
Assert.NotEqual(wukong1, wukong2);
var wuJing1 = scope.ResolveRequiredService<WuJing>();
var wuJing2 = scope.ResolveRequiredService<WuJing>();
Assert.Equal(wuJing1, wuJing2);
var s0 = scope.ResolveRequiredService<HasDependencyTest>();
s0.Test();
Assert.Equal(s0._fly, fly1);
var s1 = scope.ResolveRequiredService<HasDependencyTest1>();
s1.Test();
var s2 = scope.ResolveRequiredService<HasDependencyTest2>();
s2.Test();
var s3 = scope.ResolveRequiredService<HasDependencyTest3>();
s3.Test();
var s4 = scope.ResolveRequiredService<HasDependencyTest4<string>>();
s4.Test();
using (var innerScope = scope.CreateScope())
{
var config2 = innerScope.ResolveRequiredService<IConfiguration>();
Assert.True(rootConfig == config2);
var fly3 = innerScope.ResolveRequiredService<IFly>();
fly3.Fly();
Assert.NotEqual(fly1, fly3);
}
var flySvcs = scope.ResolveServices<IFly>();
foreach (var f in flySvcs)
f.Fly();
}
var genericService1 = container.ResolveRequiredService<GenericServiceTest<int>>();
genericService1.Test();
var genericService2 = container.ResolveRequiredService<GenericServiceTest<string>>();
genericService2.Test();
}
更多详情可以参考:< https://github.com/WeihanLi/WeihanLi.Common/blob/dev/test/WeihanLi.Common.Test/DependencyInjectionTest.cs >
More
源码已经在 Github 上,可以自行下载阅览或等后面的几篇文章分享解读
Reference
- https://blog.csdn.net/sinat_21843047/article/details/80297951
- https://www.cnblogs.com/artech/p/inside-asp-net-core-03-04.html
- https://github.com/aspnet/DependencyInjection/tree/rel/2.0.0
- https://github.com/microsoft/MinIoC
- https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/DependencyInjection