目錄
1.1 什麼是依賴?
1.2 什麼是控制反轉?
1.3 什麼是依賴注入?
1.4 簡單總結
2.1 IOC容器說明
2.2 IOC容器的原理
2.3 使用AutoFac的簡介示例
3.1 準備工作
3.2 註冊整個程序集中的所有實現類
3.3 注入接口實現類中的接口類型的屬性
3.4 關於一個接口有多個不同的實現類
3.5 關於一個實現類實現了多個接口
3.6 關於實例作用域
shanzm-2020年3月16日 02:17:35
依賴是面向對象中用來描述類與類之間一種關係的概念。兩個相對獨立的對象,當一個對象負責構造另一個對象的實例,或者依賴另一個對象的服務,這樣的兩個對象之間主要體現為依賴關係
說反轉則要先說「正轉」,傳統中,在程序中使用new關鍵字配合構造函數去創建一個對象,這就是程序主動的創建其所依賴對象,這就是「正轉」。
調用者不自己創建被調用者對象,而交由第三方(容器)進行創建被調用者對象,這個過程稱為控制反轉(inversion of control,IOC)。
為什麼要控制反轉?控制反轉是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度,便於擴展和後期維護。
實現控制反轉的主要方式是依賴注入。(當然不止依賴注入這一種方法,還有依賴查找(Dependency Lookup,DL)。二者區別可參考:維基:控制反轉)
依賴注入具體是指:調用類 不主動創建依賴對象,而是使用容器來幫忙創建及注入依賴對象,這個過程就稱為依賴注入(Dependency Injection,DI)
具體的說:Class A(調用類)中用到 Class B 類型的對象(依賴對象),通常情況下,我們在 Class A 中使用new關鍵字配合構造函數創建一個 Class B 的對象
但是,採用依賴注入技術之後, Class A只需要定義一個Class B類型的屬性,不需要直接new來獲得這個對象,而是通過IOC容器 將Class B類型的對象在外部new出來並注入到Class A里的引用中,從而實現Class A和Class B解耦。
明白了上述幾個概念,那麼就可以理解這樣一句話「模塊間的依賴關係從程序內部提到外部來實例化管理稱之為控制反轉,這個實例化的過程就叫做依賴注入。」
在說到控制反轉時提到「使用IOC容器在 調用類 外部創建 依賴對象 並注入到 調用類」,其中IOC容器是什麼?
IOC容器就是具有依賴注入功能的容器,IOC容器負責實例化、定位、配置應用程式中的對象及建立這些對象間的依賴。從而,應用程式無需直接在代碼中new相關的對象,應用程式由IOC容器進行組裝。
簡而言之,IOC容器主要就兩個作用:1、綁定服務與實例之間的關係。2、對實例進行創建和銷毀
在.NET程序中IOC容器有許多,如Unity、AutoFac、Spring.net等等。
據說AutoFac是關於.NET的最流行的IOC容器,本文簡單的介紹一下AutoFac的使用方式。
IOC容器是怎麼實現的呢?
可以參考 手寫一個簡單的IOC容器
使用AutoFac容器一般是面向接口編程。所以這裡使用一個分層項目來演示AutoFac的使用,即使用AutoFac創建接口實現類的對象
(完整Demo下載)
新建一個類庫項目TestIBLL
用於定義一些接口
public interface IUserBll
{
//檢查登錄信息
bool Login(string userName, string pwd);
//添加新用戶
void AddNew(string userName, string pwd);
}
新建一個類庫項目TestBLLImpl
添加對TestIBLL項目的引用,用於定義接口的實現類
public class UserBll : IUserBll
{
//實現接口
public void AddNew(string userName, string pwd)
{
Console.WriteLine($"新增了一個用戶:{userName}");//為了演示,簡單模擬
}
public bool Login(string userName, string pwd)
{
Console.WriteLine($"登錄用戶是:{userName}");//為了演示,簡單模擬
return true;
}
}
【說明】:
這裡定義了UserBll類,實現了IUserBll接口,
按照AutoFac中的術語,有如下稱呼:
新建一個控制台項目TestUI
用於模擬UI層
添加對TestIBLL和TestBLLImpl項目的引用
安裝AutoFac:PM>Install-Package Autofac
static void Main(string[] args)
{
//創建容器構造者
ContainerBuilder builder = new ContainerBuilder();
//註冊組件UserBll類,並把服務IUserBll接口暴露給該組件
//把服務(IUserBll)暴露給組件(UserBll)
builder.RegisterType().As();
//創建容器
IContainer container = builder.Build();
//使用容器解析服務,創建實例(不推薦,見下面說明):IUserBll userBll = container.Resolve();
//使用生命周期解析服務,創建實例
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
IUserBll userBll = scope.Resolve();
userBll.Login("shanzm", "123456");
}
}
【說明】:
下面演示一下AutoFac最基本的一些API,具體細節和其他的功能,可以參考AutoFac文檔,其文檔非常詳細且有中文版本(AutoFac文檔)
接著上面的示例,在類庫項目TestIBLL中添加以下接口:
創建IAnimalBll.cs
//IAnimalBll接口
public interface IAnimalBll
{
void Cry();//動物都有叫的動作
}
創建IMasterBll.cs
//IMasterBll接口
public interface IMasterBll
{
void Walk();
}
在類庫項目TestBLLImpl中分別實現上述接口
創建DogBll.cs
//DogBll類實現IAnimalBll接口
public class DogBll : IAnimalBll
{
public void Cry()
{
Console.WriteLine("汪汪汪!");
}
}
創建CatBll.cs
//CatBll類實現IAnimalBll接口
public class CatBll : IAnimalBll
{
public void Cry()
{
Console.WriteLine("喵喵喵!");
}
}
創建MasterBll.cs
//MasterBll類,實現了IMasterBll接口和IUserBll接口
public class MasterBll : IMasterBll,IUserBll
{
//注意這裡,MasterBll是接口的實現類,這個類還有一個接口類型的屬性
public IAnimalBll dogBll { get; set; }
public void AddNew(string userName, string pwd)
{
Console.WriteLine($"新增了一個Master用戶:{userName}");
}
public bool Login(string userName, string pwd)
{
Console.WriteLine($"登錄用戶是Master:{userName}");
return true;
}
public void Walk()
{
Console.WriteLine("帶著狗散步!");
dogBll.Cry();//在調用中,使用.PropertiesAutowired()方法給dogBll註冊其實現類
}
}
項目中,其實我們可以使用.RegisterAssemblyTypes(),一次性把程序集(類庫項目)中的的所有接口實現類都註冊給相應的接口
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();//創建容器構造者
Assembly asm = Assembly.Load(" TestBLLImpl");//獲取指定的程序集
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces();//註冊指定程序集中的所有接口實現類
IContainer container = builder.Build();//創建容器
IUserBll userBll = container.Resolve();//解析服務,創建實例
userBll.Login("shanzm", "123456");//使用服務提供者
}
【說明】:
對實現類中的屬性也是可以使用AutoFac注入的,
對於接口的實現類中若是有某個接口類型的屬性,我們可以使用.PropertiesAutowired()在註冊該實現類的同時,把該屬性同時註冊,即實現屬性的自動裝配,即屬性注入
static void Mian(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
Assembly asm = Assembly.Load("TestBLLImpl");
//在這裡通過.PropertiesAutowired(),給接口實現類中的接口屬性也註冊一個該類型的接口的實現類,即實現屬性自裝配
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired();
builder.RegisterType().As();
IContainer container = builder.Build();
IMasterBll masterBll = container.Resolve();
masterBll.Walk();//列印:帶著狗散步!汪汪汪!
}
為已給接口註冊實現類的時候,可能該接口有多個實現類,則我們可以為每一個註冊提供已給命名builder.RegisterType
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
//builder.RegisterType().As();//這樣寫,下面註冊服務的時候,你只能給IAnimalBll對象創建一個DogBll類型的實例
builder.RegisterType().Named("Dog");
builder.RegisterType().Named("Cat");
IContainer container = builder.Build();
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
IAnimalBll dogBll = scope.ResolveNamed("Dog");
IAnimalBll catBll = scope.ResolveNamed("Cat");
dogBll.Cry();
catBll.Cry();
}
}
但是我們在註冊整個程序集中的實現類的時候,該怎麼註冊已給接口的不同的實現類呢?
使用IEnumerable
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
Assembly asm = Assembly.Load(" TestBLLImpl");
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces();
IContainer container = builder.Build();
//解析服務,將所有實現了IAnimalBll接口的類都註冊,結果存放在集合中
IEnumerable animalBlls = container.Resolve>();
foreach (var bll in animalBlls)
{
Console.WriteLine(bll.GetType());
bll.Cry();
}
//選取指定的實現類
IAnimalBll dogBll = animalBlls.Where(t => t.GetType() == typeof(DogBll)).First();
dogBll.Cry();
}
之前我們說了使用在註冊組件時,一個組件暴露多個服務的時候,可以連續使用 .As()方法
使用.AsImplementedInterfaces()可以達到同樣的效果
MasterBll類實現了多個接口,我們可以把該類註冊給他所有實現的接口換言之,只要是MasterBll實現的接口,我們都註冊給他一個MasterBll類型的對象但是注意,這個MasterBll對象只包含當前接口中的方法
static void Mian(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
//builder.RegisterType().As().As();
//即一個組件暴露了多個服務,這裡就等價於:
builder.RegisterType().AsImplementedInterfaces();//把MasterBll類註冊給所有他實現的接口
IContainer container = builder.Build();
//解析IUserBll服務
//其實這裡的userBll是MasterBll類型的對象,但是這個MasterBll類型的對象只具有IUserBll接口中的方法,不具有IMasterBll接口中的方法
IUserBll userBll = container.Resolve();
userBll.Login("shanzm", "11111");//列印:登錄用戶是Master:shanzm
Console.WriteLine(userBll.GetType());//列印:TestBLLImpl.MasterBll
//userBll.Walk();//注意雖然是MasterBll類型對象,但是只具有當前解析的IUserBll接口中的方法
}
【說明】:
在使用AutoFac的時候,最後解析服務,創建提供服務的實例對象
這個對象的在程序中存在時長,也就是從實例化到最後釋放的時間,稱之為服務的生命周期,
這個對象在應用中能共享給其他組件並被消費的作用域,稱之為服務的作用域
在理解了以上的概念後,我們才能解釋什麼是實例作用域
1.一個依賴一個實例(Instance Per Dependency)
當我們調用 Resolve() 解析服務的時候返回一個實例,每次請求都返回一個唯一的實例,如無說明,默認就是這種作用域!
static void Mian(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
//註冊實例
//builder.RegisterType().As().InstancePerDependency(); //默認就是這種
builder.RegisterType().As();
//創建容器
IContainer container = builder.Build();
//解析服務
using (var scope = container.BeginLifetimeScope())
{
IUserBll userBll1 = scope.Resolve();
userBll1.Login("shanzm", "1111");//列印:登錄用戶是普通用戶:shanzm
IUserBll userBll2 = scope.Resolve();
userBll2.Login("shanzm", "2222");//列印:登錄用戶是普通用戶:shanzm
Console.WriteLine(ReferenceEquals(userBll1, userBll2));//列印結果:false
}
}
//說明:根據調試,結果就可以看出,每次在解析服務,創建的服務提供者都是新的。
//你要注意,我們上面的示例代碼在同一個生命周期中註冊的兩個IUserBll接口的實例,但是它們依舊是兩個不同的實例
2.單一實例(Single Instance)
它也被稱為 '單例.' 使用單一實例作用域, 在根容器和所有嵌套作用域內所有的請求都將會返回同一個實例.
建議在面向接口的編程中,實例作用域採用:單一實例。防止出現並發操作,造成髒數據!
static void Mian(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
//註冊實例
builder.RegisterType().As().SingleInstance();//設置為單一實例
//創建容器
IContainer container = builder.Build();
//解析服務
using (var scope1 = container.BeginLifetimeScope())
{
IUserBll userBll1 = scope1.Resolve();
userBll1.Login("shanzm", "1111");
using (var scope2 = container.BeginLifetimeScope())
{
IUserBll userBll2 = scope2.Resolve();
userBll2.Login("shanzm", "2222");
Console.WriteLine(ReferenceEquals(userBll1, userBll2));
//因為是單一實例,所以就是在不同的生命周期中,也是同一個實例,列印結果:true
}
}
}
//說明:最終的列印結果:true 。即使在不同的生命周期中每次在解析服務,創建的服務提供者都是同一個!
其他的實例作用域,詳細可以參考文檔:實例作用域
AutoFac在 ASP .NET MVC中使用更加的方便,主要需要注意的地方就是在Global.asax.cs文件中對AutoFac配置
做一個簡單示例:(點擊下載完整的Demo源碼)
創建名為TestIService的類庫項目,定義所有接口
創建IUserService.cs 文件
public interface IUserService
{
bool CheckLogin(string userName, string pwd);
bool CheckUserNameExists(string userName);
}
創建INewsService.cs 文件
public interface INewsService
{
string AddNews(string title, string body);
}
創建名為TestServiceImpl的類庫項目,定義接口的實現類
首先,添加對TestIService項目的引用
創建UserService.cs 文件
public class UserService : IUserService
{
//注意接口的實現類是可以有接口類型的屬性,該屬性也會被註冊一個實現對應類型接口的類的對象
public INewsService newsService { get; set; }
public bool CheckLogin(string userName, string pwd)
{
return true;
}
public string UserAction(string userName)
{
string result = newsService.AddNews("2020年3月16日-新冠病毒", "中國境內的新冠病毒被有效遏制");
return userName+" 添加新聞 :"+result;
}
}
創建NewsService.cs 文件
public class NewsService : INewsService
{
public string AddNews(string title, string body)
{
return ($"Title:{title},Content:{body}");
}
}
創建名為TestMVC 的Web MVC項目
首先,添加對TestIService項目和TestServiceImpl項目的引用
接著安裝AutoFac在MVC中插件:PM> Install-Package AutoFac.Mvc5
在Global.asax.cs中添加對AutoFac的配置:
using Autofac.Integration.Mvc;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
/*------------------------------AutoFac配置--開始--------------*/
ContainerBuilder builder = new ContainerBuilder();
//此處需要:using Autofac.Integration.Mvc;
//把當前程序集中的所有Controllerr類中的接口類型的屬性註冊
builder.RegisterControllers(typeof(MvcApplication).Assembly).PropertiesAutowired();
Assembly asmSevice = Assembly.Load("TestServiceImpl");
builder.RegisterAssemblyTypes(asmSevice)
.Where(type => !type.IsAbstract)//除去抽象類,抽象類不可以實例化(其實這一句也可以不寫)
.AsImplementedInterfaces()//將實現類註冊給其實現的所有接口
.PropertiesAutowired();//接口實現類中接口類型的屬性也註冊
IContainer container = builder.Build();
//MVC中的所有Controller類都是由AutoFac幫我們創建對象
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
/*------------------------------AutoFac配置--結束--------------*/
}
創建HomeController.cs 控制器
public class HomeController : Controller
{
public IUserService userService { get; set; }//通過AutoFac自動為我們賦值一個IUserService接口實現對象
public ActionResult CheckLogin()
{
bool b = userService.CheckLogin("shanzm", "123456");
return Content(b.ToString());//結果:頁面顯示true
}
public ActionResult UserAddNews()
{
string result = userService.UserAction("shanzm");
return Content(result);//結果:頁面顯示:shanzm 添加新聞 :Title:2020年3月16日-新冠病毒,Content:中境內的新冠病毒被有效遏制
}
}
在瀏覽器中分別請求HomeController中的兩個Action,即可以看到我們使用AutoFac給userService屬性注入相應的實例成功了!
原文來源:博客園
原文地址:https://www.cnblogs.com/shanzhiming/p/12501543.html