动手造轮子:实现一个简单的依赖注入(二) --- 服务注册优化

Intro

之前实现的那版依赖注入框架基本可用,但是感觉还是不够灵活,而且注册服务和解析服务在同一个地方感觉有点别扭,有点职责分离不够。于是借鉴 Autofac 的做法,增加了一个 ServiceContainerBuilder 来负责注册服务, ServiceContainer负责解析服务,并且增加了一个 ServiceContainerModule 可以支持像 Autofac 中 Module/ RegisterAssemblyModules 一样注册服务

实现代码

ServiceContainerBuilder

增加 ServiceContainerBuild 来专门负责注册服务,原来注册服务的那些扩展方法则从 IServiceContainer 的扩展方法变成 IServiceContainerBuilder 的扩展

  1. public interface IServiceContainerBuilder

  2. {

  3. IServiceContainerBuilder Add(ServiceDefinition item);

  4. IServiceContainerBuilder TryAdd(ServiceDefinition item);

  5. IServiceContainer Build();

  6. }

  7. public class ServiceContainerBuilder : IServiceContainerBuilder

  8. {

  9. private readonly List<ServiceDefinition> _services = new List<ServiceDefinition>();

  10. public IServiceContainerBuilder Add(ServiceDefinition item)

  11. {

  12. if (_services.Any(_ => _.ServiceType == item.ServiceType && _.GetImplementType() == item.GetImplementType()))

  13. {

  14. return this;

  15. }

  16. _services.Add(item);

  17. return this;

  18. }

  19. public IServiceContainerBuilder TryAdd(ServiceDefinition item)

  20. {

  21. if (_services.Any(_ => _.ServiceType == item.ServiceType))

  22. {

  23. return this;

  24. }

  25. _services.Add(item);

  26. return this;

  27. }

  28. public IServiceContainer Build() => new ServiceContainer(_services);

  29. }

IServiceContainer

增加 ServiceContainerBuilder 之后就不再支持注册服务了, ServiceContainer 这个类型也可以变成一个内部类了,不必再对外暴露

  1. public interface IServiceContainer : IScope, IServiceProvider

  2. {

  3. IServiceContainer CreateScope();

  4. }

  5. internal class ServiceContainer : IServiceContainer

  6. {

  7. private readonly IReadOnlyList<ServiceDefinition> _services;

  8. public ServiceContainer(IReadOnlyList<ServiceDefinition> serviceDefinitions)

  9. {

  10. _services = serviceDefinitions;

  11. // ...

  12. }

  13. // 此处约省略一万行代码 ...

  14. }

ServiceContainerModule

定义了一个 ServiceContainerModule 来实现像 Autofac 那样,在某一个程序集内定义一个 Module 注册程序集内需要注册的服务,在服务注册的地方调用 RegisterAssemblyModules 来扫描所有程序集并注册自定义 ServiceContainerModule 需要注册的服务

  1. public interface IServiceContainerModule

  2. {

  3. void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);

  4. }

  5. public abstract class ServiceContainerModule : IServiceContainerModule

  6. {

  7. public abstract void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);

  8. }

自定义 ServiceContainerModule 使用示例:

  1. public class TestServiceContainerModule : ServiceContainerModule

  2. {

  3. public override void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder)

  4. {

  5. serviceContainerBuilder.AddSingleton<IIdGenerator>(GuidIdGenerator.Instance);

  6. }

  7. }

RegisterAssemblyModules 扩展方法实现如下:

  1. public static IServiceContainerBuilder RegisterAssemblyModules(

  2. [NotNull] this IServiceContainerBuilder serviceContainerBuilder, params Assembly[] assemblies)

  3. {

  4. #if NET45

  5. // 解决 asp.net 在 IIS 下应用程序域被回收的问题

  6. // https://autofac.readthedocs.io/en/latest/register/scanning.html#iis-hosted-web-applications

  7. if (null == assemblies || assemblies.Length == 0)

  8. {

  9. if (System.Web.Hosting.HostingEnvironment.IsHosted)

  10. {

  11. assemblies = System.Web.Compilation.BuildManager.GetReferencedAssemblies()

  12. .Cast<Assembly>().ToArray();

  13. }

  14. }

  15. #endif

  16. if (null == assemblies || assemblies.Length == 0)

  17. {

  18. assemblies = AppDomain.CurrentDomain.GetAssemblies();

  19. }

  20. foreach (var type in assemblies.WhereNotNull().SelectMany(ass => ass.GetTypes())

  21. .Where(t => t.IsClass && !t.IsAbstract && typeof(IServiceContainerModule).IsAssignableFrom(t))

  22. )

  23. {

  24. try

  25. {

  26. if (Activator.CreateInstance(type) is ServiceContainerModule module)

  27. {

  28. module.ConfigureServices(serviceContainerBuilder);

  29. }

  30. }

  31. catch (Exception e)

  32. {

  33. Console.WriteLine(e);

  34. }

  35. }

  36. return serviceContainerBuilder;

  37. }

