最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

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

来源:博客园

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


【资料图】

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

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

void ConfigureServices(IServiceCollection services){    services.AddTransient();    services.AddTransient();    services.AddTransient();    services.AddTransient();    //.......    services.AddTransient();    services.AddTransient();}

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

在 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? 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(discoverer);        }        return builder;    }

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

///     /// 发现工作目录中所有程序集中的依赖类型。    ///     ///     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 用于对单个程序集进行服务发现并进行注册,这里同样也有类型过滤器,如下:

///     /// 发现程序集中的所有依赖类型。    ///     ///     ///     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()!.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 生命周期来进行注册,一样达到了目的。

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

单例测试:

///     /// 测试单例服务    ///     [TestMethod]    public void TestSingletonService()    {        var services = new ServiceCollection();        var builder = services.AddFireasy();        var serviceProvider = services.BuildServiceProvider();        var service1 = serviceProvider.GetService();        var service2 = serviceProvider.GetService();        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!");    }

瞬时测试:

///     /// 测试瞬时服务    ///     [TestMethod]    public void TestTransientService()    {        var services = new ServiceCollection();        var builder = services.AddFireasy();        var serviceProvider = services.BuildServiceProvider();        var service1 = serviceProvider.GetService();        var service2 = serviceProvider.GetService();        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!");    }

作用域测试:

///     /// 测试作用域服务    ///     [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();            var service2 = scope1.ServiceProvider.GetService();            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();            var service2 = scope2.ServiceProvider.GetService();            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下的相关文件。

关键词: 的情况下 相关文件 可维护性