前言

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

自己动手,丰衣足食

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

基本实现

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

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

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

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 个服务类用于注册,示例代码如下所示:

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");
}
}

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

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,此外,我们还需要将我们的服务集合类修改为线程安全类型。所以,整个修改完毕后,示例代码如下所示:

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");
}
}
}

延迟加载

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

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 的方式来获取该示例,并存储起来,我们称之为 显式创建。第二种方式依然是通过反射方式,只是这个反射可能会复杂一下,我们称之为 隐式创建。我们分别对于这两个实现方式进行代码示例。

  • 显式构造
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,那我们可以采用这种方法进行服务注册,比较灵活。

  • 隐式构造
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 大致明白了其中的实现思路,所以有必要看一下官方是如何实现的。

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

var services= new ServiceCollection();

......
services.AddSingleton(Configuration.GetSection(nameof(AppSettings)).Get<AppSettings>());
...... ServiceProvider serviceProvider = services.BuildServiceProvider();

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

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

  • ServiceProvider
  • CallSiteFactory

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

总结

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

题外话

CLICK ME(玻璃心请勿点击)

本来我不是太想说这件事情,但是想想还是有必要说一下。

  • 首先,我这人最烦 高级黑小粉红。在最开始的那几年,我天真地以为技术圈和其他行业比起来,要相对好一些,因为这个圈子里都是搞技术的朋友。可过了几年之后,我的这种想法确实被打脸了。所以我现在看到一些事,遇到一些人,多少感觉挺悲哀的。说实话,这几年,我遇到过很多 伪程序员,他们大多喜欢 拿来主义,从来不会自己 主动研究问题。看见别人做什么,自己就跟着做什么,遇到不会的,搞不定的要么甩锅,要么放过。如果他来 请教 你,你对他说了解决思路,然后让他自己花时间研究研究,他就不高兴了,会给你 贴标签。对此我想说的是,当你走出校门步入社会开始工作后,除了你父母外,没有任何人有责任和义务免费给你进行专门指导,学习是自己的事情,一切都是事在人为。也罢,我也懒得解释,你开心就好。
  • 其次,我个人觉得抄代码这件事情不丢人。自己经验不足,看看老前辈之前优秀的代码,拿来学习学习,理解并明白前辈这样设计的优缺点,并把它转化为自己的。当以后再遇到一些业务场景能够活学活用就可以了,这种行为我不反对,并且我个人一直都是这样的。但是有一点我接受不了,抄袭但不注明出处的行为,只要你的文章灵感有来源于其他地方,无论质量如何,请务必引入相关出处,否则这和剽窃没什么区别。我当时在博客园看到一篇首页文章的标题和内容后真不知道该说什么。这里我就不指出该文章的链接,还望好自为之。总之,希望每一个技术人不要把写博客这件事情玩变味了。

共勉!

相关参考

ASP.NET Core 中的 ServiceProvider的更多相关文章

  1. ASP.NET Core中的依赖注入(3): 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

  2. ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】

    本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...

  3. ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】

    通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最 ...

  4. ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理

    ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...

  5. ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】

    到目前为止,我们定义的ServiceProvider已经实现了基本的服务提供和回收功能,但是依然漏掉了一些必需的细节特性.这些特性包括如何针对IServiceProvider接口提供一个Service ...

  6. ASP.NET Core 中的依赖注入 [共7篇]

    一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了 ...

  7. 在ASP.NET Core中怎么使用HttpContext.Current

    一.前言 我们都知道,ASP.NET Core作为最新的框架,在MVC5和ASP.NET WebForm的基础上做了大量的重构.如果我们想使用以前版本中的HttpContext.Current的话,目 ...

  8. ASP.NET Core中的ActionFilter与DI

    一.简介 前几篇文章都是讲ASP.NET Core MVC中的依赖注入(DI)与扩展点的,也许大家都发现在ASP.NET CORE中所有的组件都是通过依赖注入来扩展的,而且面向一组功能就会有一组接口或 ...

  9. ASP.NET Core中使用Razor视图引擎渲染视图为字符串

    一.前言 在有些项目需求上或许需要根据模板生产静态页面,那么你一样可以用Razor语法去直接解析你的页面从而把解析的页面生成静态页,这样的使用场景很多,不限于生成静态页面,视图引擎为我们提供了模型到视 ...

随机推荐

  1. 我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3cp8ng15g94wc

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3cp8ng15g94wc

  2. 原生 js基础常用的判断和循环

    原生 js基础常用的判断和循环 以下部分是个人实践及和搜集的资料: 最常用的if判断语句: if (/* 条件表达式 */){ // 成立执行语句 } else { // 否则执行语句 } 原生js的 ...

  3. MyBatis-Plus 使用说明介绍

    先看一下和MyBatis 不同点说明: @GetMapping("/select_sql") public Object getUserBySql() { User user=ne ...

  4. c的格式输出“%”

  5. web设计_5_自由的框式组件

    1. CSS3 border-radius 圆角矩形框 圆角矩形框组件是页面布局中常常用到的,利用CSS3的border-radius可非常方便的创建. 并且在横向纵向上面都有很好的扩展性和灵活性. ...

  6. java名词

    1 applet Java语言编写的小程序,可以包含在html页面中,有支持Java语言的浏览器执行,作用是在页面产生动态效果. 2 jdk java development kit java 开发环 ...

  7. jdk1.8HashMap底层数据结构:散列表+链表+红黑树,jdk1.8HashMap数据结构图解+源码说明

    一.前言 本文由jdk1.8源码整理而得,附自制jdk1.8底层数据结构图,并截取部分源码加以说明结构关系. 二.jdk1.8 HashMap底层数据结构图 三.源码 1.散列表(Hash table ...

  8. 【Java例题】2.3 计算银行存款本息

    3.计算银行存款本息. 用户输入存款金额money,存款期years和年利率rate, 根据公式: sum=money(1+rate)^years ,计算到期存款本息. 这里的"^" ...

  9. 【原创】POI操作Excel导入导出工具类ExcelUtil

    关于本类线程安全性的解释: 多数工具方法不涉及共享变量问题,至于添加合并单元格方法addMergeArea,使用ThreadLocal变量存储合并数据,ThreadLocal内部借用Thread.Th ...

  10. Centos安装git并配置ssh

    1.下载git安装包 git-2.9.4.tar.gz 2.解压 tar -xzvf git-2.9.4.tar.gz 3.修改解压后的文件名 mv git-2.9.4 git 4.安装git依赖的库 ...