翼度科技»论坛 编程开发 .net 查看内容

Fireasy3 揭秘 -- 万物伊始(依赖注入与服务发现)

3

主题

3

帖子

9

积分

新手上路

Rank: 1

积分
9
    最近在忙于 Fireasy 的重构,3.x 抛弃了 .Net Framework 时代的一些思想和模式,紧密拥抱 .Net Core,但它的思想仍然是开放性和灵活性。今天我主要来说说依赖注入与服务发现。
    .Net Core 有自己的一套依赖注入,它的容器暴露给 IServiceCollection,通过在里面放入一些单例(Singleton)、瞬时(Transient)、作用域(Scoped)的一些服务描述(服务与实现的关系映射),这一部分我就不再细说了。
    当然,一般常用的方式是,通过 AddSingleton、AddTransient 和 AddScoped 方法往容器里面加,但如果是依赖比较多的情况下(比如业务服务类),那你可能会经常忘了写这一部分代码了,而且也很难于维护。如常见的方式:
  1. void ConfigureServices(IServiceCollection services)
  2. {
  3.     services.AddTransient<IDeptmentService, DeptmentService>();
  4.     services.AddTransient<IRoleService, RoleService>();
  5.     services.AddTransient<IUserService, UserService>();
  6.     services.AddTransient<IDataRoleService, DataRoleService>();
  7.     //.......
  8.     services.AddTransient<IProcessService, ProcessService>();
  9.     services.AddTransient<IWorkService, WorkService>();
  10. }
复制代码
    有没有更简便更容易维护的方式呢?答案是当然有!
    在 Fireasy,我们定义了三个服务接口,分别是 ISingletonService、ITransientService 和 IScopedService,这三个类只是一个标识,没有具体的方法和属性。使用需要注入的类实现此接口,如下:
  1. public class DeptmentService : IDeptmentService, ITransientService
  2. {
  3.     // ......
  4. }
  5. public class DataRoleHelper : IDataRoleHelper, ISingletonService
  6. {
  7.     // ......
  8. }
复制代码
    好了,你只需在 ConfigureServices 里添加上这么一行代码,就能实现依赖注入:
  1. void ConfigureServices(IServiceCollection services)
  2. {
  3.     services.AddFireasy();
  4. }
复制代码
    现在开始步入正题了,来看看 AddFireasy 是如何工作的。
    IServiceDiscoverer 是用于服务发现的接口,它的默认实现是 DefaultServiceDiscoverer。如下:
  1.     public static SetupBuilder AddFireasy(this IServiceCollection services, Action<SetupOptions>? configure = null)
  2.     {
  3.         var options = new SetupOptions();
  4.         configure?.Invoke(options);
  5.         var builder = new SetupBuilder(services, options);
  6.         var discoverer = options.DiscoverOptions.DiscovererFactory == null ? new DefaultServiceDiscoverer(services, options.DiscoverOptions)
  7.             : options.DiscoverOptions.DiscovererFactory(services, options.DiscoverOptions);
  8.         if (discoverer != null)
  9.         {
  10.             services.AddSingleton<IServiceDiscoverer>(discoverer);
  11.         }
  12.         return builder;
  13.     }
复制代码
    入口方法是 DiscoverServices,它会遍列程序目录下的所有程序集文件(*.dll),这里有程序集过滤器,你可以自己定义过滤规则。如下:
  1.     /// <summary>
  2.     /// 发现工作目录中所有程序集中的依赖类型。
  3.     /// </summary>
  4.     /// <param name="services"></param>
  5.     private void DiscoverServices(IServiceCollection services)
  6.     {
  7.         foreach (var assembly in GetAssemblies())
  8.         {
  9.             if (_options?.AssemblyFilters?.Any(s => s.IsFilter(assembly)) == true)
  10.             {
  11.                 continue;
  12.             }
  13.             if (_options?.AssemblyFilterPredicates?.Any(s => s(assembly)) == true)
  14.             {
  15.                 continue;
  16.             }
  17.             _assemblies.Add(assembly);
  18.             ConfigureServices(services, assembly);
  19.             DiscoverServices(services, assembly);
  20.         }
  21.     }
