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

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

Intro

之前实现的那版依赖注入框架基本可用,但是感觉还是不够灵活,而且注册服务和解析服务在同一个地方感觉有点别扭,有点职责分离不够。于是借鉴 Autofac 的做法,增加了一个 ServiceContainerBuilder 来负责注册服务,ServiceContainer负责解析服务,并且增加了一个 ServiceContainerModule 可以支持像 Autofac 中 Module/RegisterAssemblyModules 一样注册服务

实现代码

ServiceContainerBuilder

增加 ServiceContainerBuild 来专门负责注册服务,原来注册服务的那些扩展方法则从 IServiceContainer 的扩展方法变成 IServiceContainerBuilder 的扩展

public interface IServiceContainerBuilder
{
    IServiceContainerBuilder Add(ServiceDefinition item);

    IServiceContainerBuilder TryAdd(ServiceDefinition item);

    IServiceContainer Build();
}

public class ServiceContainerBuilder : IServiceContainerBuilder
{
    private readonly List<ServiceDefinition> _services = new List<ServiceDefinition>();

    public IServiceContainerBuilder Add(ServiceDefinition item)
    {
        if (_services.Any(_ => _.ServiceType == item.ServiceType && _.GetImplementType() == item.GetImplementType()))
        {
            return this;
        }

        _services.Add(item);
        return this;
    }

    public IServiceContainerBuilder TryAdd(ServiceDefinition item)
    {
        if (_services.Any(_ => _.ServiceType == item.ServiceType))
        {
            return this;
        }
        _services.Add(item);
        return this;
    }

    public IServiceContainer Build() => new ServiceContainer(_services);
}

IServiceContainer

增加 ServiceContainerBuilder 之后就不再支持注册服务了,ServiceContainer 这个类型也可以变成一个内部类了,不必再对外暴露

public interface IServiceContainer : IScope, IServiceProvider
{
    IServiceContainer CreateScope();
}

internal class ServiceContainer : IServiceContainer
{
    private readonly IReadOnlyList<ServiceDefinition> _services;
    
    public ServiceContainer(IReadOnlyList<ServiceDefinition> serviceDefinitions)
    {
        _services = serviceDefinitions;
        // ...
    }
    
    // 此处约省略一万行代码 ...
}

ServiceContainerModule

定义了一个 ServiceContainerModule 来实现像 Autofac 那样,在某一个程序集内定义一个 Module 注册程序集内需要注册的服务,在服务注册的地方调用 RegisterAssemblyModules 来扫描所有程序集并注册自定义 ServiceContainerModule 需要注册的服务

public interface IServiceContainerModule
{
    void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);
}

public abstract class ServiceContainerModule : IServiceContainerModule
{
    public abstract void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);
}

自定义 ServiceContainerModule 使用示例:

public class TestServiceContainerModule : ServiceContainerModule
{
    public override void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder)
    {
        serviceContainerBuilder.AddSingleton<IIdGenerator>(GuidIdGenerator.Instance);
    }
}

RegisterAssemblyModules 扩展方法实现如下:


