3. abp依賴註入的分析.md

来源:https://www.cnblogs.com/zhiyong-ITNote/archive/2019/12/08/12005772.html
-Advertisement-
Play Games

abp依賴註入的原理剖析 請先移步參考 "[Abp vNext 源碼分析] 3. 依賴註入與攔截器" 本文此篇文章的補充和完善。 abp的依賴註入最後是通過IConventionalRegister介面的AddType方法實現的。先看下該介面: 該介面定義了三個方法,支持傳入程式集、類型數組、具體類 ...


abp依賴註入的原理剖析

請先移步參考 [Abp vNext 源碼分析] - 3. 依賴註入與攔截器 本文此篇文章的補充和完善。

abp的依賴註入最後是通過IConventionalRegister介面的AddType方法實現的。先看下該介面:

public interface IConventionalRegistrar
{
    void AddAssembly(IServiceCollection services, Assembly assembly);

    void AddTypes(IServiceCollection services, params Type[] types);

    void AddType(IServiceCollection services, Type type);
}

該介面定義了三個方法,支持傳入程式集、類型數組、具體類型,其實現在一個抽象類==ConventionalRegistrarBase==中:

public abstract class ConventionalRegistrarBase : IConventionalRegistrar
{
    public virtual void AddAssembly(IServiceCollection services, Assembly assembly)
    {
        var types = AssemblyHelper
            .GetAllTypes(assembly)
            .Where(
                type => type != null &&
                        type.IsClass &&
                        !type.IsAbstract &&
                        !type.IsGenericType
            ).ToArray();

        AddTypes(services, types);
    }

    public virtual void AddTypes(IServiceCollection services, params Type[] types)
    {
        foreach (var type in types)
        {
            AddType(services, type);
        }
    }

    public abstract void AddType(IServiceCollection services, Type type);
}

不管是程式集,還是類型數組最後都是調用AddType方法,AddType的實現在這個抽象類的派生類中,abp有一個預設的實現類==DefaultConventionalRegistrar==,該類實現了AddType方法,abp的依賴註入就是通過該類的AddType方法註入的。源碼:

public class DefaultConventionalRegistrar : ConventionalRegistrarBase
{
    public override void AddType(IServiceCollection services, Type type)
    {
        if (IsConventionalRegistrationDisabled(type))
        {
            return;
        }

        var dependencyAttribute = GetDependencyAttributeOrNull(type);
        var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute);

        if (lifeTime == null)
        {
            return;
        }

        var serviceTypes = ExposedServiceExplorer.GetExposedServices(type);

        TriggerServiceExposing(services, type, serviceTypes);

        foreach (var serviceType in serviceTypes)
        {
            var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);

            if (dependencyAttribute?.ReplaceServices == true)
            {
                services.Replace(serviceDescriptor);
            }
            else if (dependencyAttribute?.TryRegister == true)
            {
                services.TryAdd(serviceDescriptor);
            }
            else
            {
                services.Add(serviceDescriptor);
            }
        }
    }
    
    // 其他方法實現
}

從這個類中可以看出abp依賴註入的實現思路:根據GetExposedServices方法返回的服務類型列表去構造服務描述符(服務描述符的第一個參數就是服務類型,第二個參數就是實現類型)。而實現類型就是我們要註入的類型。再將此服務描述符註入到DI容器中。
GetDependencyAttributeOrNull方法和GetLifeTimeOrNull方法是獲取使用了Dependency特性註入的類及其生命周期,如果沒有則使用預設的生命周期,因此如果Dependency特性的註入優先順序更高。GetExposedServices是在靜態類ExposedServiceExplorer中,該靜態類是用來獲取註入類型的定義及實現的。源碼實現:

public static List<Type> GetExposedServices(Type type)
{
    return type
        .GetCustomAttributes()
        .OfType<IExposedServiceTypesProvider>()
        .DefaultIfEmpty(DefaultExposeServicesAttribute)
        .SelectMany(p => p.GetExposedServiceTypes(type))
        .ToList();
}

IExposedServicveTypeProvider介面定義及實現:

//定義:
public interface IExposedServiceTypesProvider
{
    Type[] GetExposedServiceTypes(Type targetType);
}