使用示例

使用起来除了注册服务变化了之外,别的地方并没有什么不同,看一下单元测试代码

  1. public class DependencyInjectionTest : IDisposable

  2. {

  3. private readonly IServiceContainer _container;

  4. public DependencyInjectionTest()

  5. {

  6. var containerBuilder = new ServiceContainerBuilder();

  7. containerBuilder.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());

  8. containerBuilder.AddScoped<IFly, MonkeyKing>();

  9. containerBuilder.AddScoped<IFly, Superman>();

  10. containerBuilder.AddScoped<HasDependencyTest>();

  11. containerBuilder.AddScoped<HasDependencyTest1>();

  12. containerBuilder.AddScoped<HasDependencyTest2>();

  13. containerBuilder.AddScoped<HasDependencyTest3>();

  14. containerBuilder.AddScoped(typeof(HasDependencyTest4<>));

  15. containerBuilder.AddTransient<WuKong>();

  16. containerBuilder.AddScoped<WuJing>(serviceProvider => new WuJing());

  17. containerBuilder.AddSingleton(typeof(GenericServiceTest<>));

  18. containerBuilder.RegisterAssemblyModules();

  19. _container = containerBuilder.Build();

  20. }

  21. [Fact]

  22. public void Test()

  23. {

  24. var rootConfig = _container.ResolveService<IConfiguration>();

  25. Assert.Throws<InvalidOperationException>(() => _container.ResolveService<IFly>());

  26. Assert.Throws<InvalidOperationException>(() => _container.ResolveRequiredService<IDependencyResolver>());

  27. using (var scope = _container.CreateScope())

  28. {

  29. var config = scope.ResolveService<IConfiguration>();

  30. Assert.Equal(rootConfig, config);

  31. var fly1 = scope.ResolveRequiredService<IFly>();

  32. var fly2 = scope.ResolveRequiredService<IFly>();

  33. Assert.Equal(fly1, fly2);

  34. var wukong1 = scope.ResolveRequiredService<WuKong>();

  35. var wukong2 = scope.ResolveRequiredService<WuKong>();

  36. Assert.NotEqual(wukong1, wukong2);

  37. var wuJing1 = scope.ResolveRequiredService<WuJing>();

  38. var wuJing2 = scope.ResolveRequiredService<WuJing>();

  39. Assert.Equal(wuJing1, wuJing2);

  40. var s0 = scope.ResolveRequiredService<HasDependencyTest>();

  41. s0.Test();

  42. Assert.Equal(s0._fly, fly1);

  43. var s1 = scope.ResolveRequiredService<HasDependencyTest1>();

  44. s1.Test();

  45. var s2 = scope.ResolveRequiredService<HasDependencyTest2>();

  46. s2.Test();

  47. var s3 = scope.ResolveRequiredService<HasDependencyTest3>();

  48. s3.Test();

  49. var s4 = scope.ResolveRequiredService<HasDependencyTest4<string>>();

  50. s4.Test();

  51. using (var innerScope = scope.CreateScope())

  52. {

  53. var config2 = innerScope.ResolveRequiredService<IConfiguration>();

  54. Assert.True(rootConfig == config2);

  55. var fly3 = innerScope.ResolveRequiredService<IFly>();

  56. fly3.Fly();

  57. Assert.NotEqual(fly1, fly3);

  58. }

  59. var flySvcs = scope.ResolveServices<IFly>();

  60. foreach (var f in flySvcs)

  61. f.Fly();

  62. }

  63. var genericService1 = _container.ResolveRequiredService<GenericServiceTest<int>>();

  64. genericService1.Test();

  65. var genericService2 = _container.ResolveRequiredService<GenericServiceTest<string>>();

  66. genericService2.Test();

  67. }

  68. public void Dispose()

  69. {

  70. _container.Dispose();

  71. }

  72. }

Reference

  • https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/DependencyInjection

  • https://www.cnblogs.com/weihanli/p/implement-dependency-injection-01.html

  • https://www.cnblogs.com/weihanli/p/implement-dependency-injection.html

  • https://autofac.org/

  • https://autofac.readthedocs.io/en/latest/register/scanning.html