复制代码
    方法 DiscoverServices 用于对单个程序集进行服务发现并进行注册,这里同样也有类型过滤器,如下:
  1.     /// <summary>
  2.     /// 发现程序集中的所有依赖类型。
  3.     /// </summary>
  4.     /// <param name="services"></param>
  5.     /// <param name="assembly"></param>
  6.     private void DiscoverServices(IServiceCollection services, Assembly assembly)
  7.     {
  8.         foreach (var type in assembly.GetExportedTypes())
  9.         {
  10.             if (_options?.TypeFilters?.Any(s => s.IsFilter(assembly, type)) == true)
  11.             {
  12.                 continue;
  13.             }
  14.             if (_options?.TypeFilterPredicates?.Any(s => s(assembly, type)) == true)
  15.             {
  16.                 continue;
  17.             }
  18.             ServiceLifetime? lifetime;
  19.             var interfaceTypes = type.GetDirectImplementInterfaces().ToArray();
  20.             //如果使用标注
  21.             if (type.IsDefined(typeof(ServiceRegisterAttribute)))
  22.             {
  23.                 lifetime = type.GetCustomAttribute<ServiceRegisterAttribute>()!.Lifetime;
  24.             }
  25.             else
  26.             {
  27.                 lifetime = GetLifetimeFromType(type);
  28.             }
  29.             if (lifetime == null)
  30.             {
  31.                 continue;
  32.             }
  33.             if (interfaceTypes.Length > 0)
  34.             {
  35.                 interfaceTypes.ForEach(s => AddService(services, s, type, (ServiceLifetime)lifetime));
  36.             }
  37.             else
  38.             {
  39.                 AddService(services, type, type, (ServiceLifetime)lifetime);
  40.             }
  41.         }
  42.     }
  43.     private ServiceLifetime? GetLifetimeFromType(Type type)
  44.     {
  45.         if (typeof(ISingletonService).IsAssignableFrom(type))
  46.         {
  47.             return ServiceLifetime.Singleton;
  48.         }
  49.         else if (typeof(ITransientService).IsAssignableFrom(type))
  50.         {
  51.             return ServiceLifetime.Transient;
  52.         }
  53.         else if (typeof(IScopedService).IsAssignableFrom(type))
  54.         {
  55.             return ServiceLifetime.Scoped;
  56.         }
  57.         return null;
  58.     }
  59.     private ServiceDescriptor AddService(IServiceCollection services, Type serviceType, Type implType, ServiceLifetime lifetime)
  60.     {
  61.         var descriptor = ServiceDescriptor.Describe(serviceType, implType, lifetime);
  62.         _descriptors.Add(descriptor);
  63.         services.Add(descriptor);
  64.         return descriptor;
  65.     }
复制代码
    从上面的代码中可看出,通过在程序集内部查找实现了 ISingletonService、ITransientService 或 IScopedService 的类,并将它们添加到 services 中,这样就完成了开篇提到的工作。
    这里还出现了一个 ServiceRegisterAttribute,它在不实现以上三个接口的情况下,通过标注 Lifetime 生命周期来进行注册,一样达到了目的。
    接下来做几个简单的单元测试。
    单例测试:
  1.     /// <summary>
  2.     /// 测试单例服务
  3.     /// </summary>
  4.     [TestMethod]
  5.     public void TestSingletonService()
  6.     {
  7.         var services = new ServiceCollection();
  8.         var builder = services.AddFireasy();
  9.         var serviceProvider = services.BuildServiceProvider();
  10.         var service1 = serviceProvider.GetService<ITestSingletonService>();
  11.         var service2 = serviceProvider.GetService<ITestSingletonService>();
  12.         Assert.IsNotNull(service1);
  13.         Assert.IsNotNull(service2);
  14.         //两对象的id应相等
  15.         Assert.AreEqual(service1.Id, service2.Id);
  16.     }
  17.     public interface ITestSingletonService
  18.     {
  19.         Guid Id { get; }
  20.         void Test();
  21.     }
  22.     public class TestSingletonServiceImpl : ITestSingletonService, ISingletonService
  23.     {
  24.         public TestSingletonServiceImpl()
  25.         {
  26.             Id = Guid.NewGuid();
  27.         }
  28.         public Guid Id { get; }
  29.         public void Test() => Console.WriteLine("Hello TestSingletonService!");
  30.     }