//實現:
public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
{
    public ExposeServicesAttribute(params Type[] serviceTypes)
    {
        ServiceTypes = serviceTypes ?? new Type[0];
    }
    
    public Type[] GetExposedServiceTypes(Type targetType)
    {
        var serviceList = ServiceTypes.ToList();
        
        if (IncludeDefaults == true)
        {
            foreach (var type in GetDefaultServices(targetType))
            {
                serviceList.AddIfNotContains(type);
            }
        
            if (IncludeSelf != false)
            {
                serviceList.AddIfNotContains(targetType);
            }
        }
        else if (IncludeSelf == true)
        {
            serviceList.AddIfNotContains(targetType);
        }
        
        return serviceList.ToArray();
    }
    
    private static List<Type> GetDefaultServices(Type type)
    {
        var serviceTypes = new List<Type>();
        
        foreach (var interfaceType in type.GetTypeInfo().GetInterfaces())
        {
            var interfaceName = interfaceType.Name;
        
            if (interfaceName.StartsWith("I"))
            {
                interfaceName = interfaceName.Right(interfaceName.Length - 1);
            }
        
            if (type.Name.EndsWith(interfaceName))
            {
                serviceTypes.Add(interfaceType);
            }
        }
        
        return serviceTypes;
    }
}

該介面的實現是在ExposeServices特性的實現類中,這是個特性類,是abp三種註入服務的第一種——ExposeServices特性註入。該類的構造函數會直接保存要註入類型的服務列表。其次就是GetDefaultService方法,該方法會返回預設的服務類型。通過反射獲取類繼承的介面,並截取介面的名稱(除去I之後的介面名),==只有實現類與介面名稱相同的條件下才會註入到服務類型列表中==,這點要註意!對於該方法返回的類型會被添加到服務類型列表中(ServiceTypes)。==預設情況下,實現類本身會註入到服務類型列表中==,從源碼中可以分析到:

if (IncludeSelf != false)
{
    serviceList.AddIfNotContains(targetType);
}

targetType就是我們當前註入的類型。在此便註入了類型本身。如此的好處是,可以獲取到類的實例,減少了直接實例化而帶來依賴。

如此便返回了註入類型的定義及實現列表(serviceTypes),而後遍歷這個列表,服務描述符(ServiceDescriptor)的參數ServiceType就是這個列表的項。這個服務描述符便註入到了DI容器中。對於Dependency特性註入的方式,如果參數是ReplaceServices,那麼將會替換;如果參數是Register,那麼將會直接註入。否則的話,直接添加進DI容器中。

三種註入方式的實現:

  • ExposeServices特性的註入分析
// 介面
public interface IMessageWriter
{
    void Write();
}

// 實現 1
[ExposeServices(typeof(IMessageWriter))]
public class TestMessageTwo : IMessageWriter, ITransientDependency
{
    public void Write()
    {
        Console.WriteLine("TestMessageTwo");
    }
}

// 實現 2
[ExposeServices(typeof(IMessageWriter), typeof(TestMessageOne))]
public class TestMessageOne : IMessageWriter, ITransientDependency
{
    public void Write()
    {
        Console.WriteLine("TestMessageOne");
    }
}

// 註入
_services = new ServiceCollection();
_services.AddType<TestMessageOne>();
_services.AddType<TestMessageTwo>();

// 底層調用:
var serviceTypes = ExposedServiceExplorer.GetExposedServices(type);

abp底層通過ExposedServiceExplorer靜態類的GetExposedServices方法確定需要註冊類型的定義和實現。這個靜態類最後實際上是調用了ExposeServicesAttribute類的構造函數和GetExposedServiceTypes方法確定了服務類型列表。

public ExposeServicesAttribute(params Type[] serviceTypes)
{
    ServiceTypes = serviceTypes ?? new Type[0];
}

public Type[] GetExposedServiceTypes(Type targetType)
{
}
  • Dependency特性註入
//介面
public interface IMyService : ITransientDependency
{
}

//實現
[Dependency(TryRegister = true)]
public class TryRegisterImplOfMyService : IMyService
{
}

//註入
_services = new ServiceCollection();
_services.AddTypes(typeof(TryRegisterImplOfMyService));

//底層調用
ExposeServicesAttribute.GetDefaultServices(typeof(TryRegisterImplOfMyService));

Dependency特性註入在調用GetDefaultServices方法返回服務類型列表,而後在DefaultConventionalRegistrar類的AddType方法中構造服務描述符,註入到DI中。

var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);

if (dependencyAttribute?.ReplaceServices == true)
{
    services.Replace(serviceDescriptor);
}
else if (dependencyAttribute?.TryRegister == true)
{
    services.TryAdd(serviceDescriptor);
}

備註:
對於Dependency註入和介面方式註入,實現類的類名必須以介面名結尾,否則將不能註入到DI中。

  • 介面註入
