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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ping某个域名的详细过程

在前一篇文章中&#xff0c;我们已经对ping命令的整个过程做了一个详解。但是&#xff0c;前一篇文章中所涉及到的两种ping命令使用情况&#xff0c;都是ping的IP&#xff0c;在这篇文章中&#xff0c;我们将要详细讲解ping某个域名的整个过程。 一、ICMP协议 在了解ping命令之…

Magicodes.IE之导入学生数据教程

基础教程之导入学生数据说明本教程主要说明如果使用Magicodes.IE.Excel完成学生数据的Excel导入。要点本教程使用Magicodes.IE.Excel来完成Excel数据导入需要通过创建Dto来完成导入Magicodes.IE.Excel可以根据Dto以及特性设置来自动生成导入的Excel模板&#xff0c;数据验证&am…

IP地址分类详解

一、IP地址简介 IP&#xff0c;即网际协议&#xff08;Internet Protocol&#xff09;&#xff0c;或称互联网协议&#xff0c;是用于报文交换网络的一种面向数据的协议。 IP是在TCP/IP协议中网络层的主要协议&#xff0c;任务是仅仅根据源主机和目的主机的地址传送数据。为此目…

[ASP.NET Core 3框架揭秘] 文件系统[1]:抽象的“文件系统”

ASP.NET Core应用 具有很多读取文件的场景&#xff0c;比如配置文件、静态Web资源文件&#xff08;比如CSS、JavaScript和图片文件等&#xff09;以及MVC应用的View文件&#xff0c;甚至是直接编译到程序集中的内嵌资源文件。这些文件的读取都需要使用到一个IFileProvider对象。…

交换机和路由器的区别

交换机和路由器的主要区别如下&#xff1a; &#xff08;1&#xff09;工作层次不同 最初的交换机是设计工作在OSI模型的数据链路层&#xff08;第二层&#xff09;&#xff0c;而路由器一开始就设计工作在OSI模型的网络层&#xff08;第三层&#xff09;。交换机的工作原理比较…

[原]调试PInvoke导致的内存破坏

缘起 最近项目中遇到一个诡异的问题&#xff0c;程序在升级到.net4.6.1后&#xff0c;执行某个功能时会崩溃&#xff0c;提示访问只读内存区。大概规律如下:debug版不崩溃&#xff0c;release版稳定崩溃。只有x64位的程序崩溃&#xff0c;32位及anycpu编译出来的程序运行不会崩…

Java Socket编程详解

Java Socket编程是Java网络编程很重要的内容&#xff0c;现参考了许多资料&#xff0c;总结如下&#xff1a; 1. Java网络编程原理Socket编程 http://www.cnblogs.com/linzheng/archive/2011/01/23/1942328.html 2. 一个 Java 的 Socket 服务器和客户端通信的例子 http://blog.…

被忽略的TraceId,可以用起来了

前言.NetCore日志&#xff0c;相信大家多少都接触过&#xff0c;博客园有关 ① AspNetCore依赖注入第三方日志组件 ②第三方日志组件Nlog,Serilog 应用方法的博文层出不穷。结合程序的部署结构&#xff0c;本文分单体和微服务聊一聊AspNetCore中追踪日志流的方法。TraceIdAsp…

TCP协议详解

一、TCP协议简介 TCP协议&#xff0c;即传输控制协议&#xff08;Transmission Control Protocol&#xff09;&#xff0c;是一种面向连接的、可靠的、基于字节流的传输层通信协议。在因特网协议族中&#xff0c;TCP层是位于IP层之上&#xff0c;应用层之下的中间层。尽管IP层只…

开始你的api:NetApiStarter

在此之前&#xff0c;写过一篇 给新手的WebAPI实践 &#xff0c;获得了很多新人的认可&#xff0c;那时还是基于.net mvc&#xff0c;文档生成还是自己闹洞大开写出来的&#xff0c;经过这两年的时间&#xff0c;netcore的发展已经势不可挡&#xff0c;自己也在不断的学习&…

UDP协议详解

