专栏首页DotNet Core圈圈ASP.NET Core 中的 ServiceProvider

ASP.NET Core 中的 ServiceProvider

前言#

在 ASP.NET Core 中,微软提供了一套默认的依赖注入实现,该实现对应的包为:Microsoft.Extensions.DependencyInjection,我们可以通过查看其对应的开源仓库看一下它的具体实现。基于该实现,我们不必显式创建我们的服务对象,可以将其统一注入到 ServiceProvider 中进行集中维护,使用的时候直接在该对象中获取即可。让我们在编写业务逻辑时,不用太关注对象的创建和销毁。这也是为什么现在有些最佳实践中建议不要过多使用 New 的方式来获取对象。在本文中,我们将一起了解一下如何实现一个自己的 ServiceProvider。

自己动手,丰衣足食#

为了方便区分,我这里自定义定义的类叫:ServiceLocator,其功能与官方的 ServiceProvider 类似。

基本实现#

首先,我们需要定义一个简单的服务发现接口,用于约束上层具体的实现,示例代码如下所示:

Copy
public interface IServiceLocator
{
    void AddService<T>();
    T GetService<T>();
}

接着,我们定义一个继承上述接口的具体实现类,示例代码如下所示:

Copy
public class ServiceLocator : IServiceLocator
{
    private readonly IDictionary<object, object> services;

    public ServiceLocator()
    {
        services = new Dictionary<object, object>();
    }

    public void AddService<T>()
    {
        services.TryAdd(typeof(T), Activator.CreateInstance<T>());
    }

    public T GetService<T>()
    {
        try
        {
            return (T)services[typeof(T)];
        }
        catch (KeyNotFoundException)
        {
            throw new ApplicationException("The requested service is not registered");
        }
    }
}

这样,我们就实现了一个最基本的服务发现类,通过该类,我们可以将我们的多个服务进行集中管理。这里我为了方便,模拟了 3 个服务类用于注册,示例代码如下所示:

Copy
public interface IService
{
    void SayHello();
}
public class ServiceA : IService
{
    public void SayHello()
    {
        Console.WriteLine("Hello,I'm from A");
    }
}
public class ServiceB : IService
{
    public void SayHello()
    {
        Console.WriteLine("Hello,I'm from B");
    }
}
public class ServiceC : IService
{
    public void SayHello()
    {
        Console.WriteLine("Hello,I'm from C");
    }
}

使用方式就很简单了,如下所示:

Copy
class Program
{
    static void Main(string[] args)
    {
        IServiceLocator locator = new ServiceLocator();
        locator.AddService<ServiceA>();
        locator.AddService<ServiceB>();
        locator.AddService<ServiceC>();


        locator.GetService<ServiceA>().SayHello();
        locator.GetService<ServiceB>().SayHello();
        locator.GetService<ServiceC>().SayHello();
    }
}

程序运行效果如下图所示:

程序看起来运行不错,结果也符合我们的预期。但是稍微有点工作经验的朋友就会发现上述的实现是有很多潜在问题的。对于 IServiceLocator 的实例,我们一般会以单例模式来进行使用,这就会设计到线程安全的委托,所以我们的服务列表必须要是线程安全的。此外,如果我们需要注册的服务过多,通过上述方式来进行注册的话会加到系统开销,因为我们的服务一旦注册进去就会立刻被初始化,从而耗费不必要的系统内存,所以我们应该让其实例化推迟,在使用的时候才进行实例化操作。下面我们对上述问题一一进行改进。

单例模式#

单例模式是一种最简单也是使用最频繁的设计模式,单例模式本身也有很多形式,感兴趣的可以查看我之前的博文:设计模式系列 - 单例模式,这里,我采用 线程安全 方式来修改我们的 ServiceLocator,此外,我们还需要将我们的服务集合类修改为线程安全类型。所以,整个修改完毕后,示例代码如下所示:

Copy
public class ServiceLocator : IServiceLocator
{
    private static ServiceLocator _instance;

    private static readonly object _locker = new object();