//介面
public interface IMyService : ITransientDependency
{
}

//實現 1
public class FirstImplOfMyService : IMyService
{
}

//實現 2
public class SecondImplOfMyService : IMyService
{
}

// 註入
_services = new ServiceCollection();
_services.AddTypes(typeof(FirstImplOfMyService),typeof(SecondImplOfMyService));

//底層調用
ExposeServicesAttribute.GetDefaultServices(typeof(TryRegisterImplOfMyService));

介面方式的註入,也是調用GetDefaultServices返回一個類型列表,然後遍歷,保存到服務類型列表中,最後註入到DI容器中。

  • ReplaceServices替換
    如果介面方式註入與Dependency特性註入同時使用且介面相同那麼就是另外一種情況,示例:
// 介面
public interface IMyService : ITransientDependency
{
}

// 介面方式實現
public class FirstImplOfMyService : IMyService
{
}

// Dependency特性註入 -- 替換掉 介面方式註入的實現
[Dependency(ReplaceServices = true)]
public class MyServiceReplacesIMyService : IMyService
{
}

// 註入
_services = new ServiceCollection();
_services.AddTypes(typeof(FirstImplOfMyService),typeof(MyServiceReplacesIMyService));

使用ReplaceServices將會使Dependency特性註入替換介面方式的註入。因此只有Dependency特性的註入會被添加到DI容器中。


代碼示例:

#region ExposeServices 屬性註入
public interface ICalculator { }

public interface ITaxCalculator { }

[ExposeServices(typeof(IService))]
public class TaxCalculator : ICalculator, ITaxCalculator, ITransientDependency
{
}
#endregion


#region 介面約定 模式註入

public interface IService : ITransientDependency { }

public class MyService : IService
{
}

#endregion

#region Dependency特性註入

public interface IMyDependencyTest { }

[Dependency(lifetime: ServiceLifetime.Transient, TryRegister = true)]
public class MyDependencyTest : IMyDependencyTest { }

#endregion

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddType(typeof(MyDependencyTest));
        services.AddType(typeof(MyService));
        services.AddType<TaxCalculator>();

        foreach(var service in services)
        {
            Console.WriteLine($"{service.ServiceType} --- {service.ImplementationType} --- {service.Lifetime}");
        }

        Console.Read();
    }
}

除去ExposeServices屬性註入外,其餘的兩種模式必須介面與類名相對應。否則,就只能註入類本身,但是,ExposeServics屬性註入不會截取介面名與類名比較。

輸出:

