您好,登錄后才能下訂單哦!
小編給大家分享一下在.net core中如何實現字段和屬性注入,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
簡單來說,使用Ioc模式需要兩個步驟,第一是把服務注冊到容器中,第二是從容器中獲取服務,我們一個一個討論并演化。這里不會考慮使用如Autofac等第三方的容器來代替默認容器,只是提供一些簡單實用的小方法用于簡化應用層的開發。
將服務注入到容器
asp.netcore官方給出的在容器中注冊服務方法是,要在Startup類的ConfigureServices方法中添加服務,如下所示:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton(typeof(UserService)); services.AddSingleton(typeof(MsgService)); services.AddSingleton(typeof(OrderService)); }
AddMvc方法添加了mvc模塊內部用到的一些服務,這個是封裝好的,一句話就行了,其他第三方組件也都提供了類似的Add方法,把自己內部需要的服務都封裝好注冊進去了。但是我們應用開發人員使用的類,還是需要一個一個寫進去的,大家最常見的三層架構中的數據訪問層和業務邏輯層便是此類服務,上面代碼中我加入了三個業務服務類。這顯然不是長久之計,我想大家在開發中也會針對此問題做一些處理,這里說下我的,僅供參考吧。
解決方法就是批量注冊!說到批量,就需要一個東西來標識一批東西,然后用這一個東西來控制這一批東西。在.net程序的世界中,有兩個可選的角色,一個是接口Interface,另一個是特性Attribute。
如果使用接口作為標識來使用,限制就太死板了,一個標識的信息不是絕對的單一,是不推薦使用接口的,因為可能需要引入多個接口才能共同完成,所以我選擇特性作為標識。特性相較與接口有什么特點呢?特性在運行時是類的實例,所以可以存儲更多的信息。
下面我們簡單實現一個AppServiceAttribute:
/// <summary> /// 標記服務 /// </summary> [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class AppServiceAttribute : Attribute { }
這個特性類取名AppService有兩個理由,一是指定是應用層的服務類,二是避免使用Service這樣的通用命名和其他類庫沖突。
有了標識,就可以批量處理了,我們在一個新的類中給IServiceCollection提供一個擴展方法,用來批量添加標記有AppService特性的服務到容器中。
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屬性:
[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
以上是“在.net core中如何實現字段和屬性注入”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。