public static IServiceContainerBuilder RegisterAssemblyModules(
    [NotNull] this IServiceContainerBuilder serviceContainerBuilder, params Assembly[] assemblies)
{
    #if NET45
        // 解决 asp.net 在 IIS 下应用程序域被回收的问题
        // https://autofac.readthedocs.io/en/latest/register/scanning.html#iis-hosted-web-applications
        if (null == assemblies || assemblies.Length == 0)
        {
            if (System.Web.Hosting.HostingEnvironment.IsHosted)
            {
                assemblies = System.Web.Compilation.BuildManager.GetReferencedAssemblies()
                    .Cast<Assembly>().ToArray();
            }
        }
    #endif

        if (null == assemblies || assemblies.Length == 0)
        {
            assemblies = AppDomain.CurrentDomain.GetAssemblies();
        }

    foreach (var type in assemblies.WhereNotNull().SelectMany(ass => ass.GetTypes())
             .Where(t => t.IsClass && !t.IsAbstract && typeof(IServiceContainerModule).IsAssignableFrom(t))
            )
    {
        try
        {
            if (Activator.CreateInstance(type) is ServiceContainerModule module)
            {
                module.ConfigureServices(serviceContainerBuilder);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
    return serviceContainerBuilder;
}

使用示例

使用起来除了注册服务变化了之外,别的地方并没有什么不同,看一下单元测试代码

public class DependencyInjectionTest : IDisposable
{
    private readonly IServiceContainer _container;

    public DependencyInjectionTest()
    {
        var containerBuilder = new ServiceContainerBuilder();
        containerBuilder.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
        containerBuilder.AddScoped<IFly, MonkeyKing>();
        containerBuilder.AddScoped<IFly, Superman>();

        containerBuilder.AddScoped<HasDependencyTest>();
        containerBuilder.AddScoped<HasDependencyTest1>();
        containerBuilder.AddScoped<HasDependencyTest2>();
        containerBuilder.AddScoped<HasDependencyTest3>();
        containerBuilder.AddScoped(typeof(HasDependencyTest4<>));

        containerBuilder.AddTransient<WuKong>();
        containerBuilder.AddScoped<WuJing>(serviceProvider => new WuJing());
        containerBuilder.AddSingleton(typeof(GenericServiceTest<>));
        
        containerBuilder.RegisterAssemblyModules();

        _container = containerBuilder.Build();
    }

    [Fact]
    public void Test()
    {
        var rootConfig = _container.ResolveService<IConfiguration>();

        Assert.Throws<InvalidOperationException>(() => _container.ResolveService<IFly>());
        Assert.Throws<InvalidOperationException>(() => _container.ResolveRequiredService<IDependencyResolver>());

        using (var scope = _container.CreateScope())
        {
            var config = scope.ResolveService<IConfiguration>();

            Assert.Equal(rootConfig, config);

            var fly1 = scope.ResolveRequiredService<IFly>();
            var fly2 = scope.ResolveRequiredService<IFly>();
            Assert.Equal(fly1, fly2);

            var wukong1 = scope.ResolveRequiredService<WuKong>();
            var wukong2 = scope.ResolveRequiredService<WuKong>();

            Assert.NotEqual(wukong1, wukong2);

            var wuJing1 = scope.ResolveRequiredService<WuJing>();
            var wuJing2 = scope.ResolveRequiredService<WuJing>();

            Assert.Equal(wuJing1, wuJing2);

            var s0 = scope.ResolveRequiredService<HasDependencyTest>();
            s0.Test();
            Assert.Equal(s0._fly, fly1);

            var s1 = scope.ResolveRequiredService<HasDependencyTest1>();
            s1.Test();

            var s2 = scope.ResolveRequiredService<HasDependencyTest2>();
            s2.Test();

            var s3 = scope.ResolveRequiredService<HasDependencyTest3>();
            s3.Test();

            var s4 = scope.ResolveRequiredService<HasDependencyTest4<string>>();
            s4.Test();

            using (var innerScope = scope.CreateScope())
            {
                var config2 = innerScope.ResolveRequiredService<IConfiguration>();
                Assert.True(rootConfig == config2);

                var fly3 = innerScope.ResolveRequiredService<IFly>();
                fly3.Fly();

                Assert.NotEqual(fly1, fly3);
            }

            var flySvcs = scope.ResolveServices<IFly>();
            foreach (var f in flySvcs)
                f.Fly();
        }

        var genericService1 = _container.ResolveRequiredService<GenericServiceTest<int>>();
        genericService1.Test();

        var genericService2 = _container.ResolveRequiredService<GenericServiceTest<string>>();
        genericService2.Test();
    }

    public void Dispose()
    {
        _container.Dispose();
    }
}

Reference

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353