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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...