一、UDP协议简介 UDP协议&#xff0c;即用户数据报协议&#xff08;User Datagram Protocol&#xff09;&#xff0c;是一个简单的面向数据报的传输层协议。UDP协议只在IP数据报服务商增加了很少一点的功能&#xff0c;就是复用和分用&#xff0c;以及差错检测的功能。 二、UDP…

冰雪奇缘,白色世界:四个IT人的四姑娘山双桥沟游记

去年9月初去了川西的稻城亚丁&#xff0c;体会了金色世界秋日童话&#xff0c;还写了一篇游记《从你的全世界路过-一群程序员的稻城亚丁游记》&#xff0c;也是得到了很多朋友和童鞋的点赞。今年11月初趁着周末的两天时间和朋友去了川西的四姑娘山&#xff0c;体会了白色世界冰…

TCP提供可靠传输的工作原理和实现过程

TCP发送的报文段是交给IP层传送的&#xff0c;但IP层只能提供尽最大努力交付的服务&#xff0c;也就是说&#xff0c;TCP下面的网络所提供的是不可靠的传输。因此&#xff0c;TCP采用了一些适当的措施来提供可靠的传输&#xff0c;使得两个传输层直接的通信变得可靠。 --------…

TCP三次握手建立连接

一、三次握手的过程 TCP需要三次握手才能建立连接&#xff0c;整个过程如下图所示&#xff1a; 假设A运行的是TCP客户端进程&#xff0c;而B运行的是TCP服务端进程。最开始的时候两端的TCP进程都处于ClOSED&#xff08;关闭&#xff09;状态。 这时候&#xff0c;A主动打开连接…

[原]排错实战——通过对比分析sysinternals事件修复程序功能异常

缘起 最近&#xff0c;我们程序的某个功能在一台机器上不正常&#xff0c;但是在另外一台机器上却是正常的。代码是同一份&#xff0c;vs版本也一样&#xff08;打的补丁也一样&#xff09;。编译出来的程序在两台电脑上运行的结果就是不一样。惊不惊喜&#xff0c;意不意外&am…

[原]windbg调试系列——崩溃在ComFriendlyWaitMtaThreadProc

前言 这是几年前在项目中遇到的一个崩溃问题&#xff0c;崩溃在了ComFriendlyWaitMtaThreadProc()里。没有源码&#xff0c;耗费了我很大精力&#xff0c;最终通过反汇编并结合原代码才最终搞清楚了事情的来龙去脉。本文的分析是基于真实项目进行的&#xff0c;中间略去了很多反…

TCP四次握手释放连接

一、四次握手的过程 TCP需要三次握手才能建立连接&#xff0c;整个过程如下图所示&#xff1a; 假设A运行的是TCP客户端进程&#xff0c;而B运行的是TCP服务端进程。最开始的时候两端的TCP进程都处于ESTABLISHED&#xff08;已建立连接&#xff09;状态。 这时候&#xff0c;A…

开源WPF控件库MaterialDesignInXAML推荐

今天介绍一个开源的C# WPF开源控件库&#xff0c;非常漂亮&#xff0c;重点是开源哦WPF做桌面开发是很有优势的&#xff0c;除了微软自带的控件外&#xff0c;还有很多第三方的控件库&#xff0c;比如收费的Dev Express For WPF、Telerik For WPF等&#xff0c;及Github上开源免…

TCP连续ARQ协议和滑动窗口协议

TCP协议通过使用连续ARQ协议和滑动窗口协议&#xff0c;来保证数据传输的正确性&#xff0c;从而提供可靠的传输。 一、ARQ协议 ARQ协议&#xff0c;即自动重传请求&#xff08;Automatic Repeat-reQuest&#xff09;&#xff0c;是OSI模型中数据链路层和传输层的错误纠正协议之…

ASP.NET Core 集成测试中模拟登录用户的一种姿势

不管哪种用户验证方式&#xff0c;最终都是在验证成功后设置 HttpContext.User &#xff0c;后续处理环节通过 HttpContext.User 获取用户信息。如果能直接修改 HttpContext.User &#xff0c;就能达到模拟登录的目的&#xff0c;而 ASP.NET Core 的中间件&#xff08;middlewa…