    public static ServiceLocator GetInstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new ServiceLocator();
                }
            }
        }

        return _instance;
    }

    private readonly IDictionary<object, object> services;

    private ServiceLocator()
    {
        services = new ConcurrentDictionary<object, object>();
    }

    public void AddService<T>()
    {
        services.TryAdd(typeof(T), Activator.CreateInstance<T>());
    }

    public T GetService<T>()
    {
        try
        {
            return (T)services[typeof(T)];
        }
        catch (KeyNotFoundException)
        {
            throw new ApplicationException("The requested service is not registered");
        }
    }
}

延迟加载#

要想让所有的注册的服务支持懒加载,我们需要引入一个新的集合,这个新的集合是用于存储我们相应的实例对象,在注册的时候我们只记录注册类型,在需要访问到相应的服务时,我们只需要在这个实例集合列表中访问,如果发现我们需要的服务还未被实例化,那我们再进行实例化,然后将该实例化对象存储起来并返回。对于用哪种数据结构来存,我们可以采用多种数据结构,我这里仍然采用字典来存储,示例代码如下所示:

Copy
public class ServiceLocator : IServiceLocator
{
    private static ServiceLocator _instance;

    private static readonly object _locker = new object();

    public static ServiceLocator GetInstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new ServiceLocator();
                }
            }
        }
        return _instance;
    }

    private readonly IDictionary<Type, Type> servicesType;
    private readonly IDictionary<Type, object> instantiatedServices;

    private ServiceLocator()
    {
        servicesType = new ConcurrentDictionary<Type, Type>();
        instantiatedServices = new ConcurrentDictionary<Type, object>();
    }

    public void AddService<T>()
    {
        servicesType.Add(typeof(T), typeof(T));
    }

    public T GetService<T>()
    {
        if (!instantiatedServices.ContainsKey(typeof(T)))
        {
            try
            {
                ConstructorInfo constructor = servicesType[typeof(T)].GetConstructor(new Type[0]);
                Debug.Assert(constructor != null, "Cannot find a suitable constructor for " + typeof(T));
                T service = (T)constructor.Invoke(null);

                instantiatedServices.Add(typeof(T), service);
            }
            catch (KeyNotFoundException)
            {
                throw new ApplicationException("The requested service is not registered");
            }
        }

        return (T)instantiatedServices[typeof(T)];
    }
}

自匹配构造#

上面的所有改进都支持无参构造函数的服务,但是对于有参构造函数的服务注册,我们定义的 服务提供者就不满足的,因为上述的反射方式是不支持有参构造函数的。对于这种情况我们有两种解决办法。第一种是将服务的初始化放到最上层,然后 ServiceLocator 通过一个 Fun 的方式来获取该示例,并存储起来,我们称之为 显示创建。第二种方式依然是通过反射方式,只是这个反射可能会复杂一下,我们称之为 隐式创建。我们分别对于这两个实现方式进行代码示例。

  • 显示构造
Copy
public interface IServiceLocator
{
    void AddService<T>();

    //新增接口
    void AddService<T>(Func<T> Implementation);

    T GetService<T>();
}

public class ServiceLocator : IServiceLocator
{
    private static ServiceLocator _instance;

    private static readonly object _locker = new object();

    public static ServiceLocator GetInstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new ServiceLocator();
                }
            }
        }
        return _instance;
    }

    private readonly IDictionary<Type, Type> servicesType;
    private readonly IDictionary<Type, object> instantiatedServices;

    private ServiceLocator()
    {
        servicesType = new ConcurrentDictionary<Type, Type>();
        instantiatedServices = new ConcurrentDictionary<Type, object>();
    }

    public void AddService<T>()
    {
        servicesType.Add(typeof(T), typeof(T));
    }

    //新增接口对应的具体实现
    public void AddService<T>(Func<T> Implementation)
    {
        servicesType.Add(typeof(T), typeof(T));
        var done = instantiatedServices.TryAdd(typeof(T), Implementation());
        Debug.Assert(done, "Cannot add current service: " + typeof(T));
    }

    public T GetService<T>()
    {
        if (!instantiatedServices.ContainsKey(typeof(T)))
        {
            try
            {
                ConstructorInfo constructor = servicesType[typeof(T)].GetConstructor(new Type[0]);
                Debug.Assert(constructor != null, "Cannot find a suitable constructor for " + typeof(T));
                T service = (T)constructor.Invoke(null);

                instantiatedServices.Add(typeof(T), service);
            }
            catch (KeyNotFoundException)
            {
                throw new ApplicationException("The requested service is not registered");
            }
        }
        return (T)instantiatedServices[typeof(T)];
    }
}