动手造轮子:实现一个简单的依赖注入(二) --- 服务注册优化相关推荐

  1. 动手造轮子:实现一个简单的依赖注入(一)

    动手造轮子:实现一个简单的依赖注入(一) Intro 在上一篇文章中主要介绍了一下要做的依赖注入的整体设计和大概编程体验,这篇文章要开始写代码了,开始实现自己的依赖注入框架. 类图 首先来温习一下上次 ...

  2. 动手造轮子:实现一个简单的依赖注入(零)

    动手造轮子:实现一个简单的依赖注入(零) Intro 依赖注入为我们写程序带来了诸多好处,在微软的 .net core 出来的同时也发布了微软开发的依赖注入框架 Microsoft.Extension ...

  3. 动手造轮子:实现简单的 EventQueue

    动手造轮子:实现简单的 EventQueue Intro 最近项目里有遇到一些并发的问题,想实现一个队列来将并发的请求一个一个串行处理,可以理解为使用消息队列处理并发问题,之前实现过一个简单的 Eve ...

  4. 动手造轮子:实现一个简单的基于 Console 的日志输出

    动手造轮子:实现一个简单的基于 Console 的日志输出 Intro 之前结合了微软的 Logging 框架和 Serilog 写了一个简单的日志框架,但是之前的用法都是基于 log4net.ser ...

  5. 动手造轮子:实现一个简单的 EventBus

    动手造轮子:实现一个简单的 EventBus Intro EventBus 是一种事件发布订阅模式,通过 EventBus 我们可以很方便的实现解耦,将事件的发起和事件的处理的很好的分隔开来,很好的实 ...

  6. 动手造轮子:写一个日志框架

    动手造轮子:写一个日志框架 Intro 日志框架有很多,比如 log4net / nlog / serilog / microsoft.extensions.logging 等,如何在切换日志框架的时 ...

  7. 动手造轮子:基于 Redis 实现 EventBus

    动手造轮子:基于 Redis 实现 EventBus Intro 上次我们造了一个简单的基于内存的 EventBus,但是如果要跨系统的话就不合适了,所以有了这篇基于 Redis 的 EventBus ...

  8. 用 Go 编写一个简单的 WebSocket 推送服务

    用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhon- 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息都是通过短信,微 ...

  9. php自动发邮件系统,一个简单的自动发送邮件系统(二)_php基础

    一个简单的自动发送邮件系统(二)_php基础 发布时间:2016-06-17 来源: 点击: 次 这里介绍php和mysql结合起来实用. 基本上,可以说php是介于后台数据库和前台浏览器的一个中间层 ...

最新文章

  1. oracle 插入 基准测试,oracle proc 插入操作性能优化实践
  2. Windows API一日一练(70)GetSystemTime和GetLocalTime函数
  3. Qt Creator开发基于小部件的应用程序
  4. Oracle11默认用户名和密码
  5. java 间隔分钟_java 计算两个 日期时间 相间隔多少天小时分钟 等
  6. JavaScript:原型设计模式
  7. Vue3中导入项目Eslint和TS语法检测问题解决方案
  8. 基于 Python 与 mxget 的音乐下载器
  9. ArcGISServer 发布地理处理服务:以CAD数据至地理数据库为例(1)CAD转至地理数据库
  10. 两台电脑实现串口通信
  11. Learn Git Branching学习笔记 Git常用命令
  12. 12306余票查询(二)——获取余票数据
  13. Windows中texstudio的主题代码(持续更新)
  14. 二维邮局选址问题-带权中位数
  15. linux开篇——硬盘和mbr简介
  16. 求函数:x的n次方(函数递归)
  17. java解压obb_obb文件怎么解压 打开obb文件的注意事项 - 驱动管家
  18. 华东理工计算机类在哪个校区,华东理工大学有几个校区及校区地址 哪个校区最好...
  19. 输入某年某月某日,判断这一天是这一年的第几天?(JS实现)
  20. 《Java核心技术:卷I 基础知识》第1章 Java 程序设计概述 阅读与重点标记

热门文章

  1. HTFS.Software.v7.3-ISO 1DVD(传热模拟,最新完全解密版)
  2. java如何实现封装_java如何实现封装
  3. linux备份mysql需要暂停服务吗_【MySQL运维】线上MySQL数据库停服迁移流程
  4. 如何在不支付Adobe Photoshop费用的情况下处理Camera Raw
  5. 您可能没有注意到的7个Ubuntu File Manager功能
  6. 如何在Xbox One或PlayStation 4上为Skyrim特别版安装Mods
  7. 基于scikit-learn机器学习库的分类预测
  8. LVS负载均衡-NET、DR模式配置
  9. 如何保证执行异步方法时不会遗漏 await 关键字
  10. 我的技术回顾那些与ABP框架有关的故事-2017年