img


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 雖然說學習新的開發框架是一項巨大的投資,但是作為一個開發人員,不斷學習新的技術並快速上手是我們應該掌握的技能,甚至是一個.NET Framework開發人員,學習.NET Core 新框架可以更快速掌握其中的編寫,構建,測試,部署和維護應用程式。 您現有的.NET Framework應用程式可 ...
  • 我們可以使有dotnetcore跨平臺的特性,優雅的實現在dotnetcore執行shell (bash). 代碼如下:using System;using System.Collections.Generic;using System.Text;namespace hshoc{ using Sys... ...
  • [toc] 控制台視窗是一種簡單的命令提示視窗,允許程式顯示文本並從鍵盤接受輸人。 使用 方法獲取輸入,使用 方法輸出。 從控制台獲取輸入 可用 方法獲取控制台輸入的文本。程式執行到這一行代碼時,它將暫停程式執行並等待用戶輸入。用戶輸入內容後(也可以不輸入)按回車鍵,程式將繼續執行。 方法的輸出,也 ...
  • [toc] 程式離不開數據。把數字、字母和文字輸入電腦,就是希望它利用這些數據完成某些任務。例如,需要計算雙十一怎麼買才最省錢或者顯示購物車裡面的商品列表。 C 語言必須允許程式存儲和讀取數據,才能進行各種複雜的計算,而這正是通過變數實現的。 變數的聲明 上面這行代碼叫作聲明變數(declarat ...
  • 1、準備工作 環境 本地: 、`Docker` 代碼倉庫: 伺服器: 、`Docker` 前提準備 1. 創建個有 文件的 項目 新建一個dotnet 3.0的web項目,在項目文件夾添加Dockerfile文件,內容如下: 2. 準備git倉庫,將項目的代碼上傳上去 3. 構建有 的`jenkin ...
  • 大家好,我是Dotnet9小編,一個從事dotnet開發8年+的程式員。我最近開始寫dotnet分享文章,希望能讓更多人看到dotnet的發展,瞭解更多dotnet技術,幫助dotnet程式員應用dotnet技術更好的運用於工作和學習中去。 歷經3個白天2個黑夜(至凌晨2點),Dotnet9小編經過 ...
  • 一、鍵盤類和鍵盤事件 WPF提供了基礎的鍵盤類(System.Input.Keyboard類),該類提供與鍵盤相關的事件、方法和屬性,這些事件、方法和屬性提供有關鍵盤狀態的信息。Keyboard的事件也通過UIElement等XAML基元素類的事件向外提供。 對於鍵盤操作,其常用的事件有兩組: Ke ...
  • 項目框架介紹: 1:Application: 在service裡面事件具體業務,Dto相當於viewmodel實現了驗證 2:Core:實現了數據層Model 3:EntityFrameworkCore: 資料庫管理 對Model的改動使用 add-migration xxxx(這裡是你給這次遷移文 ...
一周排行
    -Advertisement-
    Play Games
  • JWT(JSON Web Token)是一種用於在網路應用之間傳遞信息的開放標準(RFC 7519)。它使用 JSON 對象在安全可靠的方式下傳遞信息,通常用於身份驗證和信息交換。 在Web API中,JWT通常用於對用戶進行身份驗證和授權。當用戶登錄成功後,伺服器會生成一個Token並返回給客戶端 ...
  • 老周在幾個世紀前曾寫過樹莓派相關的 iOT 水文,之所以沒寫 Nano Framework 相關的內容,是因為那時候這貨還不成熟,可玩性不高。不過,這貨現在已經相對完善,老周都把它用在項目上了——第一個是自製的智能插座,這個某寶上50多塊可以買到,搜“esp32 插座”就能找到。一種是 86 型盒子 ...
  • 引言 上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。 Xunit.DependencyInjection Xunit.DependencyInjection 是一個用於 xUnit ...
  • 在 Avalonia 中,樣式是定義控制項外觀的一種方式,而控制項主題則是一組樣式和資源,用於定義應用程式的整體外觀和感覺。本文將深入探討這些概念,並提供示例代碼以幫助您更好地理解它們。 樣式是什麼? 樣式是一組屬性,用於定義控制項的外觀。它們可以包括背景色、邊框、字體樣式等。在 Avalonia 中,樣 ...
  • 在處理大型Excel工作簿時,有時候我們需要在工作表中凍結窗格,這樣可以在滾動查看數據的同時保持某些行或列固定不動。凍結窗格可以幫助我們更容易地導航和理解複雜的數據集。相反,當你不需要凍結窗格時,你可能需要解凍它們以獲得完整的視野。 下麵將介紹如何使用免費.NET庫通過C#實現凍結Excel視窗以鎖 ...
  • .NET 部署 IIS 的簡單步驟一: 下載 dotnet-hosting-x.y.z-win.exe ,下載地址:.NET Downloads (Linux, macOS, and Windows) (microsoft.com) .NET 部署 IIS 的簡單步驟二: 選擇對應的版本,點擊進入詳 ...
  • 拓展閱讀 資料庫設計工具-08-概覽 資料庫設計工具-08-powerdesigner 資料庫設計工具-09-mysql workbench 資料庫設計工具-10-dbdesign 資料庫設計工具-11-dbeaver 資料庫設計工具-12-pgmodeler 資料庫設計工具-13-erdplus ...
  • 初識STL STL,(Standard Template Library),即"標準模板庫",由惠普實驗室開發,STL中提供了非常多對信息學奧賽很有用的東西。 vector vetor是STL中的一個容器,可以看作一個不定長的數組,其基本形式為: vector<數據類型> 名字; 如: vector ...
  • 前言 最近自己做了個 Falsk 小項目,在部署上伺服器的時候,發現雖然不乏相關教程,但大多都是將自己項目代碼複製出來,不講核心邏輯,不太簡潔,於是將自己部署的經驗寫成內容分享出來。 uWSGI 簡介 uWSGI: 一種實現了多種協議(包括 uwsgi、http)並能提供伺服器搭建功能的 Pytho ...
  • 1 文本Embedding 將整個文本轉化為實數向量的技術。 Embedding優點是可將離散的詞語或句子轉化為連續的向量,就可用數學方法來處理詞語或句子,捕捉到文本的語義信息,文本和文本的關係信息。 ◉ 優質的Embedding通常會讓語義相似的文本在空間中彼此接近 ◉ 優質的Embedding相 ...