-------------------------------------------------------------------------------------------

public interface IService
{
    void SayHello();
}
public class ServiceA : IService
{
    public void SayHello()
    {
        Console.WriteLine("Hello,I'm from A");
    }
}
public class ServiceB : IService
{
    public void SayHello()
    {
        Console.WriteLine("Hello,I'm from B");
    }
}
public class ServiceC : IService
{
    public void SayHello()
    {
        Console.WriteLine("Hello,I'm from C");
    }
}

public class ServiceD : IService
{
    private readonly IService _service;

    public ServiceD(IService service)
    {
        _service = service;
    }
    public void SayHello()
    {
        Console.WriteLine("-------------");
        _service.SayHello();
        Console.WriteLine("Hello,I'm from D");
    }
}

-------------------------------------------------------------------------------------------

class Program
{
    static void Main(string[] args)
    {
        IServiceLocator locator = ServiceLocator.GetInstance();
        locator.AddService<ServiceA>();
        locator.AddService<ServiceB>();
        locator.AddService<ServiceC>();

        locator.GetService<ServiceA>().SayHello();
        locator.GetService<ServiceB>().SayHello();
        locator.GetService<ServiceC>().SayHello();

        locator.AddService(() => new ServiceD(locator.GetService<ServiceA>()));
        locator.GetService<ServiceD>().SayHello();
    }
}

程序输出如下图所示:

当我们需要注册的服务对应的有参构造函数中的参数不需要注册到 ServiceLocator,那我们可以采用这种方法进行服务注册,比较灵活。

  • 隐式构造
Copy
public class ServiceLocator : IServiceLocator
{
    private static ServiceLocator _instance;

    private static readonly object _locker = new object();

    public static ServiceLocator GetInstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new ServiceLocator();
                }
            }
        }
        return _instance;
    }

    private readonly IDictionary<Type, Type> servicesType;
    private readonly IDictionary<Type, object> instantiatedServices;

    private ServiceLocator()
    {
        servicesType = new ConcurrentDictionary<Type, Type>();
        instantiatedServices = new ConcurrentDictionary<Type, object>();
    }

    public void AddService<T>()
    {
        servicesType.Add(typeof(T), typeof(T));
    }
    public void AddService<T>(Func<T> Implementation)
    {
        servicesType.Add(typeof(T), typeof(T));
        var done = instantiatedServices.TryAdd(typeof(T), Implementation());
        Debug.Assert(done, "Cannot add current service: " + typeof(T));
    }

    public T GetService<T>()
    {
        var service = (T)GetService(typeof(T));
        if (service == null)
        {
            throw new ApplicationException("The requested service is not registered");
        }
        return service;
    }

    /// <summary>
    /// 关键代码
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    private object GetService(Type type)
    {
        if (!instantiatedServices.ContainsKey(type))
        {
            try
            {
                ConstructorInfo constructor = servicesType[type].GetTypeInfo().DeclaredConstructors
                                            .Where(constructor => constructor.IsPublic).FirstOrDefault();
                ParameterInfo[] ps = constructor.GetParameters();

                List<object> parameters = new List<object>();
                for (int i = 0; i < ps.Length; i++)
                {
                    ParameterInfo item = ps[i];
                    bool done = instantiatedServices.TryGetValue(item.ParameterType, out object parameter);
                    if (!done)
                    {
                        parameter = GetService(item.ParameterType);
                    }
                    parameters.Add(parameter);
                }

                object service = constructor.Invoke(parameters.ToArray());

                instantiatedServices.Add(type, service);
            }
            catch (KeyNotFoundException)
            {
                throw new ApplicationException("The requested service is not registered");
            }
        }
        return instantiatedServices[type];
    }
}

-------------------------------------------------------------------------------------------

public interface IService
{
    void SayHello();
}
public class ServiceA : IService
{
    public void SayHello()
    {
        Console.WriteLine("Hello,I'm from A");
    }
}
public class ServiceD : IService
{
    private readonly ServiceA _service;

    public ServiceD(ServiceA service)
    {
        _service = service;
    }
    public void SayHello()
    {
        Console.WriteLine("-------------");
        _service.SayHello();
        Console.WriteLine("Hello,I'm from D");
    }
}

