ASP.NET CORE 第四篇 依賴注入IoC學(xué)習(xí) + AOP界面編程初探

原文作者:老張的哲學(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)始今天的講解惜互;

零布讹、今天完成的綠色部分

image.png

一、依賴注入的理解和思考

更新(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)目注入


image.png

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

image
image

AddSingleton的對(duì)象沒(méi)有變
AddScoped的對(duì)象沒(méi)有變化
AddTransient的對(duì)象發(fā)生變化


請(qǐng)求 // GET api/values/5

image

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ò)展)

image.png
2戴质、接管ConfigureServices

讓Autofac接管Starup中的ConfigureServices方法,記得修改返回類型IServiceProvider

image.png

這個(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)成功

image.png

注意:這里經(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ō)下使用方法:

image.png

這個(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ù):

image.png

核心代碼如下锭亏,注意這里是 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)目固该,一切正常锅减,換其他接口也可以

image

到這里,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è)接口層玉工。

image

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\”


image.png

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ò)誤:

image

這個(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 :

image

原因:在 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 文件

image

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;
        }       
    }
image.png

好啦评汰,現(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)容:

image

五、 無(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ǔ)層給解耦了。

image

2卖氨、如果沒(méi)有接口

案例是這樣的:

如果我們的項(xiàng)目是這樣的会烙,沒(méi)有接口,會(huì)怎么辦:


image
// 服務(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)類層村斟,這樣注入:

image

通過(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切面編程

image

八、結(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的緩存使用筷屡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市簸喂,隨后出現(xiàn)的幾起案子毙死,更是在濱河造成了極大的恐慌,老刑警劉巖喻鳄,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扼倘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)再菊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門爪喘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人纠拔,你說(shuō)我怎么就攤上這事秉剑。” “怎么了稠诲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵侦鹏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我臀叙,道長(zhǎng)略水,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任劝萤,我火速辦了婚禮渊涝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘床嫌。我一直安慰自己跨释,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布既鞠。 她就那樣靜靜地躺著煤傍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘱蛋。 梳的紋絲不亂的頭發(fā)上蚯姆,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音洒敏,去河邊找鬼龄恋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凶伙,可吹牛的內(nèi)容都是我干的郭毕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼函荣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼显押!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起傻挂,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乘碑,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后金拒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體兽肤,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了资铡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片电禀。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖笤休,靈堂內(nèi)的尸體忽然破棺而出尖飞,到底是詐尸還是另有隱情,我是刑警寧澤宛官,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布葫松,位于F島的核電站,受9級(jí)特大地震影響底洗,放射性物質(zhì)發(fā)生泄漏腋么。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一亥揖、第九天 我趴在偏房一處隱蔽的房頂上張望珊擂。 院中可真熱鬧,春花似錦费变、人聲如沸摧扇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扛稽。三九已至,卻和暖如春滑负,著一層夾襖步出監(jiān)牢的瞬間在张,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工矮慕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帮匾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓痴鳄,卻偏偏與公主長(zhǎng)得像瘟斜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痪寻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容