代碼已上傳Github涧尿,文末有地址
說接上文勒叠,上回說到了《從壹開始前后端分離【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之八 || API項目整體搭建 6.3 異步泛型+依賴注入初探》,后來的標(biāo)題中闸拿,我把倉儲兩個字給去掉了仰禀,因為好像大家對這個模式很有不同的看法,嗯~可能還是我學(xué)藝不精秋泄,沒有說到其中的好處,現(xiàn)在在學(xué)DDD領(lǐng)域驅(qū)動設(shè)計相關(guān)資料规阀,有了好的靈感再給大家分享吧恒序。
到目前為止我們的項目已經(jīng)有了基本的雛形,后端其實已經(jīng)可以搭建自己的接口列表了谁撼,框架已經(jīng)有了規(guī)模歧胁,原本應(yīng)該說vue了,但是呢彤敛,又聽說近來Vue已經(jīng)從2.0升級到了3.0了与帆,還變化挺大,前端大佬們墨榄,都不停歇呀玄糟。細心的讀者已經(jīng)看到了我已經(jīng)把項目改成了3.0,當(dāng)然我還在學(xué)習(xí)當(dāng)中袄秩,我也需要了解下有關(guān)3.0的特性阵翎,希望給沒有接觸到,或者剛剛接觸到的朋友們之剧,有一些幫助郭卫,當(dāng)然我這個不是隨波逐流,只是在眾多的博文中背稼,給大家一個入門參考贰军,屆時說3.0的時候,還是會說2.0的相關(guān)問題的蟹肘。
雖然項目整體可以運行了词疼,但是我還有幾個小知識點要說下,主要是1帘腹、依賴注入和AOP相關(guān)知識贰盗;2、跨域代理等問題(因為Vue是基于Node開發(fā)的阳欲,與后臺API接口不在同一個地址)舵盈;3、實體類的DTO相關(guān)小問題球化;4秽晚、Redis緩存等;5赊窥、部署服務(wù)器中的各種坑爆惧;雖然都是很小的知識點,我還是都下給大家說下的锨能,好啦扯再,開始今天的講解芍耘;
零、今天完成的綠色部分
一熄阻、依賴注入的理解和思考
說到依賴斋竞,我就想到了網(wǎng)上有一個例子,依賴注入和工廠模式中的相似和不同:
(1)原始社會里秃殉,沒有社會分工坝初。須要斧子的人(調(diào)用者)僅僅能自己去磨一把斧子(被調(diào)用者)。相應(yīng)的情形為:Java程序里的調(diào)用者自己創(chuàng)建被調(diào)用者钾军。
(2)進入工業(yè)社會鳄袍,工廠出現(xiàn)。斧子不再由普通人完畢吏恭,而在工廠里被生產(chǎn)出來拗小,此時須要斧子的人(調(diào)用者)找到工廠,購買斧子樱哼,無須關(guān)心斧子的制造過程哀九。相應(yīng)Java程序的簡單工廠的設(shè)計模式。
(3)進入“按需分配”社會搅幅,須要斧子的人不須要找到工廠阅束,坐在家里發(fā)出一個簡單指令:須要斧子。斧子就自然出如今他面前茄唐。相應(yīng)Spring的依賴****注入息裸。
在上篇文章中,我們已經(jīng)了解到了沪编,什么是依賴倒置界牡、控制反轉(zhuǎn)(IOC),什么是依賴注入(DI)漾抬,網(wǎng)上這個有很多很多的講解,我這里就不說明了常遂,其實主要是見到這樣的纳令,就是存在依賴
public class A : D
{ public A(B b)
{ // do something
}
C c = new C();
}
就比如我們的項目中的BlogController,只要是通過new 實例化的克胳,都是存在依賴
public async Task<List<Advertisement>> Get(int id)
{
IAdvertisementServices advertisementServices = new AdvertisementServices();
return await advertisementServices.Query(d => d.Id == id);
}
使用依賴注入呢平绩,有以下優(yōu)點:
- 傳統(tǒng)的代碼,每個對象負責(zé)管理與自己需要依賴的對象漠另,導(dǎo)致如果需要切換依賴對象的實現(xiàn)類時捏雌,需要修改多處地方。同時笆搓,過度耦合也使得對象難以進行單元測試性湿。
- 依賴注入把對象的創(chuàng)造交給外部去管理,很好的解決了代碼緊耦合(tight couple)的問題纬傲,是一種讓代碼實現(xiàn)松耦合(loose couple)的機制。
- 松耦合讓代碼更具靈活性肤频,能更好地應(yīng)對需求變動叹括,以及方便單元測試。
舉個栗子宵荒,就是關(guān)于日志記錄的
日志記錄:有時需要調(diào)試分析汁雷,需要記錄日志信息,這時可以采用輸出到控制臺报咳、文件侠讯、數(shù)據(jù)庫、遠程服務(wù)器等暑刃;假設(shè)最初采用輸出到控制臺厢漩,直接在程序中實例化ILogger logger = new ConsoleLogger(),但有時又需要輸出到別的文件中稍走,也許關(guān)閉日志輸出袁翁,就需要更改程序,把ConsoleLogger改成FileLogger或者NoLogger婿脸, new FileLogger()或者new SqlLogger() 粱胜,此時不斷的更改代碼,就顯得心里不好了狐树,如果采用依賴注入焙压,就顯得特別舒暢。
我有一個個人的理解抑钟,不知道恰當(dāng)與否涯曲,比如我們平時食堂吃飯,都是食堂自己炒的菜在塔,就算是配方都一樣幻件,控制者(廚師)還是會有些變化,或者是出錯蛔溃,但是肯德基這種绰沥,由總店提供的,店面就失去了控制贺待,就出現(xiàn)了第三方(總店或者工廠等)徽曲,這就是實現(xiàn)了控制反轉(zhuǎn),我們只需要說一句麸塞,奧爾良雞翅秃臣,嗯就拿出來一個,而且全部分店的都一樣哪工,我們不用管是否改配方奥此,不管是否依賴哪些調(diào)理食材弧哎,哈哈。
二得院、常見的IoC框架有哪些
Autofac:貌似目前net下用的最多吧
Ninject:目前好像沒多少人用了
Unity:也是較為常見
其實.Net Core 有自己的輕量級的IoC框架傻铣,
ASP.NET Core本身已經(jīng)集成了一個輕量級的IOC容器,開發(fā)者只需要定義好接口后祥绞,在Startup.cs的ConfigureServices方法里使用對應(yīng)生命周期的綁定方法即可非洲,常見方法如下
services.AddTransient<IApplicationService,ApplicationService>//服務(wù)在每次請求時被創(chuàng)建,它最好被用于輕量級無狀態(tài)服務(wù)(如我們的Repository和ApplicationService服務(wù))
services.AddScoped<IApplicationService,ApplicationService>//服務(wù)在每次請求時被創(chuàng)建蜕径,生命周期橫貫整次請求
services.AddSingleton<IApplicationService,ApplicationService>//Singleton(單例) 服務(wù)在第一次請求時被創(chuàng)建(或者當(dāng)我們在ConfigureServices中指定創(chuàng)建某一實例并運行方法)两踏,其后的每次請求將沿用已創(chuàng)建服務(wù)。如果開發(fā)者的應(yīng)用需要單例服務(wù)情景兜喻,請設(shè)計成允許服務(wù)容器來對服務(wù)生命周期進行操作梦染,而不是手動實現(xiàn)單例設(shè)計模式然后由開發(fā)者在自定義類中進行操作。
當(dāng)然.Net Core自身的容器還是比較簡單朴皆,如果想要更多的功能和擴展帕识,還是需要使用上邊上個框架。
三遂铡、較好用的IoC框架使用——Autofac
首先呢肮疗,我們要明白,我們注入是要注入到哪里——Controller API層扒接。然后呢伪货,我們看到了在接口調(diào)用的時候浑度,如果需要其中的方法惧互,需要using兩個命名空間
[HttpGet("{id}", Name = "Get")]
public async Task<List<Advertisement>> Get(int id)
{
IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用兩個命名空間Blog.Core.IServices;Blog.Core.Services;
return await advertisementServices.Query(d => d.Id == id);
}
接下來我們就需要做處理:
1、在Nuget中引入兩個:Autofac.Extras.DynamicProxy(Autofac的動態(tài)代理蔗牡,它依賴Autofac宗侦,所以可以不用單獨引入Autofac)愚臀、Autofac.Extensions.DependencyInjection(Autofac的擴展)
2、讓Autofac接管Starup中的ConfigureServices方法矾利,記得修改返回類型IServiceProvider
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
#region 配置信息
//Blog.Core.Repository.BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:SqlServerConnection").Value;
#endregion
#region Swagger
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v0.1.0",
Title = "Blog.Core API",
Description = "框架說明文檔",
TermsOfService = "None",
Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "http://www.reibang.com/u/94102b59cc2a" }
}); //就是這里
#region 讀取xml信息
var basePath = PlatformServices.Default.Application.ApplicationBasePath; var xmlPath = Path.Combine(basePath, "Blog.Core.xml");//這個就是剛剛配置的xml文件名
var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");//這個就是Model層的xml文件名
c.IncludeXmlComments(xmlPath, true);//默認的第二個參數(shù)是false懊悯,這個是controller的注釋,記得修改
c.IncludeXmlComments(xmlModelPath);
#endregion
#region Token綁定到ConfigureServices
//添加header驗證信息 //c.OperationFilter<SwaggerHeader>();
var security = new Dictionary<string, IEnumerable<string>> { { "Blog.Core", new string[] { } }, };
c.AddSecurityRequirement(security); //方案名稱“Blog.Core”可自定義梦皮,上下一致即可
c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme
{
Description = "JWT授權(quán)(數(shù)據(jù)將在請求頭中進行傳輸) 直接在下框中輸入{token}\"",
Name = "Authorization",//jwt默認的參數(shù)名稱
In = "header",//jwt默認存放Authorization信息的位置(請求頭中)
Type = "apiKey"
});
#endregion }); #endregion
#region Token服務(wù)注冊 services.AddSingleton<IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return cache;
});
services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy => policy.RequireClaim("AdminType").Build());//注冊權(quán)限管理,可以自定義多個
});
#endregion
#region AutoFac
//實例化 AutoFac 容器
var builder = new ContainerBuilder(); //注冊要通過反射創(chuàng)建的組件
builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); //將services填充到Autofac容器生成器中
builder.Populate(services); //使用已進行的組件登記創(chuàng)建新容器
var ApplicationContainer = builder.Build();
#endregion
return new AutofacServiceProvider(ApplicationContainer);//第三方IOC接管 core內(nèi)置DI容器
}
這個時候我們就把AdvertisementServices的new 實例化過程注入到了Autofac容器中
3桃焕、通過依賴注入的三種方式(構(gòu)造方法注入剑肯、setter方法注入和接口方式注入)中的構(gòu)造函數(shù)方式實現(xiàn)注入
在BlogController中,添加構(gòu)造函數(shù)观堂,并在Get方法中让网,去掉實例化過程呀忧;
IAdvertisementServices advertisementServices; /// <summary>
/// 構(gòu)造函數(shù) /// </summary>
/// <param name="advertisementServices"></param>
public BlogController(IAdvertisementServices advertisementServices)
{ this.advertisementServices = advertisementServices;
}
[HttpGet("{id}", Name = "Get")] public async Task<List<Advertisement>> Get(int id)
{ //IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用兩個命名空間Blog.Core.IServices;Blog.Core.Services;
return await advertisementServices.Query(d => d.Id == id);
}
4、然后運行調(diào)試溃睹,發(fā)現(xiàn)在斷點剛進入的時候而账,接口已經(jīng)被實例化了,達到了注入的目的因篇。
5泞辐、這個時候,我們發(fā)現(xiàn)已經(jīng)成功的注入了竞滓,Advertisement實體類到接口中咐吼,但是項目中有那么多的類,都要一個個手動添加么商佑,答案當(dāng)然不是滴~
四锯茄、通過反射將Blog.Core.Services和Blog.Core.Repository兩個程序集的全部方法注入
修改如下代碼,注意這個時候需要在項目依賴中茶没,右鍵肌幽,添加引用Blog.Core.Services到項目,或者把dll文件拷貝到Blog.Core的bin文件夾中抓半,否則反射會找不到喂急。
var builder = new ContainerBuilder(); //注冊要通過反射創(chuàng)建的組件
//builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();
var assemblysServices = Assembly.Load("Blog.Core.Services");
builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已掃描程序集中的類型注冊為提供所有其實現(xiàn)的接口。
var assemblysRepository = Assembly.Load("Blog.Core.Repository");
builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); //將services填充到Autofac容器生成器中
builder.Populate(services); //使用已進行的組件登記創(chuàng)建新容器
var ApplicationContainer = builder.Build();
其他不變琅关,運行項目煮岁,一切正常,換其他接口也可以
到這里涣易,Autofac依賴注入已經(jīng)完成画机,基本的操作就是這樣,別忙新症!現(xiàn)在還沒有真正的完成喲步氏!現(xiàn)在只是把Service和API層解耦了,Service和Repository還沒有徒爹!
注意:文中和Git代碼中荚醒,因為為了說明的方便,沒有把Api層的Service 層 和 Repository 層給去掉隆嗅,大家手動去掉界阁,我已經(jīng)更新到git上了。
最終的效果是這樣的:工程只是依賴接口層
2胖喳、把service.dll 和 Repository.dll 兩個文件拷貝到項目 的 bin \ debug \netcoreapp2.1 下
3泡躯、然后在Autofac依賴注入的時候,出現(xiàn)加載程序集失敗的情況,可以修改如下:
var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//獲取項目路徑
var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");//獲取注入項目絕對路徑
var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加載文件的方法
還記得Blog.Core.Services中的BaseServices.cs么较剃,它還是通過new 實例化的方式在創(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;//通過在子類的構(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)在整個項目已經(jīng)完成了相互直接解耦的功能啊送,以后就算是Repository和Service如何變化偿短,接口層都不用修改,因為已經(jīng)完成了注入删掀,第三方Autofac會做實例化的過程翔冀。
五、 當(dāng)然披泪,你也可以直接將一個類進行注入纤子,而不一定要繼承接口的方式;
//1\. 定義一個服務(wù)款票,包含一個方法
public class PeopleService
{ public string Run(string m) { return m; }
} //2\. 寫一個擴展方法用來注入服務(wù)控硼,與直接在ConfigureServices返回是一個道理
namespace Haos.Develop.CoreTest
{ public static class Extension
{ public static IServiceCollection AddTestService(this IServiceCollection service)
{ return service.AddScoped(factory => new PeopleService());
}
}
} //3\. 回到Startup類中找到ConfigureServices方法添加如下代碼
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTestService();//將上邊的方法注入
} //4.我們可以采用構(gòu)造函數(shù)方式來注入和直接獲取
public PeopleService People; public HomeController(PeopleService people)//構(gòu)造函數(shù)
{
People = people;
} public JsonResult Test()
{
People.Run("111");
return Json("");
}
上邊這個方法采用的是單獨注冊一個擴展方法來注入服務(wù),和我們的直接return是一樣的艾少,感興趣的可以試一試卡乾。
同時可以參考這個網(wǎng)友的:
https://www.cnblogs.com/stulzq/p/6880394.html
六、簡單了解通過AOP切面實現(xiàn)日志記錄
什么是AOP缚够?引用百度百科:AOP為Aspect Oriented Programming的縮寫幔妨,意為:面向切面編程,通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)谍椅。實現(xiàn)AOP主要由兩種方式误堡,
一種是編譯時靜態(tài)植入,優(yōu)點是效率高雏吭,缺點是缺乏靈活性锁施,.net下postsharp為代表者(好像是付費了。杖们。)悉抵。
另一種方式是動態(tài)代理,優(yōu)點是靈活性強摘完,但是會影響部分效率姥饰,動態(tài)為目標(biāo)類型創(chuàng)建代理,通過代理調(diào)用實現(xiàn)攔截孝治。
AOP能做什么列粪,常見的用例是事務(wù)處理栅螟、日志記錄等等。
常見的AOP都是配合在Ioc的基礎(chǔ)上進行操作篱竭,上邊咱們講了Autofac這個簡單強大的Ioc框架,下面就講講Autofac怎么實現(xiàn)AOP步绸。Autofac的AOP是通過Castle(也是一個容器)項目的核心部分實現(xiàn)的掺逼,名為Autofac.Extras.DynamicProxy,顧名思義,其實現(xiàn)方式為動態(tài)代理瓤介。當(dāng)然AOP并不一定要和依賴注入在一起使用吕喘,自身也可以單獨使用。
網(wǎng)上有一個博友的圖片刑桑,大概講了AOP切面編程
七氯质、結(jié)語
今天的文章呢,主要說了依賴注入IoC在項目中的使用祠斧,從上邊的說明中闻察,可以看到,最大的一個優(yōu)點就是實現(xiàn)解耦的作用琢锋,最明顯的就是辕漂,在Controller中,不用在實例服務(wù)層或者業(yè)務(wù)邏輯層了吴超,不過還是有些缺點的钉嘹,缺點之一就是會占用一定的時間資源,效率稍稍受影響鲸阻,不過和整體框架相比跋涣,這個影響應(yīng)該也是值得的。
明天鸟悴,我們繼續(xù)將面向切面編程AOP中的陈辱,日志記錄和面向AOP的緩存使用。
八遣臼、CODE
https://github.com/anjoy8/Blog.Core
QQ群:
867095512 (blod.core)