原文作者:老張的哲學(xué)
更新
1尺锚、如果看不懂本文贮缕,或者比較困難,先別著急問(wèn)問(wèn)題问欠,我單寫了一個(gè)關(guān)于依賴注入的小Demo肝匆,可以下載看看,多思考思考注入的原理:
https://github.com/anjoy8/BlogArti/tree/master/Blog.Core_IOC%26DI
2顺献、重要:如果你實(shí)現(xiàn)了解耦旗国,也就是 api 層只引用了 IService 和 IRepository 的話,那每次修改 service 層注整,都需要清理解決方案能曾,重新編譯項(xiàng)目,因?yàn)檫@個(gè)時(shí)候你的api層的dll肿轨,還是之前未修改的代碼寿冕。
3、重要+ :請(qǐng)注意椒袍,依賴注入的目的不是為了解耦驼唱,依賴注入是為了控制反轉(zhuǎn),通俗來(lái)說(shuō)驹暑,就是不用我們自己去 new 服務(wù)實(shí)例了玫恳,所以大家不需要一定去解耦(比如下文說(shuō)到的我沒(méi)有引用 Service層 和 Repository層)辨赐,我下一個(gè)DDD系列,依賴注入就沒(méi)有解耦京办,因?yàn)槲矣玫氖亲詭У淖⑷胂菩颍皇茿utofac的反射dll ,我解耦的目的臂港,是為了讓大家更好的理解森枪,服務(wù)是怎么注入到宿主容器里的。
代碼已上傳Github+Gitee审孽,文末有地址
說(shuō)接上文县袱,上回說(shuō)到了《八 || API項(xiàng)目整體搭建 6.3 異步泛型+依賴注入初探》,后來(lái)的標(biāo)題中佑力,我把倉(cāng)儲(chǔ)兩個(gè)字給去掉了式散,因?yàn)楹孟翊蠹覍?duì)這個(gè)模式很有不同的看法,嗯~可能還是我學(xué)藝不精打颤,沒(méi)有說(shuō)到其中的好處暴拄,現(xiàn)在在學(xué)DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)相關(guān)資料,有了好的靈感再給大家分享吧编饺。
到目前為止我們的項(xiàng)目已經(jīng)有了基本的雛形乖篷,后端其實(shí)已經(jīng)可以搭建自己的接口列表了,框架已經(jīng)有了規(guī)模透且,原本應(yīng)該說(shuō)vue了撕蔼,但是呢,又聽(tīng)說(shuō)近來(lái)Vue-cli已經(jīng)從2.0升級(jí)到了3.0了秽誊,還變化挺大鲸沮,前端大佬們,都不停歇呀锅论。當(dāng)然我還在學(xué)習(xí)當(dāng)中讼溺,我也需要了解下有關(guān)3.0的特性,希望給沒(méi)有接觸到最易,或者剛剛接觸到的朋友們怒坯,有一些幫助,當(dāng)然我這個(gè)不是隨波逐流耘纱,只是在眾多的博文中敬肚,給大家一個(gè)入門參考,屆時(shí)說(shuō)3.0的時(shí)候束析,還是會(huì)說(shuō)2.0的相關(guān)問(wèn)題的。
雖然項(xiàng)目整體可以運(yùn)行了憎亚,但是我還有幾個(gè)小知識(shí)點(diǎn)要說(shuō)下员寇,主要是1弄慰、依賴注入和AOP相關(guān)知識(shí);2蝶锋、跨域代理等問(wèn)題(因?yàn)閂ue是基于Node開(kāi)發(fā)的陆爽,與后臺(tái)API接口不在同一個(gè)地址);3扳缕、實(shí)體類的DTO相關(guān)小問(wèn)題慌闭;4、Redis緩存等躯舔;5驴剔、部署服務(wù)器中的各種坑;雖然都是很小的知識(shí)點(diǎn)粥庄,我還是都下給大家說(shuō)下的丧失,好啦,開(kāi)始今天的講解惜互;
零布讹、今天完成的綠色部分
一、依賴注入的理解和思考
更新(19-04-17):如果想好好的理解依賴注入训堆,可以從以下幾個(gè)方面入手:
1描验、項(xiàng)目之間引用是如何起作用的,比如為啥 api 層只是引用了 service 層坑鱼,那為啥也能使用 repository 和 model 等多層的類膘流?
2、項(xiàng)目在啟動(dòng)的時(shí)候姑躲,也就是運(yùn)行時(shí)睡扬,是如何動(dòng)態(tài) 獲取和訪問(wèn) 每一個(gè)對(duì)象的實(shí)例的?也就是 new 的原理
3黍析、項(xiàng)目中有 n 個(gè)類卖怜,對(duì)應(yīng) m 個(gè)實(shí)例等,那這些服務(wù)阐枣,都放在了哪里马靠?肯定每一個(gè)項(xiàng)目都有專屬自己的一塊。如果項(xiàng)目不啟動(dòng)的話蔼两,內(nèi)存里肯定是沒(méi)有這些服務(wù)的甩鳄。
4、使用接口(面向抽象)的好處额划?
5妙啃、在項(xiàng)目后期,如何業(yè)務(wù)需要要全部修改接口的實(shí)現(xiàn)類,比如想把 IA = new A()揖赴;全部 改成 IA = new B();
6馆匿、反射的重要性,為什么要用到反射 dll 燥滑?
如果這些每一條自己都能說(shuō)清楚渐北,那肯定就知道依賴注入是干啥的了。
說(shuō)到依賴铭拧,我就想到了網(wǎng)上有一個(gè)例子赃蛛,依賴注入和工廠模式中的相似和不同:
(1)原始社會(huì)里,沒(méi)有社會(huì)分工搀菩。須要斧子的人(調(diào)用者)僅僅能自己去磨一把斧子(被調(diào)用者)呕臂。相應(yīng)的情形為:軟件程序里的調(diào)用者自己創(chuàng)建被調(diào)用者。
(2)進(jìn)入工業(yè)社會(huì)秕磷,工廠出現(xiàn)诵闭。斧子不再由普通人完畢,而在工廠里被生產(chǎn)出來(lái)澎嚣,此時(shí)須要斧子的人(調(diào)用者)找到工廠疏尿,購(gòu)買斧子,無(wú)須關(guān)心斧子的制造過(guò)程易桃。相應(yīng)軟件程序的簡(jiǎn)單工廠的設(shè)計(jì)模式褥琐。
(3)進(jìn)入“按需分配”社會(huì),需要斧子的人不需要找到工廠晤郑,坐在家里發(fā)出一個(gè)簡(jiǎn)單指令:須要斧子敌呈。斧子就自然出如今他面前。相應(yīng)Spring的依賴****注入造寝。
image
在上篇文章中磕洪,我們已經(jīng)了解到了,什么是依賴倒置诫龙、控制反轉(zhuǎn)(IOC)析显,什么是依賴注入(DI),網(wǎng)上這個(gè)有很多很多的講解签赃,我這里就不說(shuō)明了谷异,其實(shí)主要是見(jiàn)到這樣的,就是存在依賴
public class A : D
{
public A(B b)
{
// do something
}
C c = new C();
}
就比如我們的項(xiàng)目中的BlogController锦聊,只要是通過(guò)new 實(shí)例化的歹嘹,都是存在依賴
public async Task<List<Advertisement>> Get(int id)
{
IAdvertisementServices advertisementServices = new AdvertisementServices();
return await advertisementServices.Query(d => d.Id == id);
}
使用依賴注入呢,有以下優(yōu)點(diǎn):
傳統(tǒng)的代碼孔庭,每個(gè)對(duì)象負(fù)責(zé)管理與自己需要依賴的對(duì)象尺上,導(dǎo)致如果需要切換依賴對(duì)象的實(shí)現(xiàn)類時(shí),需要修改多處地方。同時(shí)尖昏,過(guò)度耦合也使得對(duì)象難以進(jìn)行單元測(cè)試仰税。
依賴注入把對(duì)象的創(chuàng)造交給外部去管理,很好的解決了代碼緊耦合(tight couple)的問(wèn)題构资,是一種讓代碼實(shí)現(xiàn)松耦合(loose couple)的機(jī)制抽诉。
松耦合讓代碼更具靈活性,能更好地應(yīng)對(duì)需求變動(dòng)吐绵,以及方便單元測(cè)試迹淌。
舉個(gè)栗子,就是關(guān)于日志記錄的
日志記錄:有時(shí)需要調(diào)試分析己单,需要記錄日志信息唉窃,這時(shí)可以采用輸出到控制臺(tái)、文件纹笼、數(shù)據(jù)庫(kù)纹份、遠(yuǎn)程服務(wù)器等;假設(shè)最初采用輸出到控制臺(tái)廷痘,直接在程序中實(shí)例化ILogger logger = new ConsoleLogger()蔓涧,但有時(shí)又需要輸出到別的文件中,也許關(guān)閉日志輸出笋额,就需要更改程序元暴,把ConsoleLogger改成FileLogger或者NoLogger, new FileLogger()或者new SqlLogger() 兄猩,此時(shí)不斷的更改代碼茉盏,就顯得心里不好了吟宦,如果采用依賴注入烹笔,就顯得特別舒暢。
我有一個(gè)個(gè)人的理解奢米,不知道恰當(dāng)與否淹真,比如我們平時(shí)食堂吃飯讶迁,都是食堂自己炒的菜,就算是配方都一樣趟咆,控制者(廚師)還是會(huì)有些變化添瓷,或者是出錯(cuò),但是肯德基這種值纱,由總店提供的鳞贷,店面就失去了控制,就出現(xiàn)了第三方(總店或者工廠等)虐唠,這就是實(shí)現(xiàn)了控制反轉(zhuǎn)搀愧,我們只需要說(shuō)一句,奧爾良雞翅,嗯就拿出來(lái)一個(gè)咱筛,而且全部分店的都一樣搓幌,我們不用管是否改配方,不管是否依賴哪些調(diào)理食材迅箩,哈哈溉愁。
二、常見(jiàn)的IoC框架有哪些
1饲趋、Autofac+原生
我常用的還是原生注入和 Autofac 注入拐揭。
Autofac:貌似目前net下用的最多吧
Ninject:目前好像沒(méi)多少人用了
Unity:也是較為常見(jiàn)
微軟 core 自帶的 DI
其實(shí).Net Core 有自己的輕量級(jí)的IoC框架,
ASP.NET Core本身已經(jīng)集成了一個(gè)輕量級(jí)的IOC容器奕塑,開(kāi)發(fā)者只需要定義好接口后堂污,在Startup.cs的ConfigureServices方法里使用對(duì)應(yīng)生命周期的綁定方法即可,常見(jiàn)方法如下
services.AddTransient<IApplicationService,ApplicationService>//服務(wù)在每次請(qǐng)求時(shí)被創(chuàng)建龄砰,它最好被用于輕量級(jí)無(wú)狀態(tài)服務(wù)(如我們的Repository和ApplicationService服務(wù))
services.AddScoped<IApplicationService,ApplicationService>//服務(wù)在每次請(qǐng)求時(shí)被創(chuàng)建盟猖,生命周期橫貫整次請(qǐng)求
services.AddSingleton<IApplicationService,ApplicationService>//Singleton(單例) 服務(wù)在第一次請(qǐng)求時(shí)被創(chuàng)建(或者當(dāng)我們?cè)贑onfigureServices中指定創(chuàng)建某一實(shí)例并運(yùn)行方法),其后的每次請(qǐng)求將沿用已創(chuàng)建服務(wù)换棚。如果開(kāi)發(fā)者的應(yīng)用需要單例服務(wù)情景式镐,請(qǐng)?jiān)O(shè)計(jì)成允許服務(wù)容器來(lái)對(duì)服務(wù)生命周期進(jìn)行操作,而不是手動(dòng)實(shí)現(xiàn)單例設(shè)計(jì)模式然后由開(kāi)發(fā)者在自定義類中進(jìn)行操作圃泡。
當(dāng)然.Net Core自身的容器還是比較簡(jiǎn)單碟案,如果想要更多的功能和擴(kuò)展,還是需要使用上邊上個(gè)框架颇蜡。
2价说、三種注入的生命周期
權(quán)重:
AddSingleton→AddTransient→AddScoped
AddSingleton的生命周期:
項(xiàng)目啟動(dòng)-項(xiàng)目關(guān)閉 相當(dāng)于靜態(tài)類 只會(huì)有一個(gè)
AddScoped的生命周期:
請(qǐng)求開(kāi)始-請(qǐng)求結(jié)束 在這次請(qǐng)求中獲取的對(duì)象都是同一個(gè)
AddTransient的生命周期:
請(qǐng)求獲取-(GC回收-主動(dòng)釋放) 每一次獲取的對(duì)象都不是同一個(gè)
這里來(lái)個(gè)簡(jiǎn)單的小DEMO:
1、定義四個(gè)接口风秤,并分別對(duì)其各自接口實(shí)現(xiàn)鳖目,目的是測(cè)試Singleton,Scope缤弦,Transient三種领迈,以及最后的 Service 服務(wù):
public interface ISingTest
{
int Age { get; set; }
string Name { get; set; }
}
public class SingTest: ISingTest
{
public int Age { get; set; }
public string Name { get; set; }
}
//--------------------------
public interface ISconTest
{
int Age { get; set; }
string Name { get; set; }
}
public class SconTest: ISconTest
{
public int Age { get; set; }
public string Name { get; set; }
}
//--------------------------
public interface ITranTest
{
int Age { get; set; }
string Name { get; set; }
}
public class TranTest: ITranTest
{
public int Age { get; set; }
public string Name { get; set; }
}
//-----------------------
public interface IAService
{
void RedisTest();
}
public class AService : IAService
{
private ISingTest sing; ITranTest tran; ISconTest scon;
public AService(ISingTest sing, ITranTest tran, ISconTest scon)
{
this.sing = sing;
this.tran = tran;
this.scon = scon;
}
public void RedisTest()
{
}
}
2、項(xiàng)目注入
3碍沐、控制器調(diào)用
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace IOCDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private ISingTest sing; ITranTest tran; ISconTest scon;IAService aService;
public ValuesController(ISingTest sing,ITranTest tran,ISconTest scon,IAService aService)
{
this.sing = sing;
this.tran = tran;
this.scon = scon;
this.aService = aService;
}
[HttpGet]
public ActionResult<IEnumerable<string>> SetTest()
{
sing.Age = 18;
sing.Name = "小紅";
tran.Age = 19;
tran.Name = "小明";
scon.Age = 20;
scon.Name = "小藍(lán)";
aService.RedisTest();
return new string[] { "value1","value2" };
}
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
}
}
4狸捅、開(kāi)始測(cè)試,三種注入方法出現(xiàn)的情況
請(qǐng)求SetTest // GET api/values
AddSingleton的對(duì)象沒(méi)有變
AddScoped的對(duì)象沒(méi)有變化
AddTransient的對(duì)象發(fā)生變化
請(qǐng)求 // GET api/values/5
AddSingleton的對(duì)象沒(méi)有變
AddScoped的對(duì)象發(fā)生變化
AddTransient的對(duì)象發(fā)生變化
注意:
由于AddScoped對(duì)象是在請(qǐng)求的時(shí)候創(chuàng)建的
所以不能在AddSingleton對(duì)象中使用
甚至也不能在AddTransient對(duì)象中使用
所以權(quán)重為AddSingleton→AddTransient→AddScoped
不然則會(huì)拋如下異常
image
三累提、較好用的IoC框架使用——Autofac
首先呢尘喝,我們要明白,我們注入是要注入到哪里——Controller API層斋陪。然后呢朽褪,我們看到了在接口調(diào)用的時(shí)候置吓,如果需要其中的方法,需要using兩個(gè)命名空間
[HttpGet("{id}", Name = "Get")]
public async Task<List<Advertisement>> Get(int id)
{
// 需要引用兩個(gè)命名空間Blog.Core.IServices;Blog.Core.Services;
IAdvertisementServices advertisementServices = new AdvertisementServices();
return await advertisementServices.Query(d => d.Id == id);
}
接下來(lái)我們就需要做處理:
1缔赠、引入nuget包
在Nuget中引入兩個(gè):Autofac.Extras.DynamicProxy(Autofac的動(dòng)態(tài)代理衍锚,它依賴Autofac,所以可以不用單獨(dú)引入Autofac)嗤堰、Autofac.Extensions.DependencyInjection(Autofac的擴(kuò)展)
2戴质、接管ConfigureServices
讓Autofac接管Starup中的ConfigureServices方法,記得修改返回類型IServiceProvider
這個(gè)時(shí)候我們就把AdvertisementServices的new 實(shí)例化過(guò)程注入到了Autofac容器中梁棠,
這個(gè)時(shí)候要看明白置森,前邊的是實(shí)現(xiàn)類,后邊的是接口符糊,順序不要搞混了。
3呛凶、構(gòu)造函數(shù)方式來(lái)注入
依賴注入有三種方式(構(gòu)造方法注入男娄、setter方法注入和接口方式注入),我們平時(shí)基本都是使用其中的構(gòu)造函數(shù)方式實(shí)現(xiàn)注入漾稀,
在BlogController中模闲,添加構(gòu)造函數(shù),并在方法中崭捍,去掉實(shí)例化過(guò)程尸折;
readonly IAdvertisementServices _advertisementServices;
/// <summary>
/// 構(gòu)造函數(shù)
/// </summary>
/// <param name="advertisementServices"></param>
public BlogController(IAdvertisementServices advertisementServices)
{
_advertisementServices = advertisementServices;
}
[HttpGet("{id}", Name = "Get")]
public async Task<List<Advertisement>> Get(int id)
{
//IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用兩個(gè)命名空間Blog.Core.IServices;Blog.Core.Services;
return await _advertisementServices.Query(d => d.Id == id);
}
4、效果調(diào)試殷蛇,已經(jīng)成功
注意:這里經(jīng)常會(huì)遇到一個(gè)錯(cuò)誤:None of the constructors found with ........实夹,
查看你的service服務(wù),是不是用了其他的倉(cāng)儲(chǔ)repository粒梦,但是又缺少了構(gòu)造函數(shù)亮航。
如果沒(méi)有問(wèn)題,大家就需要想想匀们,除了 Autofac 還有沒(méi)有其他的不用第三方框架的注入方法呢缴淋?聰明如你,netcore 還真自帶了注入擴(kuò)展泄朴。
5重抖、NetCore 自帶的注入實(shí)現(xiàn)效果
當(dāng)然,我們用 Asp.net core 自帶的注入方式也是可以的祖灰,也挺簡(jiǎn)單的钟沛,這里先說(shuō)下使用方法:
這個(gè)時(shí)候,我們發(fā)現(xiàn)已經(jīng)成功的注入了夫植,而且特別簡(jiǎn)單讹剔,那為啥還要使用 Autofac 這種第三方擴(kuò)展呢油讯,我們想一想,上邊我們僅僅是注入了一個(gè) Service 延欠,但是項(xiàng)目中有那么多的類陌兑,都要一個(gè)個(gè)手動(dòng)添加么,多累啊由捎,答案當(dāng)然不是滴~
四兔综、整個(gè) dll 程序集的注入
通過(guò)反射將 Blog.Core.Services 和 Blog.Core.Repository 兩個(gè)程序集的全部方法注入
修改如下代碼,注意這個(gè)時(shí)候需要在項(xiàng)目依賴中狞玛,右鍵软驰,添加引用 Blog.Core.Services 層和 Repository 層 到項(xiàng)目中,如下圖心肪,這個(gè)時(shí)候我們的程序依賴了具體的服務(wù):
核心代碼如下锭亏,注意這里是 Load 模式(程序集名):
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
...
#region AutoFac
// 實(shí)例化 AutoFac 容器
var builder = new ContainerBuilder();
// 注冊(cè)要通過(guò)反射創(chuàng)建的組件
//builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();
// 要記得!!!這個(gè)注入的是實(shí)現(xiàn)類層,不是接口層硬鞍!不是 IServices
var assemblyServices = Assembly.Load("Blog.Core.Services");
// 指定已掃描程序集中的類型注冊(cè)為提供所有其實(shí)現(xiàn)的接口慧瘤。
builder.RegisterAssemblyTypes(assemblyServices).AsImplementedInterfaces();
// 模式是 Load(解決方案名)
var assemblyRepository = Assembly.Load("Blog.Core.Repository");
builder.RegisterAssemblyTypes(assemblyRepository).AsImplementedInterfaces();
// 將services填充AutoFac容器生成器
builder.Populate(services);
// 使用已進(jìn)行的組件登記創(chuàng)建新容器
var ApplicationContainer = builder.Build();
#endregion
// 第三方IOC接管
return new AutofacServiceProvider(ApplicationContainer);
}
其他不變,運(yùn)行項(xiàng)目固该,一切正常锅减,換其他接口也可以
到這里,Autofac依賴注入已經(jīng)完成伐坏,基本的操作就是這樣怔匣,不過(guò)可能你還沒(méi)有真正體會(huì)到注入的好處,挑戰(zhàn)下吧桦沉,看看下邊的內(nèi)容每瞒,將層級(jí)進(jìn)行解耦試試!
2永部、程序集注入 —— 實(shí)現(xiàn)層級(jí)解耦
這是一個(gè)學(xué)習(xí)的思路独泞,大家要多想想,可能會(huì)感覺(jué)無(wú)聊或者沒(méi)用苔埋,但是對(duì)理解項(xiàng)目啟動(dòng)和加載懦砂,還是很有必要的。
1组橄、項(xiàng)目最終只依賴抽象
最終的效果是這樣的:工程只依賴抽象荞膘,把兩個(gè)實(shí)現(xiàn)層刪掉,引用這兩個(gè)接口層玉工。
2羽资、配置倉(cāng)儲(chǔ)和服務(wù)層的程序集輸出
將 Blog.Repository 層和 Service 層項(xiàng)目生成地址改成相對(duì)路徑,這樣大家就不用手動(dòng)拷貝這兩個(gè) dll 了遵班,F(xiàn)6編譯的時(shí)候就直接生成到了 api 層 bin 下了:
“...\Blog.Core\bin\Debug\”
3屠升、使用 LoadFile 加載服務(wù)層的程序集
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
...
#region 使用 LoadFile 加載服務(wù)層的程序集
// 獲取項(xiàng)目路徑
var basePath2 = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
// 獲取注入項(xiàng)目絕對(duì)路徑
var servicesDllFile = Path.Combine(basePath2, "Blog.Core.Services.dll");
// 直接采用加載文件的方法
var assemblyServices = Assembly.LoadFile(servicesDllFile);
builder.RegisterAssemblyTypes(assemblyServices).AsImplementedInterfaces();
#endregion
// 將services填充AutoFac容器生成器
builder.Populate(services);
// 使用已進(jìn)行的組件登記創(chuàng)建新容器
var ApplicationContainer = builder.Build();
#endregion
// 第三方IOC接管
return new AutofacServiceProvider(ApplicationContainer);
}
這個(gè)時(shí)候潮改,可能我們編譯成功后,頁(yè)面能正常啟動(dòng)腹暖,證明我們已經(jīng)把 Service 和 Repository 兩個(gè)服務(wù)層的所有服務(wù)給注冊(cè)上了汇在,但是訪問(wèn)某一個(gè)接口,還是會(huì)出現(xiàn)錯(cuò)誤:
這個(gè)錯(cuò)誤表示脏答,我們的 SqlSugar 服務(wù)糕殉,沒(méi)有被注冊(cè)成功,那肯定就是我們的 Sqlsugar 程序集沒(méi)有正常的引用殖告,怎么辦呢阿蝶,這里有兩種方法,請(qǐng)往下看黄绩。
**4羡洁、Sqlsugar 加載失敗,方式一:api層引用 sugar **
你可以直接在 api 層宝与,添加對(duì) sqlsugar 的使用焚廊,也可以直接不用修改,比如我的項(xiàng)目习劫,多級(jí)之間存在級(jí)聯(lián)的關(guān)系,api >> IService >> Model >> sqlSugar :
原因:在 Api層嚼隘,引入 sqlSugarCore nuget 包诽里,因?yàn)榇嬖趯蛹?jí)是存在依賴的
注意!這個(gè)地方很多人不理解為啥:
解耦飞蛹,并不是啥直接不用了谤狡!
解耦僅僅是去掉引用耦合,目的是不用在修改了service.dll 層的某一個(gè)方法的時(shí)候卧檐,而停到api.dll這個(gè)整個(gè)服務(wù)墓懂,
當(dāng)項(xiàng)目啟動(dòng)的時(shí)候,還是需要將所有的服務(wù)都注冊(cè)到主機(jī)里霉囚,
autofac依賴注入捕仔,僅僅是用反射的方法,將service.dll 和 repository.dll 項(xiàng)目服務(wù)進(jìn)行注冊(cè)盈罐,這個(gè)過(guò)程和引用是一樣的榜跌,因?yàn)槿绻阋茫?dāng)項(xiàng)目編譯或啟動(dòng)的時(shí)候盅粪,也是把層服務(wù)全部注入到主機(jī)的過(guò)程钓葫,
所以sqlsugar還是需要引用,因?yàn)樾枰@個(gè)組件的服務(wù)票顾。
**5础浮、方式二:不引用Sugar包帆调!但是需要拷貝 .dll 文件 **
如果你就想要 api 層干凈,就是不想引用 sqlsugar 層的話豆同,那就除非是把 sugar下的所有dll文件都拷貝進(jìn)去番刊,其實(shí)這樣也是可以的,只要把第三方的nuget包生成的dll文件全部拷貝就行诱告,你可以看下撵枢,sqlsugar依賴了很多dll
但是這個(gè)時(shí)候我們需要使用 LoadFrom 模式,因?yàn)槲覀兩线吺褂玫氖?LoadFile 作為反射加載精居,這樣的話锄禽,有一個(gè)問(wèn)題,就是Repository層引用的 sqlsugar 會(huì)提示找不到靴姿,那我們就換一個(gè)反射加載方式 —— LoadFrom:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">//二者的區(qū)別 Assembly.LoadFile只載入相應(yīng)的dll文件沃但,比如Assembly.LoadFile("a.dll"),則載入a.dll佛吓,假如a.dll中引用了b.dll的話宵晚,b.dll并不會(huì)被載入。
Assembly.LoadFrom則不一樣维雇,它會(huì)載入dll文件及其引用的其他dll淤刃,比如上面的例子,b.dll也會(huì)被載入吱型。</pre>
哦逸贾!是不是瞬間想到了什么,沒(méi)錯(cuò)就是 Sqlsugar 加載問(wèn)題津滞,我們換成 LoadFrom 铝侵,并且刪除引用 sqlsugar 來(lái)看看,但是触徐!一定要把 sqlsugar.dll 和 它依賴的 MySql.Data.dll 給拷貝進(jìn)來(lái)api層咪鲜;
所以總體來(lái)說(shuō)有三個(gè)情況:
1、loadfile 模式 + 引用 SqlSugar
2撞鹉、loadfile 模式+ 引用
3疟丙、loadfrom 模式+ 拷貝 .dll 文件
6、連接字符串問(wèn)題
注意:如果采用上邊的方法孔祸,把 service 和 Repository 層雖然解耦了隆敢,但是必須采用 LoadFile( dll 文件) 的形式,這樣就導(dǎo)致了崔慧,在 startup.cs 啟動(dòng)中拂蝎,無(wú)法給其他類庫(kù)中的靜態(tài)屬性賦值的能力,比如:
BaseDBConfig.ConnectionString = "數(shù)據(jù)庫(kù)連接字符串";
這個(gè)在 startup.cs 的ConfigureServices 方法中惶室,是無(wú)法生效的温自。解決辦法:
1玄货、不解耦,還是采用普通辦法悼泌,引用兩個(gè)層松捉,用 Assembly.Load("Blog.Core.Services") 方式;
2馆里、按照上邊解耦隘世,但是數(shù)據(jù)庫(kù)連接字符串配置,需要在 Repostory 層
// public static string ConnectionString { get; set; }
public static string ConnectionString = Appsettings.app(new string[] { "AppSettings", "RedisCaching", "ConnectionString" });//獲取連接字符串
7鸠踪、解除Service層和Repository層之間的耦合
還記得Blog.Core.Services中的BaseServices.cs么丙者,它還是通過(guò)new 實(shí)例化的方式在創(chuàng)建,仿照contrller营密,修改BaseServices并在全部子類的構(gòu)造函數(shù)中注入:
public class BaseServices<TEntity> : IBaseServices<TEntity> where TEntity : class, new()
{
//public IBaseRepository<TEntity> baseDal = new BaseRepository<TEntity>();
public IBaseRepository<TEntity> baseDal;//通過(guò)在子類的構(gòu)造函數(shù)中注入械媒,這里是基類,不用構(gòu)造函數(shù)
//...
}
public class AdvertisementServices : BaseServices<Advertisement>, IAdvertisementServices
{
IAdvertisementRepository dal;
public AdvertisementServices(IAdvertisementRepository dal)
{
this.dal = dal;
base.baseDal = dal;
}
}
好啦评汰,現(xiàn)在整個(gè)項(xiàng)目已經(jīng)完成了相互直接解耦的功能纷捞,以后就算是Repository和Service如何變化,接口層都不用修改被去,因?yàn)橐呀?jīng)完成了注入主儡,第三方Autofac會(huì)做實(shí)例化的過(guò)程。
8惨缆、容器內(nèi)查看注入的服務(wù)數(shù)據(jù)
如果你想看看是否注入到容器里成功了缀辩,可以直接看看容器 ApplicationContainer 的內(nèi)容:
五、 無(wú)接口項(xiàng)目注入
1踪央、接口形式的注入
上邊我們討論了很多,但是都是接口框架的瓢阴,
比如:Service.dll 和與之對(duì)應(yīng)的 IService.dll畅蹂,Repository.dll和與之對(duì)應(yīng)的 IRepository.dll,
這樣荣恐,我們?cè)诙鄬又g使用服務(wù)的話液斜,直接將我們需要使用的 new 對(duì)象,注入到容器里叠穆,然后我們就可以使用相應(yīng)的接口了少漆,
比如:我們想在 controller 里使用AdvertisementServices 類,那我們就可以直接使用它的接口 IAdvertisementServices硼被,這樣就很好的達(dá)到了解耦的目的示损,這樣我們就可以在API層,就輕松的把 Service.dll 給解耦了嚷硫;
如果我們需要在 Service類里检访,使用 AdvertisementRepository 始鱼,我們就直接使用對(duì)應(yīng)的接口 IAdvertisementRepository,這樣脆贵,我們就從 Service 層中医清,把倉(cāng)儲(chǔ)層給解耦了。
2卖氨、如果沒(méi)有接口
案例是這樣的:
如果我們的項(xiàng)目是這樣的会烙,沒(méi)有接口,會(huì)怎么辦:
// 服務(wù)層類
public class StudentService
{
StudentRepository _studentRepository;
public StudentService(StudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
public string Hello()
{
return _studentRepository.Hello();
}
}
// 倉(cāng)儲(chǔ)層類
public class StudentRepository
{
public StudentRepository()
{
}
public string Hello()
{
return "hello world!!!";
}
}
// controller 接口調(diào)用
StudentService _studentService;
public ValuesController(StudentService studentService)
{
_studentService = studentService;
}
這樣的話筒捺,我們就不能使用上邊的接口注入模式了柏腻,因?yàn)槲覀兩线吺前炎⑷氲姆?wù),對(duì)應(yīng)注冊(cè)給了接口了 .AsImplementedInterfaces() 焙矛,我們就無(wú)法實(shí)現(xiàn)解耦了葫盼,因?yàn)楦緵](méi)有了接口層,所以我們只能引用實(shí)現(xiàn)類層村斟,這樣注入:
通過(guò) builder.RegisterAssemblyTypes(assemblysRepository); 方法直接注入服務(wù)贫导,沒(méi)有其他的東西。
3蟆盹、如果是沒(méi)有接口的單獨(dú)實(shí)體類
public class Love
{
// 一定要是虛方法
public virtual string SayLoveU()
{
return "I ? U";
}
}
//---------------------------
////只能注入該類中的虛方法
builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))
.EnableClassInterceptors()
.InterceptedBy(typeof(BlogLogAOP));
六孩灯、同一接口多實(shí)現(xiàn)類注入
這里暫時(shí)沒(méi)有實(shí)例代碼,如果你正好需要逾滥,可以看看這個(gè)博友的栗子:https://www.cnblogs.com/fuyujian/p/4115474.html
我會(huì)在之后的時(shí)間寫個(gè)栗子放到這里峰档。
// 定義一個(gè)接口,兩個(gè)實(shí)現(xiàn)類
public interface IDemoService
{
string Get();
}
public class DemoServiceA : IDemoService
{
public string Get()
{
return "Service A";
}
}
public class DemoServiceB : IDemoService
{
public string Get()
{
return "Service B";
}
}
// 依賴注入
services.AddSingleton(factory =>
{
Func<string, IDemoService> accesor = key =>
{
if (key.Equals("ServiceA"))
{
return factory.GetService<DemoServiceA>();
}
else if (key.Equals("ServiceB"))
{
return factory.GetService<DemoServiceB>();
}
else
{
throw new ArgumentException($"Not Support key : {key}");
}
};
return accesor;
});
// 使用
private IDemoService _serviceA;
private IDemoService _serviceB;
private readonly Func<string, IDemoService> _serviceAccessor;
public ValuesController(Func<string, IDemoService> serviceAccessor)
{
this._serviceAccessor = serviceAccessor;
_serviceA = _serviceAccessor("ServiceA");
_serviceB = _serviceAccessor("ServiceB");
}
二種寨昙,工廠模式
// 設(shè)計(jì)工廠
public class SingletonFactory
{
Dictionary<Type, Dictionary<string, object>> serviceDict;
public SingletonFactory()
{
serviceDict = new Dictionary<Type, Dictionary<string, object>>();
}
public TService GetService<TService>(string id) where TService : class
{
var serviceType = typeof(TService);
return GetService<TService>(serviceType, id);
}
public TService GetService<TService>(Type serviceType, string id) where TService : class
{
if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict))
{
if (implDict.TryGetValue(id, out object service))
{
return service as TService;
}
}
return null;
}
public void AddService<TService>(TService service, string id) where TService : class
{
AddService(typeof(TService), service, id);
}
public void AddService(Type serviceType, object service, string id)
{
if (service != null)
{
if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict))
{
implDict[id] = service;
}
else
{
implDict = new Dictionary<string, object>();
implDict[id] = service;
serviceDict[serviceType] = implDict;
}
}
}
}
// 注入工廠
SingletonFactory singletonFactory = new SingletonFactory();
singletonFactory.AddService<IServiceA>(new ImplA1(), "impla1");
singletonFactory.AddService<IServiceA>(new ImplA2(), "impla2");
services.AddSingleton(singletonFactory);
// 使用
private readonly IServiceA serviceA;
public HomeController(SingletonFactory singletonFactory)
{
this.serviceA = singletonFactory.GetService<IServiceA>("impla2"); //使用標(biāo)識(shí)從SingletonFactory獲取自己想要的服務(wù)實(shí)現(xiàn)
}
七讥巡、簡(jiǎn)單了解通過(guò)AOP切面實(shí)現(xiàn)日志記錄
什么是AOP?引用百度百科:AOP為Aspect Oriented Programming的縮寫舔哪,意為:面向切面編程欢顷,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。實(shí)現(xiàn)AOP主要由兩種方式捉蚤,
一種是編譯時(shí)靜態(tài)植入抬驴,優(yōu)點(diǎn)是效率高,缺點(diǎn)是缺乏靈活性缆巧,.net下postsharp為代表者(好像是付費(fèi)了布持。。)陕悬。
另一種方式是動(dòng)態(tài)代理题暖,優(yōu)點(diǎn)是靈活性強(qiáng),但是會(huì)影響部分效率,動(dòng)態(tài)為目標(biāo)類型創(chuàng)建代理芙委,通過(guò)代理調(diào)用實(shí)現(xiàn)攔截逞敷。
AOP能做什么,常見(jiàn)的用例是事務(wù)處理灌侣、日志記錄等等推捐。
常見(jiàn)的AOP都是配合在Ioc的基礎(chǔ)上進(jìn)行操作,上邊咱們講了Autofac這個(gè)簡(jiǎn)單強(qiáng)大的Ioc框架侧啼,下面就講講Autofac怎么實(shí)現(xiàn)AOP牛柒。Autofac的AOP是通過(guò)Castle(也是一個(gè)容器)項(xiàng)目的核心部分實(shí)現(xiàn)的,名為Autofac.Extras.DynamicProxy,顧名思義痊乾,其實(shí)現(xiàn)方式為動(dòng)態(tài)代理皮壁。當(dāng)然AOP并不一定要和依賴注入在一起使用,自身也可以單獨(dú)使用哪审。
網(wǎng)上有一個(gè)博友的圖片蛾魄,大概講了AOP切面編程
八、結(jié)語(yǔ)
今天的文章呢湿滓,主要說(shuō)了依賴注入IoC在項(xiàng)目中的使用滴须,從上邊的說(shuō)明中,可以看到叽奥,最大的一個(gè)優(yōu)點(diǎn)就是實(shí)現(xiàn)解耦的作用扔水,最明顯的就是,在Controller中朝氓,不用在實(shí)例服務(wù)層或者業(yè)務(wù)邏輯層了魔市,不過(guò)還是有些缺點(diǎn)的,缺點(diǎn)之一就是會(huì)占用一定的時(shí)間資源赵哲,效率稍稍受影響待德,不過(guò)和整體框架相比,這個(gè)影響應(yīng)該也是值得的枫夺。
明天磅网,我們繼續(xù)將面向切面編程AOP中的,日志記錄和面向AOP的緩存使用筷屡。