Fireasy3 揭秘 -- 依赖注入与服务发现

目录

  • Fireasy3 揭秘 -- 依赖注入与服务发现
  • Fireasy3 揭秘 -- 自动服务部署
  • Fireasy3 揭秘 -- 使用 SourceGeneraor 改进服务发现
  • Fireasy3 揭秘 -- 使用 SourceGeneraor 实现动态代理(AOP)
  • Fireasy3 揭秘 -- 使用 Emit 构建程序集
  • Fireasy3 揭秘 -- 代码编译器及适配器
  • Fireasy3 揭秘 -- 使用缓存提高反射性能
  • Fireasy3 揭秘 -- 动态类型及扩展支持
  • Fireasy3 揭秘 -- 线程数据共享的实现
  • Fireasy3 揭秘 -- 配置管理及解析处理
  • Fireasy3 揭秘 -- 数据库适配器
  • Fireasy3 揭秘 -- 解决数据库之间的语法差异
  • Fireasy3 揭秘 -- 获取数据库的架构信息
  • Fireasy3 揭秘 -- 数据批量插入的实现
  • Fireasy3 揭秘 -- 使用包装器对数据读取进行兼容
  • Fireasy3 揭秘 -- 数据行映射器
  • Fireasy3 揭秘 -- 数据转换器的实现
  • Fireasy3 揭秘 -- 通用序列生成器和雪花生成器的实现
  • Fireasy3 揭秘 -- 命令拦截器的实现
  • Fireasy3 揭秘 -- 数据库主从同步的实现
  • Fireasy3 揭秘 -- 大数据分页的策略
  • Fireasy3 揭秘 -- 数据按需更新及生成实体代理类
  • Fireasy3 揭秘 -- 用对象池技术管理上下文
  • Fireasy3 揭秘 -- Lambda 表达式解析的原理
  • Fireasy3 揭秘 -- 扩展选择的实现
  • Fireasy3 揭秘 -- 按需加载与惰性加载的区别与实现
  • Fireasy3 揭秘 -- 自定义函数的解析与绑定
  • Fireasy3 揭秘 -- 与 MongoDB 进行适配
  • Fireasy3 揭秘 -- 模块化的实现原理

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

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

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

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,我们定义了三个服务接口,分别是 ISingletonServiceITransientService 和 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;
    }

  从上面的代码中可看出,通过在程序集内部查找实现了 ISingletonServiceITransientServiceIScopedService 的类,并将它们添加到 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://github.com/faib920/fireasy3 ,欢迎大家前来捧场。

  本文相关代码请参考:
  https://github.com/faib920/fireasy3/src/libraries/Fireasy.Common/DependencyInjection
  https://github.com/faib920/fireasy3/tests/Fireasy.Common.Tests/DependencyInjectionTests.cs

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

posted @ 2023-03-01 23:53  fireasy  阅读(237)  评论(0编辑  收藏  举报