简单来讲,使用Ioc模式须要两个步骤,第一是把服务注册到容器中,第二是从容器中获取服务,咱们一个一个讨论并演化。这里不会考虑使用如Autofac等第三方的容器来代替默认容器,只是提供一些简单实用的小方法用于简化应用层的开发。java
asp.netcore官方给出的在容器中注册服务方法是,要在Startup类的ConfigureServices方法中添加服务,以下所示:git
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton(typeof(UserService)); services.AddSingleton(typeof(MsgService)); services.AddSingleton(typeof(OrderService)); }
AddMvc方法添加了mvc模块内部用到的一些服务,这个是封装好的,一句话就好了,其余第三方组件也都提供了相似的Add方法,把本身内部须要的服务都封装好注册进去了。可是咱们应用开发人员使用的类,仍是须要一个一个写进去的,你们最多见的三层架构中的数据访问层和业务逻辑层即是此类服务,上面代码中我加入了三个业务服务类。这显然不是长久之计,我想你们在开发中也会针对此问题作一些处理,这里说下个人,仅供参考吧。spring
解决方法就是批量注册!说到批量,就须要一个东西来标识一批东西,而后用这一个东西来控制这一批东西。在.net程序的世界中,有两个可选的角色,一个是接口Interface,另外一个是特性Attribute。编程
若是使用接口做为标识来使用,限制就太死板了,一个标识的信息不是绝对的单一,是不推荐使用接口的,由于可能须要引入多个接口才能共同完成,因此我选择特性做为标识。特性相较与接口有什么特色呢?特性在运行时是类的实例,因此能够存储更多的信息。设计模式
下面咱们简单实现一个AppServiceAttribute:缓存
/// <summary> /// 标记服务 /// </summary> [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class AppServiceAttribute : Attribute { }
这个特性类取名AppService有两个理由,一是指定是应用层的服务类,二是避免使用Service这样的通用命名和其余类库冲突。架构
有了标识,就能够批量处理了,咱们在一个新的类中给IServiceCollection提供一个扩展方法,用来批量添加标记有AppService特性的服务到容器中。mvc
public static class AppServiceExtensions { /// <summary> /// 注册应用程序域中全部有AppService特性的服务 /// </summary> /// <param name="services"></param> public static void AddAppServices(this IServiceCollection services) { foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in assembly.GetTypes()) { var serviceAttribute = type.GetCustomAttribute<AppServiceAttribute>(); if (serviceAttribute != null) { services.AddSingleton(type); } } } } }
咱们遍历应用程序中全部程序集,而后嵌套遍历每一个程序集中的全部类型,判断类型是否有AppService特性,若是有的话就添加到容器中,这里有点不自信哦,为何呢,由于我是使用AddSingleton方法以单例模式将服务添加到容器中的,虽然三层中的数据访问层和业务逻辑层绝大部分均可以使用单例,可是咱们但愿更通用一些,你们都知道netcore自带的Ioc容器支持三种生命周期,因此咱们修改AppServiceAttribute,添加一个Lifetime属性:app
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class AppServiceAttribute : Attribute { /// <summary> /// 生命周期 /// </summary> public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Singleton; }
Lifetime的默认值咱们设置成ServiceLifetime.Singleton是比较合适的,由于大部分服务咱们都但愿使用单例注册,一个合理的默认设置能够节省使用者不少代码,新手可能还会乐于复制粘贴,但老同志确定都深有体会。框架
有了Lifetime这个信息,咱们就能够改进AddAppServices方法了,在判断serviceAttribute不为null后,使用下面的代码替换services.AddSingleton(type):
switch (serviceAttribute.Lifetime) { case ServiceLifetime.Singleton: services.AddSingleton(serviceType, type); break; case ServiceLifetime.Scoped: services.AddScoped(serviceType, type); break; case ServiceLifetime.Transient: services.AddTransient(serviceType, type); break; default: break; }
如今咱们能够注册不一样生命周期的服务了,只是该控制是在类的定义中,按理说,服务对象注册到容器中的生命周期,是不该该在类的定义中肯定的,由于一个类的定义是独立的,定义好以后,使用者能够用任何一种容器支持的生命周期来注册实例。可是此时这样的设计是比较合理的,由于咱们要解决的是应用层服务的批量注册,这类服务通常在定义的时候就已经肯定了使用方式,并且不少时候服务的开发者就是该服务的使用者!因此咱们能够把这个当成合理的反范式设计。
目前这样子,对于我来讲,基本已经够用了,由于在应用层,我都是依赖实现编程的😀(哈哈,会不会不少人说咦......呢?)。设计模式说:“要依赖于抽象,不要依赖于具体”,这点我还没作到,我抽空反省(呵呵,谁信呢!)。因此呢,咱们的批量注入要支持那些优秀的同窗。
从上面的代码不难发现,若是定义接口IA和其实现A:IA,并在A上添加AppService特性是不行的:
public interface IA { } [AppService] public class A : IA { }
这个时候咱们并不能依赖IA编程,由于咱们注册的服务类是A,实现类是A,咱们须要注册成服务类是IA,实现类是A才可:
public class HomeController : Controller { private IA a; public HomeController(IA a) { this.a = a; //这里a是null,不能使用 } }
让我继续改进,在AppServiceAttribute中,咱们加入服务类型的信息:
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class AppServiceAttribute : Attribute { /// <summary> /// 生命周期 /// </summary> public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Singleton; /// <summary> /// 指定服务类型 /// </summary> public Type ServiceType { get; set; } /// <summary> /// 是否能够从第一个接口获取服务类型 /// </summary> public bool InterfaceServiceType { get; set; } = true; }
咱们从两个方面入手来解决服务类型的问题,一个是指定ServiceType,这个就毫无疑问了,在A的AppService中能够明确指定IA为其服务类:
[AppService(ServiceType = typeof(IA))] public class A : IA { }
另外一个是从服务类自身所继承的接口中获取服务类形,这一点要在AddAppServices方法中体现了,再次改进AddAppServices方法,仍是替换最开始services.AddSingleton(type)的位置:
var serviceType = serviceAttribute.ServiceType; if (serviceType == null && serviceAttribute.InterfaceServiceType) { serviceType = type.GetInterfaces().FirstOrDefault(); } if (serviceType == null) { serviceType = type; } switch (serviceAttribute.Lifetime) { case ServiceLifetime.Singleton: services.AddSingleton(serviceType, type); break; case ServiceLifetime.Scoped: services.AddScoped(serviceType, type); break; case ServiceLifetime.Transient: services.AddTransient(serviceType, type); break; default: break; }
咱们首先检查serviceAttribute.ServiceType,若是有值的话,它就是注册服务的类型,若是没有的话,看是否容许从接口中获取服务类型,若是容许,便尝试获取第一个做为服务类型,若是还没获取到,就把自身的类型做为服务类型。
第一种状况不常见,特殊状况才会指定ServiceType,由于写起来麻烦;
第二种状况适用于依赖抽象编程的同窗,注意这里只取第一个接口的类型;
第三种状况就是适用于像我这种有不良习惯的患者(依赖实现编程)!
到此为止咱们的服务注册已经讨论完了,下面看看如何获取。
这里咱们说的获取,不是框架默认容器提供的构造器注入,而是要实现字段和属性注入,先看看构造器注入是什么样的:
public class HomeController : Controller { UserService userService; OrderService orderService; MsgService msgService; OtherService otherService; OtherService2 otherService2; public HomeController(UserService userService, OrderService orderService, MsgService msgService, OtherService otherService, OtherService2 otherService2) { this.userService = userService; this.orderService = orderService; this.msgService = msgService; this.otherService = otherService; this.otherService2 = otherService2; } }
若是引用的服务再也不添加还好,若是编写边添加就太要命了,每次都要定义字段、在构造器方法签名中些添加参数、在构造器中赋值,便捷性和Spring的@autowired注解无法比,因此咱们要虚心学习,创做更便捷的操做。
首先咱们再定义个特性,叫AutowiredAttribute,虽然也是个标识,可是因为这个特性是用在字段或者属性上,因此只能用特性Attribute,而不能使用接口Interface,到这里咱们又发现一点,使用接口做为标识的话,只能用在类、接口和结构中,而不能用在他们的成员上,毕竟接口的主要做用是定义一组方法契约(即抽象)!
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class AutowiredAttribute : Attribute { }
这个特性里面什么也没有,主要是下面这个类,装配操做都在这里:
/// <summary> /// 从容器装配service /// </summary> [AppService] public class AutowiredService { IServiceProvider serviceProvider; public AutowiredService(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } public void Autowired(object service) { var serviceType = service.GetType(); //字段赋值 foreach (FieldInfo field in serviceType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { var autowiredAttr = field.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { field.SetValue(service, serviceProvider.GetService(field.FieldType)); } } //属性赋值 foreach (PropertyInfo property in serviceType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { property.SetValue(service, serviceProvider.GetService(property.PropertyType)); } } } }
咱们刚刚写的[AppService]特性在这里已经用上了,而且这个类使用构造器注入了IServiceProvider。Autowired(object service)方法的参数是要装配的服务实例,首先获取服务类型,再使用反射查询有AutowiredAttribute特性的字段和属性,咱们在构造器注入了serviceProvider,这里即可以使用serviceProvider的GetService方法从容器中获取对应类型的实例来给字段和属性赋值。 整个过程就是这样,简单明了。开始的时候我想使用静态类来编写AutowiredService,可是静态类无法注入IServiceProvider,解决方法也有,可使用定位器模式全局保存IServiceProvider:
/// <summary> /// 服务提供者定位器 /// </summary> public static class ServiceLocator { public static IServiceProvider Instance { get; set; } }
在Setup的Configure方法中赋值:
ServiceLocator.Instance = app.ApplicationServices;
这样在静态的AutowiredService中也就能够访问IServiceProvider了,可是使其本身也注册成服务能更好的和其余组件交互,java有了spring框架,你们都承认spring,一切都在容器中,一切均可注入,spring提供了统一的对象管理,很是好,我感受netcore的未来也将会是这样。
Autowired(object service)方法的实现虽然简单,可是使用了效率底下的反射,这个美中不足须要改进,之前可使用晦涩难懂的EMIT来编写,如今有Expression,编写和阅读都简单了好多,而且效率也不比EMIT差,因此咱们使用表达式+缓存来改进。Autowired方法要作的就是从容器中取出合适的对象,而后赋值给service要自动装配的字段和属性,据此咱们先编写出委托的伪代码:
(obj,serviceProvider)=>{ ((TService)obj).aa=(TAAType)serviceProvider.GetService(aaFieldType); ((TService)obj).bb=(TBBType)serviceProvider.GetService(aaFieldType); ... }
注意伪代码中的类型转换,Expression表达式在编译成委托时是很是严格的,全部转换都不能省。写表达式的时候我习惯先写伪代码,我但愿你们也能养成这个习惯!有了伪代码咱们能够开始改造AutowiredService类了:
/// <summary> /// 从容器装配service /// </summary> [AppService] public class AutowiredService { IServiceProvider serviceProvider; public AutowiredService(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } Dictionary<Type, Action<object, IServiceProvider>> autowiredActions = new Dictionary<Type, Action<object, IServiceProvider>>(); public void Autowired(object service) { Autowired(service, serviceProvider); } /// <summary> /// 装配属性和字段 /// </summary> /// <param name="service"></param> /// <param name="serviceProvider"></param> public void Autowired(object service, IServiceProvider serviceProvider) { var serviceType = service.GetType(); if (autowiredActions.TryGetValue(serviceType, out Action<object, IServiceProvider> act)) { act(service, serviceProvider); } else { //参数 var objParam = Expression.Parameter(typeof(object), "obj"); var spParam = Expression.Parameter(typeof(IServiceProvider), "sp"); var obj = Expression.Convert(objParam, serviceType); var GetService = typeof(IServiceProvider).GetMethod("GetService"); List<Expression> setList = new List<Expression>(); //字段赋值 foreach (FieldInfo field in serviceType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { var autowiredAttr = field.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { var fieldExp = Expression.Field(obj, field); var createService = Expression.Call(spParam, GetService, Expression.Constant(field.FieldType)); var setExp = Expression.Assign(fieldExp, Expression.Convert(createService, field.FieldType)); setList.Add(setExp); } } //属性赋值 foreach (PropertyInfo property in serviceType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { var propExp = Expression.Property(obj, property); var createService = Expression.Call(spParam, GetService, Expression.Constant(property.PropertyType)); var setExp = Expression.Assign(propExp, Expression.Convert(createService, property.PropertyType)); setList.Add(setExp); } } var bodyExp = Expression.Block(setList); var setAction = Expression.Lambda<Action<object, IServiceProvider>>(bodyExp, objParam, spParam).Compile(); autowiredActions[serviceType] = setAction; setAction(service, serviceProvider); } } }
代码一会儿多了很多,不过因为咱们前面的铺垫,理解起来也不难,至此自动装配字段和属性的服务已经写好了,下面看看如何使用:
编写服务类,并添加[AppService]特性
[AppService] public class MyService { //functions }
在Setup的ConfigureServices方法中注册应用服务
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //注册应用服务 services.AddAppServices(); }
在其余类中注入使用,好比Controller中
public class HomeController : Controller { [Autowired] MyUserService myUserService; public HomeController(AutowiredService autowiredService) { autowiredService.Autowired(this); } }
HomeController的构造函数是否是简洁了许多呢!并且再有新的服务要注入,只要定义字段(属性也能够,不过字段更方便)就能够了,注意:咱们定义的字段不能是只读的,由于咱们要在AutowiredService中设置。咱们还用上面的例子,看一下它的威力吧!
public class HomeController : Controller { [Autowired] UserService userService; [Autowired] OrderService orderService; [Autowired] MsgService msgService; [Autowired] OtherService otherService; [Autowired] OtherService2 otherService2; public HomeController(AutowiredService autowiredService) { autowiredService.Autowired(this); } }
感谢您的观看!全文已经完了,咱们没有使用第三方容器,也没有对自带的容器大肆修改和破坏,只是在服务类的构造器中选择性的调用了AutowiredService.Autowired(this)方法,为何是选择性的呢,由于你还可使用在构造器中注入的方式,甚至混用,一切都好,都不会错乱。
nuget安装:
PM> Install-Package Autowired.Core
git源码:
[Autowired.Core] https://gitee.com/loogn/Autowired.Core
更新: 支持多个AppServiceAttribute, 支持服务惟一标识,经过Identifier指定服务实现