复制代码
    瞬时测试:
  1.     /// <summary>
  2.     /// 测试瞬时服务
  3.     /// </summary>
  4.     [TestMethod]
  5.     public void TestTransientService()
  6.     {
  7.         var services = new ServiceCollection();
  8.         var builder = services.AddFireasy();
  9.         var serviceProvider = services.BuildServiceProvider();
  10.         var service1 = serviceProvider.GetService<ITestTransientService>();
  11.         var service2 = serviceProvider.GetService<ITestTransientService>();
  12.         Assert.IsNotNull(service1);
  13.         Assert.IsNotNull(service2);
  14.         //两对象的id应不相等
  15.         Assert.AreNotEqual(service1.Id, service2.Id);
  16.     }
  17.     public interface ITestTransientService
  18.     {
  19.         Guid Id { get; }
  20.         void Test();
  21.     }
  22.     public class TestTransientServiceImpl : ITestTransientService, ITransientService
  23.     {
  24.         public TestTransientServiceImpl()
  25.         {
  26.             Id = Guid.NewGuid();
  27.         }
  28.         public Guid Id { get; }
  29.         public void Test() => Console.WriteLine("Hello TestTransientService!");
  30.     }
复制代码
    作用域测试:
  1.     /// <summary>
  2.     /// 测试作用域服务
  3.     /// </summary>
  4.     [TestMethod]
  5.     public void TestScopedService()
  6.     {
  7.         var services = new ServiceCollection();
  8.         var builder = services.AddFireasy();
  9.         var serviceProvider = services.BuildServiceProvider();
  10.         Guid id1, id2;
  11.         //作用域1
  12.         using (var scope1 = serviceProvider.CreateScope())
  13.         {
  14.             var service1 = scope1.ServiceProvider.GetService<ITestScopedService>();
  15.             var service2 = scope1.ServiceProvider.GetService<ITestScopedService>();
  16.             Assert.IsNotNull(service1);
  17.             Assert.IsNotNull(service2);
  18.             //两对象的id应相等
  19.             Assert.AreEqual(service1.Id, service2.Id);
  20.             id1 = service1.Id;
  21.         }
  22.         //作用域2
  23.         using (var scope2 = serviceProvider.CreateScope())
  24.         {
  25.             var service1 = scope2.ServiceProvider.GetService<ITestScopedService>();
  26.             var service2 = scope2.ServiceProvider.GetService<ITestScopedService>();
  27.             Assert.IsNotNull(service1);
  28.             Assert.IsNotNull(service2);
  29.             //两对象的id应相等
  30.             Assert.AreEqual(service1.Id, service2.Id);
  31.             id2 = service1.Id;
  32.         }
  33.         //两次scoped的id应不相等
  34.         Assert.AreNotEqual(id1, id2);
  35.     }
  36.     public interface ITestScopedService
  37.     {
  38.         Guid Id { get; }
  39.         void Test();
  40.     }
  41.     public class TestScopedServiceImpl : ITestScopedService, IScopedService
  42.     {
  43.         public TestScopedServiceImpl()
  44.         {
  45.             Id = Guid.NewGuid();
  46.         }
  47.         public Guid Id { get; }
  48.         public void Test() => Console.WriteLine("Hello TestScopedService!");
  49.     }
复制代码
    可见,不需要显式 Add 也能将大量的服务类注入到容器中,不仅节省了大量的时间和代码,更是提高了程序的可维护性。
    最后,奉上 Fireasy 3 的开源地址:https://gitee.com/faib920/fireasy3 ,欢迎大家前来捧场。
    本文相关代码请参考 https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common/DependencyInjection 下的相关文件。

来源:https://www.cnblogs.com/fireasy/archive/2023/03/01/17170417.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具