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

  • Fireasy3 揭秘 — 万物伊始(依赖注入与服务发现)已关闭评论
  • 100 次浏览
  • A+
所属分类:.NET技术
摘要

    最近在忙于 Fireasy 的重构,3.x 抛弃了 .Net Framework 时代的一些思想和模式,紧密拥抱 .Net Core,但它的思想仍然是开放性和灵活性。今天我主要来说说依赖注入与服务发现。

    最近在忙于 Fireasy 的重构,3.x 抛弃了 .Net Framework 时代的一些思想和模式,紧密拥抱 .Net Core,但它的思想仍然是开放性和灵活性。今天我主要来说说依赖注入与服务发现。

    .Net Core 有自己的一套依赖注入,它的容器暴露给 IServiceCollection,通过在里面放入一些单例(Singleton)、瞬时(Transient)、作用域(Scoped)的一些服务描述(服务与实现的关系映射),这一部分我就不再细说了。

    当然,一般常用的方式是,通过 AddSingleton、AddTransient 和 AddScoped 方法往容器里面加,但如果是依赖比较多的情况下(比如业务服务类),那你可能会经常忘了写这一部分代码了,而且也很难于维护。如常见的方式:

void ConfigureServices(IServiceCollection services) {     services.AddTransient<IDeptmentService, DeptmentService>();     services.AddTransient<IRoleService, RoleService>();     services.AddTransient<IUserService, UserService>();     services.AddTransient<IDataRoleService, DataRoleService>();     //.......     services.AddTransient<IProcessService, ProcessService>();     services.AddTransient<IWorkService, WorkService>(); }

    有没有更简便更容易维护的方式呢?答案是当然有!

    在 Fireasy,我们定义了三个服务接口,分别是 ISingletonService、ITransientService 和 IScopedService,这三个类只是一个标识,没有具体的方法和属性。使用需要注入的类实现此接口,如下:

public class DeptmentService : IDeptmentService, ITransientService {     // ...... }  public class DataRoleHelper : IDataRoleHelper, ISingletonService {     // ...... }

    好了,你只需在 ConfigureServices 里添加上这么一行代码,就能实现依赖注入:

void ConfigureServices(IServiceCollection services) {     services.AddFireasy(); }

    现在开始步入正题了,来看看 AddFireasy 是如何工作的。

    IServiceDiscoverer 是用于服务发现的接口,它的默认实现是 DefaultServiceDiscoverer。如下:

    public static SetupBuilder AddFireasy(this IServiceCollection services, Action<SetupOptions>? configure = null)     {         var options = new SetupOptions();         configure?.Invoke(options);          var builder = new SetupBuilder(services, options);          var discoverer = options.DiscoverOptions.DiscovererFactory == null ? new DefaultServiceDiscoverer(services, options.DiscoverOptions)             : options.DiscoverOptions.DiscovererFactory(services, options.DiscoverOptions);          if (discoverer != null)         {             services.AddSingleton<IServiceDiscoverer>(discoverer);         }          return builder;     }

    入口方法是 DiscoverServices,它会遍列程序目录下的所有程序集文件(*.dll),这里有程序集过滤器,你可以自己定义过滤规则。如下:

    /// <summary>     /// 发现工作目录中所有程序集中的依赖类型。     /// </summary>     /// <param name="services"></param>     private void DiscoverServices(IServiceCollection services)     {         foreach (var assembly in GetAssemblies())         {             if (_options?.AssemblyFilters?.Any(s => s.IsFilter(assembly)) == true)             {                 continue;             }              if (_options?.AssemblyFilterPredicates?.Any(s => s(assembly)) == true)             {                 continue;             }              _assemblies.Add(assembly);              ConfigureServices(services, assembly);             DiscoverServices(services, assembly);         }     }

    方法 DiscoverServices 用于对单个程序集进行服务发现并进行注册,这里同样也有类型过滤器,如下:

    /// <summary>     /// 发现程序集中的所有依赖类型。     /// </summary>     /// <param name="services"></param>     /// <param name="assembly"></param>     private void DiscoverServices(IServiceCollection services, Assembly assembly)     {         foreach (var type in assembly.GetExportedTypes())         {             if (_options?.TypeFilters?.Any(s => s.IsFilter(assembly, type)) == true)             {                 continue;             }              if (_options?.TypeFilterPredicates?.Any(s => s(assembly, type)) == true)             {                 continue;             }              ServiceLifetime? lifetime;             var interfaceTypes = type.GetDirectImplementInterfaces().ToArray();              //如果使用标注             if (type.IsDefined(typeof(ServiceRegisterAttribute)))             {                 lifetime = type.GetCustomAttribute<ServiceRegisterAttribute>()!.Lifetime;             }             else             {                 lifetime = GetLifetimeFromType(type);             }              if (lifetime == null)             {                 continue;             }              if (interfaceTypes.Length > 0)             {                 interfaceTypes.ForEach(s => AddService(services, s, type, (ServiceLifetime)lifetime));             }             else             {                 AddService(services, type, type, (ServiceLifetime)lifetime);             }         }     }      private ServiceLifetime? GetLifetimeFromType(Type type)     {         if (typeof(ISingletonService).IsAssignableFrom(type))         {             return ServiceLifetime.Singleton;         }         else if (typeof(ITransientService).IsAssignableFrom(type))         {             return ServiceLifetime.Transient;         }         else if (typeof(IScopedService).IsAssignableFrom(type))         {             return ServiceLifetime.Scoped;         }          return null;     }      private ServiceDescriptor AddService(IServiceCollection services, Type serviceType, Type implType, ServiceLifetime lifetime)     {         var descriptor = ServiceDescriptor.Describe(serviceType, implType, lifetime);         _descriptors.Add(descriptor);         services.Add(descriptor);         return descriptor;     }

    从上面的代码中可看出,通过在程序集内部查找实现了 ISingletonService、ITransientService 或 IScopedService 的类,并将它们添加到 services 中,这样就完成了开篇提到的工作。

    这里还出现了一个 ServiceRegisterAttribute,它在不实现以上三个接口的情况下,通过标注 Lifetime 生命周期来进行注册,一样达到了目的。

    接下来做几个简单的单元测试。

    单例测试:

    /// <summary>     /// 测试单例服务     /// </summary>     [TestMethod]     public void TestSingletonService()     {         var services = new ServiceCollection();          var builder = services.AddFireasy();          var serviceProvider = services.BuildServiceProvider();          var service1 = serviceProvider.GetService<ITestSingletonService>();         var service2 = serviceProvider.GetService<ITestSingletonService>();          Assert.IsNotNull(service1);         Assert.IsNotNull(service2);          //两对象的id应相等         Assert.AreEqual(service1.Id, service2.Id);     }      public interface ITestSingletonService     {         Guid Id { get; }          void Test();     }      public class TestSingletonServiceImpl : ITestSingletonService, ISingletonService     {         public TestSingletonServiceImpl()         {             Id = Guid.NewGuid();         }          public Guid Id { get; }          public void Test() => Console.WriteLine("Hello TestSingletonService!");     }

    瞬时测试:

    /// <summary>     /// 测试瞬时服务     /// </summary>     [TestMethod]     public void TestTransientService()     {         var services = new ServiceCollection();          var builder = services.AddFireasy();          var serviceProvider = services.BuildServiceProvider();          var service1 = serviceProvider.GetService<ITestTransientService>();         var service2 = serviceProvider.GetService<ITestTransientService>();          Assert.IsNotNull(service1);         Assert.IsNotNull(service2);          //两对象的id应不相等         Assert.AreNotEqual(service1.Id, service2.Id);     }      public interface ITestTransientService     {         Guid Id { get; }          void Test();     }      public class TestTransientServiceImpl : ITestTransientService, ITransientService     {         public TestTransientServiceImpl()         {             Id = Guid.NewGuid();         }          public Guid Id { get; }          public void Test() => Console.WriteLine("Hello TestTransientService!");     }

    作用域测试:

    /// <summary>     /// 测试作用域服务     /// </summary>     [TestMethod]     public void TestScopedService()     {         var services = new ServiceCollection();          var builder = services.AddFireasy();          var serviceProvider = services.BuildServiceProvider();          Guid id1, id2;          //作用域1         using (var scope1 = serviceProvider.CreateScope())         {             var service1 = scope1.ServiceProvider.GetService<ITestScopedService>();             var service2 = scope1.ServiceProvider.GetService<ITestScopedService>();              Assert.IsNotNull(service1);             Assert.IsNotNull(service2);              //两对象的id应相等             Assert.AreEqual(service1.Id, service2.Id);              id1 = service1.Id;         }          //作用域2         using (var scope2 = serviceProvider.CreateScope())         {             var service1 = scope2.ServiceProvider.GetService<ITestScopedService>();             var service2 = scope2.ServiceProvider.GetService<ITestScopedService>();              Assert.IsNotNull(service1);             Assert.IsNotNull(service2);              //两对象的id应相等             Assert.AreEqual(service1.Id, service2.Id);              id2 = service1.Id;         }          //两次scoped的id应不相等         Assert.AreNotEqual(id1, id2);     }      public interface ITestScopedService     {         Guid Id { get; }          void Test();     }      public class TestScopedServiceImpl : ITestScopedService, IScopedService     {         public TestScopedServiceImpl()         {             Id = Guid.NewGuid();         }          public Guid Id { get; }          public void Test() => Console.WriteLine("Hello TestScopedService!");     }

    可见,不需要显式 Add 也能将大量的服务类注入到容器中,不仅节省了大量的时间和代码,更是提高了程序的可维护性。

    最后,奉上 Fireasy 3 的开源地址:https://gitee.com/faib920/fireasy3 ,欢迎大家前来捧场。

    本文相关代码请参考 https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common/DependencyInjection 下的相关文件。

    更多内容请移步官网 http://www.fireasy.cn 。