前言
autofac
Autofac是一套高效的依賴注入框架。
Autofac官方網站:http://autofac.org/
Autofac在Github上的開源項目:https://github.com/autofac/Autofac
Autofac安裝:通過VS的Nuget可以很方便的獲取。
依賴注入
依賴注入,這個專業詞我們可以分為兩個部分來理解:
依賴,也就是UML中描述事物之間關系的依賴關系,依賴關系描述了事物A在某些情況下會使用到事物B,事物B的變化會影響到事物A;
注入,醫生通過針頭將葯物注入到病人體內。注入也就是由外向內注入、灌輸一些東西。
綜合上面的說明,依賴注入就是A類依賴B類,B類的實例由外部向A注入,而不是由A自己進行實例化或初始化。
三種注入方式
構造器注入
我們先理解構造器注入的字面意思,構造器注入也就表示,依賴關系通過構造器進行注入。
這種我們平時是非常常見的,類A依賴於類B,類A的構造方法中,有一個參數為類B,在new 類A,會從外部為類B傳入實例,這就是構造注入:
class Program { static void Main(string[] args) { var b = new B(); var a = new A(b); } } class A { private B _b; public A(B b) { this._b = b; } } class B { }上面說明了構造注入的含義以及構造注入的表現形式,下面我們來看看autofac中的構造注入。
還記得在上一篇博文Autofac全面解析系列(版本:3.5) – [使用篇(推薦篇):2.解析獲取] 中的Resolve對象構造方法選擇原則小結中提到了構造注入這個概念。在使用autofac時,構造注入是默認行為。
以上面的代碼為例,如果類型A和類型B都注冊到了autofac中,那么在通過autofac解析獲取A時,autofac會檢測到A的構造方法中是要一個參數B,而類型B是已經注冊到autofac中的,所以autofac會自動創建b參數,然后傳入A的構造方法中的。這樣,autofac就自動幫我們完成了構造注入的工作。
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<A>(); builder.RegisterType<B>(); var container = builder.Build(); var a = container.Resolve<A>(); //A的構造方法需要參數b,但是這里不需要做更多地操作 } }class A { private B _b; public A(B b) { this._b = b; } } class B { }
屬性注入
屬性注入也就是通過屬性進行注入,我們修改上面的A類,將變量_b通過屬性暴露出來,並且刪掉有參構造方法,然后讓我們看看我們平常寫代碼時怎么實現屬性注入的:
class Program { static void Main(string[] args) { var a = new A(); //點擊A查看A類修改后結構 var b = new B(); a.B = b; //通過屬性來注入具有依賴關系的B } }class A { private B _b; public B B { get { return _b; } set { _b = value; } } } class B { }這種代碼在日常中我們寫過了無數遍,即使是這么平常的代碼,但這就是屬性注入。
依賴注入注意點
但是有一點還是要注意的,我們不能隨便把這種類似的代碼拿出去就告訴別人,我們需要注意一點,需要分清兩者之間是否真的是依賴關系。比如領域模型,簡單的領域模型就是將數據表映射為一個類,對於數據表的每個字段,我們會生成一個對應的屬性,對於這種,我們不能夠在為每個屬性進行賦值時就說“這是依賴注入”,這並不是依賴注入,更多情況下,字段與表的關系是一個組合關系。這一點對於之前的構造注入和后面會講到的方法注入都適用。
說完注意點,讓我們再來看看autofac是怎么進行屬性注入的:
自動屬性注入
屬性注入的所有注入方式都是在注冊時定義的,不像構造注入那般,可以在Resolve時傳參注入。
構造器注入是默認行為,不需要設置,默認會去檢查,而屬性注入並不是默認行為。但是我們可以通過設置,讓屬性注入也成為自動注入。
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); // 通過PropertiesAutowired制定類型A在獲取時會自動注入A的屬性 builder.RegisterType<A>().PropertiesAutowired(); builder.RegisterType<B>(); var container = builder.Build(); var a = container.Resolve<A>(); Console.Write("Press any key to continue..."); Console.ReadKey(); } }使用PropertiesAutowired也只是能指定某個類會自動進行屬性注入,沒有一鍵設置所有類型都會自動注入屬性的設置。而且還需要注意一點,設置了自動屬性注入后,也不代表所有屬性都會自動注入,只有注冊到Autofac中的類型才能自動注入。
WithProperty、WithProperties
PropertiesAutowired方式會自動注入所有可以注入的屬性,但是如果只想注入指定幾個屬性,可以使用除PropertiesAutowired以外的幾種注入方式,WithProperty就是其中一種:
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<A>().WithProperty(new NamedPropertyParameter("B", new B())); // builder.RegisterType<A>().WithProperty("B", new B()); //效果與上面相同
var container = builder.Build(); var a = container.Resolve<A>(); Console.Write("Press any key to continue..."); Console.ReadKey(); } }用法簡單,WithProprtties的使用方式與WithProperty相似,在此就不貼代碼了。
lambda
在注冊篇里面有講到一種lambda注冊方式,lambda注冊時,因為是寫lambda表達式進行注冊,其lambda內容可以寫很多,其中就可以進行屬性注入:
var builder = new ContainerBuilder(); builder.Register(c => { var _a = new A(); _a.B = new B(); //手動注入 return _a; });這里的注入,就是最開始講到屬性注入時的那種賦值注入。
事件
在autofac中,還有一些事件,這些事件在不同時期觸發,事件相關的具體內容將在后續說明。在注入中能夠使用到的事件有OnActivating和OnActivated,他們是在對象Resolve出來后觸發,可以在事件中修改或替換返回對象,同樣也可以進行屬性注入:
var builder = new ContainerBuilder(); builder.RegisterType<A>().OnActivating(e => { e.Instance.B = new B(); //Instance為Resolve出來的實例,類型為A });OnActivated事件的寫法與OnActivating相同,關於兩個事件的區別,將在后續博文中進行說明,請持續關注!
方法注入
方法注入也不是默認行為,而且還沒有提供像屬性注入那樣的自動注入設置。
方法注入有兩種方式,也就是屬性注入的后兩種方式:lambda以及事件。大家應該已經能夠想到注入的代碼是什么樣了:
var builder = new ContainerBuilder(); // lambda builder.Register(cc => { var _a = new A(); _a.MethodInjection(new B()); return _a; }); // 事件 builder.RegisterType<A>().OnActivated(e => { e.Instance.MethodInjection(new B()); });MethodInjection為A的一個方法,並且它需要一個類型為B的參數,我們在外部通過方法的方式將B傳入,這就是方法注入。這里需要特別貼一下A類型的代碼,相對之前有所改動,不僅僅是添加了一個方法:
class A { public void MethodInjection(B b) { // 做一些操作 } }這段代碼可能與一些朋友想象中的不一樣,有些朋友可能想着A中還有一個成員_b,然后在方法MethodInjection中將b賦值給_b。這里我特意將成員_b去掉,為的就是說明一個問題:
不是兩個類型之間一定是成員關系,然后才能有依賴注入,我們得理解依賴的含義。A類型在某些操作中需要使用到B類型,而並不將B類型持久的保存起來,臨時使用也是一種依賴關系。關於為什么這樣就算作依賴注入,在我們了解剛剛說的依賴關系后,再來看看依賴的注入與不注入的不同形式,如果不是注入的方式,那么B類型將不做為參數傳入,而直接在方法中new。
而依賴注入的好處,在這里還不能很好的看到,因為現在是在講autofac中關於注入的方式。如果想更只管的看到注入的好處,我們將參數B換成接口IClass,使用注入的方式,我們在外部傳入IClass的實例,因為IClass是接口,我們可以傳入不同的實現,在更換實現時,這個方法內部的代碼是不需要改動的,反之是需要改動的。依賴注入的好處,我們點到為止了,主要還是要在日常多使用對比,這樣才能更切身的體會它的美妙之處!
尾述
疑問
關於注入這塊兒,其實我個人有個疑問,關於autofac。屬性注入中,我們可以通過設置PropertiesAutowired進行自動注入,但是有時,可能大部分屬性我們都希望能夠自動注入,然而有時會有那么幾個屬性我們需要自動注入忽略掉他們,在我想來,應該是有一個Attribute用於標記屬性,被標記的屬性會在屬性自動注入時被忽略。
而我想的這種Attribute,我找了找,autofac中貌似並沒看到。也可能是我自己忽略掉了,如果大家誰有知道的,煩請指導一下,謝謝
尾述
個人還是推薦使用默認最簡單的構造注入,不需要傳參的那種;屬性注入推薦設置自動屬性注入,如果能夠找到疑問中說到的那種Attribute,那就更好了;方法注入還是不怎么推薦的。
其實這里的推薦原則是這樣的,需要在注冊時進行指定注入的方式實際是不太好的,因為后來的人可能不太清楚每個類型的注入規則,還需要到注冊的地方進行查看,而且不同人員寫的不同,這樣容易混亂。而在獲取時進行注入,實際也是不太妥的,因為在實際的用法中,我們會將注冊類型與接口進行關聯,在獲取時直接獲取接口類型。也正因為我們獲取時獲取的是接口類型,我們無法保證接口的實際實現是不是具有我們預期的參數。
如果有任何問題,還希望大家能夠提出討論,互相學習。也希望能夠有前輩對博客的內容及表達方式提出意見和建議,謝謝!