在開始之前首先解釋一下我認為的依賴注入和控制反轉的意思。(新手理解,哪里說得不正確還請指正和見諒)
控制反轉:我們向IOC容器發出獲取一個對象實例的一個請求,IOC容器便把這個對象實例“注入”到我們的手中,在這個時候我們不是一個創建者,我們是以一個請求者的身份去請求容器給我們這個對象實例。我們所有的對象依賴於容器提供給你的資源,控制權落到了容器身上。在這里的身份轉化或許就是控制反轉的核心吧。
依賴注入:我們向容器發出請求以后,獲得這個對象實例的過程就叫依賴注入。也就是我們在使用對象前我們都需要先注入也就是這個意思吧。
今天學習了下AutoFac依賴注入這個插件,然后還有以前用過的Unity這個插件簡單做個筆記整理。首先我分兩個部分記錄,第一部分一點有點的記錄今天學習的AutoFac這個插件,最后一部分直接補上以前使用Unity插件的代碼封裝不做詳細解釋。因為本篇幅寫完有點多就單獨寫一下UnIty。
Unity地址:【Unity】微軟的一款依賴注入組件
AutoFac入門
還是放上官網給出的整合流程吧;
- 按照 控制反轉 (IoC) 的思想構建你的應用.
- 添加Autofac引用.
- 在應用的 startup 處...
- 創建 ContainerBuilder.
- 注冊組件.
- 創建容器,將其保存以備后續使用.
- 應用執行階段...
- 從容器中創建一個生命周期.
- 在此生命周期作用域內解析組件實例.
創建簡單的例子
通過一個控制台簡單清晰的介紹了如何使用AutoFac這個插件。
創建項目
創建一個控制台程序叫AutoFacDome。
這里就不做過多解釋了,大家都會創建哈哈。
引用Autofac
使用我vs強大的nuget來進行添加引用:
直接在搜索欄輸入Autofac直接就可以查找出來:
Nuget命令行:
Install-Package Autofac -Version 4.8.1
創建一個依賴關系類:
首先我們定義一個服務接口=>IService
/// <summary> /// 服務接口 /// 描述:為了方法的繼承和擴展更好的演示下面的例子 /// </summary> public interface IService { //定義一個輸出方法 void PrintWord(); }
然后我們在創建一個服務類去實現接口(IService)=>Service
/// <summary> /// 服務類具體實現 /// </summary> public class Service : IService { //輸出方法的實現 public void PrintWord() { Console.WriteLine("我是service打印的Hello word"); } }
好了現在我們有了一個服務接口和一個服務類 ,並且類下面實現了輸出會打印:我是service打印的Hello word。常規我們想調用這個方法我們都是在mian函數中示例化該類進行調用,類似於這樣
IService service = new Service(); service.PrintWord(); //或者 Service service2 = new Service(); service2.PrintWord();
但是今天我們說的不是這些我們說的另外的方式。那我們看看 autofac是怎么調用的。
注冊容器
其實萬變不離其宗,不管是autofac,unity,spring.net等,其實都是這么一個套路就是先注冊容器然后才能從容器取出,其實這個也非常好理解容器本身是沒有東西的,你想用東西就要提前放進去,只有容器有了你請求才會給你。不同的插件只不過是各自的封裝方法或者形式存在着差異。autofac的注冊方式:
// 創建容器 var builder = new ContainerBuilder(); //注冊對象 builder.RegisterType<Service>().As<IService>(); Container = builder.Build();
這個時候我們就相當於把service類放入了容器,這樣在后面你才可以取出來使用。
使用容器
這里我寫了兩個使用的方法根據不同情況使用把:
//使用方法一 using (var ioc = Container.BeginLifetimeScope()) { var service = ioc.Resolve<IService>(); service.PrintWord(); } //使用方法二 //var service = Container.Resolve<IService>(); //service.PrintWord();
運行
我們可以任意注釋一個方法來檢測一下結果:
這樣我們就完成了autofac的簡單運用。
AutoFac新手村
通過上面的例子我們已經知道autofac的基本運用。基本運用還不行我們還要知道一些知識。
多構造函數
第一種:就是單純的有多個構造
如果我們同一個類存在多個構造函數會給我們一個什么結果哪這個在有時候是非常重要的。
所以我們修改我們的service類:
/// <summary> /// 服務類具體實現 /// </summary> public class Service : IService { //默認構造 public Service() { Console.WriteLine("我是service的默認構造"); } //一個參數的構造 public Service(int a) { Console.WriteLine("我是service的一個參數構造"); } //兩個參數的構造 public Service(int a,int b) { Console.WriteLine("我是service的兩個參數構造"); } //輸出方法的實現 public void PrintWord() { Console.WriteLine("我是service打印的Hello word"); } }
其他都不變運行代碼:
這里就是執行了默認構造,所有在一個類有多個構造情況下默認的形式是返回給我們默認構造函數的類實例。
第二種:多構造參數類型並且參數類型也注冊容器
這個什么意思哪就是說有兩個類,其中一個類的構造函數參數是另一個類。並且參數類型也進行注冊容器
我們增加一個ServiceTwo類:
public class ServiceTwo :IService { //輸出方法的實現 public void PrintWord() { Console.WriteLine("我是serviceTwo打印的Hello word"); } }
修改service類中的一個參數構造為:
//一個參數的構造 public Service(ServiceTwo two) { Console.WriteLine("我是service的一個參數構造"); }
main函數增加注冊:
//注冊對象 builder.RegisterType<Service>().As<IService>(); builder.RegisterType<ServiceTwo>(); Container = builder.Build();
然后運行:
這里就和上面的結果不一樣了,所有在使用時需要注意,autofac官方解釋為:當使用基於反射的組件時, Autofac 自動為你的類從容器中尋找匹配擁有最多參數的構造方法。
說白了就是如果使用注冊類並且注冊類多構造函數,並且其構造參數為其他注冊類時候,查找的構造函數包含注冊類最多的構造函數返回。
指定構造函數
由容器掌握我們的構造函數總是不好的,所有我們要自己指定想創建誰創建誰=>UsingConstructor(參數類型)可以多個
在這里需要注意我們既然指定了構造函數就要為構造函數傳參不然會抱錯,參數可以是注冊時候傳也可以解析時候傳,我寫了一個解析時候傳的:
// 創建容器 var builder = new ContainerBuilder(); //注冊對象 builder.RegisterType<Service>().As<IService>().UsingConstructor(typeof(int), typeof(int)); // builder.RegisterType<ServiceTwo>(); Container = builder.Build(); //使用方法一 using (var ioc= Container.BeginLifetimeScope()) { var service = ioc.Resolve<IService>(new NamedParameter("a", 1), new NamedParameter("b", 1)); service.PrintWord(); }
運行結果:
類的覆蓋
如果我兩個類或者多個類同時實現一個接口並且注冊的時候都與接口做了關聯。
那么會存在覆蓋現象。
下面我們把main函數改造讓serviceTwo也注冊與IService關聯
// 創建容器 var builder = new ContainerBuilder(); //注冊對象 builder.RegisterType<Service>().As<IService>(); builder.RegisterType<ServiceTwo>().As<IService>(); Container = builder.Build(); //使用方法一 using (var ioc = Container.BeginLifetimeScope()) { var service = ioc.Resolve<IService>(); service.PrintWord(); }
運行結果:
這個時候我們得到的是serviceTwo類的示例。如果改變Service和ServiceTwo的位置就會返回service實例。
當然我沒也可以阻止這個行為使用PreserveExistingDefaults()方法:
//注冊對象 builder.RegisterType<Service>().As<IService>(); builder.RegisterType<ServiceTwo>().As<IService>().PreserveExistingDefaults();
再次運行就不會覆蓋:
然后我們如何遍歷所有的注冊服務哪,使用循環:
using (var ioc = Container.BeginLifetimeScope()) { //var service = ioc.Resolve<IService>(); //service.PrintWord(); var serviceList = ioc.Resolve<IEnumerable<IService>>(); foreach (var item in serviceList) { item.PrintWord(); } }
運行結果:
屬性注入
WithProperty:綁定一個屬性和他的值
我們給ServiceTwo類添加name屬性並擴展一個打印方法:
public string name { get; set; } public void PrintName() { Console.WriteLine($"name屬性:{name}"); }
然后main函數改為
builder.RegisterType<Service>().As<IService>(); builder.RegisterType<ServiceTwo>().WithProperty("name", "張三"); Container = builder.Build(); //使用方法一 using (var ioc = Container.BeginLifetimeScope()) { var service = ioc.Resolve<ServiceTwo>(); service.PrintName(); }
運行結果:
方法注入
OnActivating:方法注入
我們在ServiceTwo類添加設置名稱方法
public void setName() { name = "李四"; }
然后main函數改為:
builder.RegisterType<ServiceTwo>().OnActivating(e=> {
e.Instance.setName();
});
運行結果:
AutoFac集成-MVC
首先創建mvc項目就不過多解釋了。
引用dll:
這里需要引用兩個dll文件: Autofac.Mvc5和 Autofac。注意這里我的是mvc5所以我安裝的Autofac.Mvc5 這個要根據mvc版本做對應不然會報錯。
通過nuget安裝就可以了。
相關類
還是我們的Service類和IService類來做示例演示:
/// <summary> /// 服務接口 /// 描述:為了方法的繼承和擴展更好的演示下面的例子 /// </summary> public interface IService { //定義一個輸出方法 void PrintWord(); } /// <summary> /// 服務類具體實現 /// </summary> public class Service : IService { //默認構造 public Service() { Console.WriteLine("我是service的默認構造"); } //輸出方法的實現 public void PrintWord() { System.Diagnostics.Debug.WriteLine("調起了service中的方法"); } }
配置文件
配置Global文件,來注入控制器。下面我只做構造函數注入和屬性注入
protected void Application_Start() { var builder = new ContainerBuilder(); // 通過程序集注冊所有控制器和屬性注入 //builder.RegisterControllers(typeof(MvcApplication).Assembly); builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(); builder.RegisterType<Service>().As<IService>(); // 將依賴性分解器設置為AutoFac。 var container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); }
控制器如何使用:
打開home控制器:
public class HomeController : Controller { /// <summary> /// 構造函數注入 /// </summary> /// <param name="serviceClient"></param> public HomeController(IService serviceClient) { this.Service = serviceClient; } public IService Service; /// <summary> /// 屬性注入 /// </summary> /// <returns></returns> // public IService Service2 { get; set; } public ActionResult Index() { //使用方法一 Service.PrintWord(); //Service2.PrintWord(); return View(); } }
運行看效果:
自動注冊所有控制器(補充)
一個一個的去注冊控制是很繁瑣的事情,最怕的就是后期修改代碼增加了業務卻沒有及時添加相應的注冊而出錯,所以我們會全部一次注冊,當然這不是必須的只是一種懶人操作。
RegisterAssemblyTypes方法:它會去掃描所有的dll並把每個類注冊為它所實現的接口。
我們首先要創建一個接口基類什么都不做只做為注冊的類型檢測:
/// <summary> /// 注冊基類 /// </summary> public interface IDependency { }
然后所以需要注冊的接口都繼承此類即可:
/// <summary> /// 基類接口 /// </summary> public interface IBaseService:IDependency { /// <summary> /// 插入錯誤日志數據 /// </summary> /// <returns></returns> int AddErrirLog(); /// <summary> /// 插入登錄日志 /// </summary> /// <returns></returns> int AddLoginLog(); }
最后修改Global文件:
var builder = new ContainerBuilder(); // 通過程序集注冊所有控制器和屬性注入 //builder.RegisterControllers(typeof(MvcApplication).Assembly); builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(); //單個控制器注入 // builder.RegisterType<BaseService>().As<IBaseService>(); //集體自動注入 var baseType = typeof(IDependency); var assbembly = AppDomain.CurrentDomain.GetAssemblies().ToList(); builder.RegisterAssemblyTypes(assbembly.ToArray()) .Where(t => baseType.IsAssignableFrom(t) && t != baseType) .AsImplementedInterfaces().InstancePerLifetimeScope(); // 將依賴性分解器設置為AutoFac。 var container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
主要是紅色部分