-------------------------------------------------------------------------------------------

class Program
{
    static void Main(string[] args)
    {
        IServiceLocator locator = ServiceLocator.GetInstance();
        locator.AddService<ServiceD>();
        locator.AddService<ServiceA>();
        locator.GetService<ServiceD>().SayHello();

        locator.GetService<ServiceA>().SayHello();
    }
}

程序输入如下图所示:

通过隐式构造的方式可以将我们待注册的服务依据其对应的构造函数参数类型来动态创建,这和 DotNetCore 中的 ServiceProvider 的方式很相似,它不依赖于我们服务的注册顺序,都能正常的进行构造。

官方实现#

上面我们通过自己手动实现了一个 ServiceLocator 大致明白了其中的实现思路,所以有必要看一下官方是如何实现的。

首先,在使用方式上,我们一般这么使用,示例代码如下所示:

Copy
var services= new ServiceCollection();

......
services.AddSingleton(Configuration.GetSection(nameof(AppSettings)).Get<AppSettings>());
......

ServiceProvider serviceProvider = services.BuildServiceProvider();

可以看到,最终我们是通过一个 ServiceProvider 来获取我们的服务提供对象,该类对应官方的源码实现如下图所示:

通过源码我们不难看出,所有的服务对象都是注册进了一个 IServiceProviderEngine 类型的对象,而该对象的具体类型又是根据 ServiceProviderOptions 的方式来进行创建。这里,有两个类我们需要着重注意一下:

  • ServiceProvider
  • CallSiteFactory

前者负责上层注入,后者负责底层构建,所以如果你想看一下官方是如何实例化这些注入对象的话可以看一下对应的实现代码。

总结#

如果你看完了我上面所有的代码示例,回头想想,其实一点都不难,要是自己写的话,也是可以写出来的。但是在实际工作中,能够活学或用的人却很少,归根到底就是思维方式的问题。官方也是通过反射来实现的,只不过他的内部逻辑会更严谨一些,这就导致了他的实现会更复杂一些,这也是里所当然的事情。

本文分享自微信公众号 - DotNet技术平台(DotNetCore_Mements),作者:hippie

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ASP.NET Core[源码分析篇] - Authentication认证

      首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务,这里通过JWT的认证方式讲解

    Edison.Ma
  • .net持续集成单元测试篇之单元测试简介以及在visual studio中配置Nunit使用环境

    单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后对这个单元的单个最终结果的某些假设进行检验。单元测试几乎都是用单元测试框架编写的。单元测试容易编写...

    Edison.Ma
  • .NET中的值类型与引用类型

    这是一个常见面试题,值类型(Value Type)和引用类型(Reference Type)有什么区别?他们性能方面有什么区别?

    Edison.Ma
  • 在线微信用户IP获取

    周俊辉
  • GTK、KDE、Gnome、XWindows 图形界面

    一、linux图形界面的实现只是linux下的应用程序实现 图形界面(GUI)并不是linux的一部分,linux只是一个基于命令行的操作系统,linux和Xf...

    阳光岛主
  • linux防火墙配置

    东营浪人
  • 富士康又宣布给AI投资3.4亿美元,“百万机器人计划”没有消息?

    曾经,富士康的“百万机器人计划”震惊世界,尽管期间机器人应用有了较大的进步,但最后无奈承认“已几乎不可能在短时间内实现”。最近,富士康又宣布,未来五年在人工智能...

    机器人网
  • 构建Flink第一个应用程序

    Flink 可以运行在 Linux、Mac 以及 Windows 上。在这我们使用的是 Mac 系统。为了开发 Flink 应用程序,需要提前安装 Java 和...

    smartsi
  • Python一行代码实现一个文件服务器

    但是这个文件服务器是单线程的,意味着如果多个用户访问会被阻塞,同时只能一个用户访问

    py3study
  • FileSystemWatcher 导致Mono ASP.NET应用程序CPU使用率比较高

    大家都知道ASP.NET 网站应用程序(WebSite)可以自动检测到你的ASP.NET应用的文件修改,其中要使用到的就是监视磁盘上的文件/目录的更改,以便应用...

    张善友

扫码关注云+社区

领取腾讯云代金券