[Abp 源碼分析]4、模塊配置

0.簡要介紹

在 Abp 框架當中經過各類 Configuration 來實現模塊的配置,Abp 自己提供的不少基礎設施功能的一些在運行時的行爲是經過不少不一樣的 Configuration 來開放給用戶進行一些自定義配置的。html

好比說緩存模塊,我要配置緩存的過時時間,Abp 默認是 1 個小時,可是我也能夠本身來定義,直接賦值或者從配置項來讀取都是由具體使用者來控制的,因此 Abp 經過各類 Configuration 類來控制一些運行時參數。數據庫

這些 Abp 自己基礎設施的配置類都是存放在 \Abp\src\Abp\Configuration\Startup\ 這個文件夾內部的,咱們來看一下他們的依賴關係。緩存

1.啓動流程

從上圖能夠看到在 IAbpStartupConfiguration 內部擁有諸多引用(可能沒有列舉完成,能夠在其定義看到),基本上 Abp 本身的基礎設施配置都在這裏面。app

那麼 IAbpStartupConfiguration 本身內部的這些屬性是在哪兒初始化的呢,其實就是在以前講過的 AbpBootstrapperInitialize() 內部初始化的。再看下代碼:框架

public virtual void Initialize()
{
    try
    {
        // 其餘代碼
        IocManager.IocContainer.Install(new AbpCoreInstaller());
        IocManager.Resolve<AbpStartupConfiguration>().Initialize();
        // 其餘代碼
    }
    catch (Exception ex)
    {
        _logger.Fatal(ex.ToString(), ex);
        throw;
    }
}

AbpCoreInstaller 類內部以前也說過,在這裏面統一注入了這些 Configuration 的單例,同時解析出 AbpStartupConfiguration ,調用其 Initialzie() 方法來對本身的那些 xxxConfiguration 接口賦值,代碼以下:ide

public void Initialize()
{
    Localization = IocManager.Resolve<ILocalizationConfiguration>();
    Modules = IocManager.Resolve<IModuleConfigurations>();
    Features = IocManager.Resolve<IFeatureConfiguration>();
    Navigation = IocManager.Resolve<INavigationConfiguration>();
    Authorization = IocManager.Resolve<IAuthorizationConfiguration>();
    Validation = IocManager.Resolve<IValidationConfiguration>();
    Settings = IocManager.Resolve<ISettingsConfiguration>();
    UnitOfWork = IocManager.Resolve<IUnitOfWorkDefaultOptions>();
    EventBus = IocManager.Resolve<IEventBusConfiguration>();
    MultiTenancy = IocManager.Resolve<IMultiTenancyConfig>();
    Auditing = IocManager.Resolve<IAuditingConfiguration>();
    Caching = IocManager.Resolve<ICachingConfiguration>();
    BackgroundJobs = IocManager.Resolve<IBackgroundJobConfiguration>();
    Notifications = IocManager.Resolve<INotificationConfiguration>();
    EmbeddedResources = IocManager.Resolve<IEmbeddedResourcesConfiguration>();
    EntityHistory = IocManager.Resolve<IEntityHistoryConfiguration>();

    CustomConfigProviders = new List<ICustomConfigProvider>();
    ServiceReplaceActions = new Dictionary<Type, Action>();
}

因此,在模塊定義的基類 AbpModule 當中,早就注入了 IAbpStartupConfiguration 接口,讓你很方便的就能夠在模塊的預加載的時候配置各類基礎設施的參數。舉個栗子:this

public override void PreInitialize()
{
    Configuration.Caching.ConfigureAll(z=>z.DefaultSlidingExpireTime = TimeSpan.FromHours(1));
}

能夠看到這裏咱們的 Configuration 屬性其實就是 IAbpStartupConfiguration 接口。3d

2.代碼分析

2.1自定義模塊配置

咱們能夠看到 IAbpStartupConfiguration 除了本身擁有大量基礎設施的配置類,同時他還繼承一個基類叫作 DictionaryBasedConfig ,那麼 Abp 框架爲何要這麼寫呢?code

其實這個基類的做用就是存放用戶自定義的 Configuration 類型的,細心觀察的話會發如今 AbpStartupConfiguration 的內部有一個 Get 方法,該方法就是用來獲取存儲的配置類型。htm

public T Get<T>()
{
    // 調用基類的 GetOrCreate 方法,不存在的話直接從 IocContainer 中解析
    return GetOrCreate(typeof(T).FullName, () => IocManager.Resolve<T>());
}

DictionaryBasedConfig 中維護了一個字典 CustomSettings ,其 Key/Value 類型爲 string/object ,由於在 Abp 框架當中是不知道你自定義模塊配置類的類型的,因此存了一個 object 對象。

而後就有如下用法,首先在模塊 PreInitialize() 方法當中注入你須要注入的配置類:

public override void PreInitialize()
{
    // 注入配置類
    IocManager.Register<IAbpAspNetCoreConfiguration, AbpAspNetCoreConfiguration>();

    // 替換服務,後面講解
    Configuration.ReplaceService<IPrincipalAccessor, AspNetCorePrincipalAccessor>(DependencyLifeStyle.Transient);
    Configuration.ReplaceService<IAbpAntiForgeryManager, AbpAspNetCoreAntiForgeryManager>(DependencyLifeStyle.Transient);
    Configuration.ReplaceService<IClientInfoProvider, HttpContextClientInfoProvider>(DependencyLifeStyle.Transient);
}

而後針對 IModuleConfigurations 寫一個擴展方法,由於在 IModuleConfigurations 內部就有一個 IAbpAspNetCoreConfiguration 的實例,IModuleConfigurations 的註釋就說該接口是用於配置模塊的,模塊能夠經過編寫擴展方法來添加本身的 Configuration 類:

public static class AbpAspNetCoreConfigurationExtensions
{
    /// <summary>
    /// Used to configure ABP ASP.NET Core module.
    /// </summary>
    public static IAbpAspNetCoreConfiguration AbpAspNetCore(this IModuleConfigurations configurations)
    {
        // 兩種寫法都差很少
        return configurations.AbpConfiguration.GetOrCreate("AbpModule", () => IocManager.Resolve<IAbpAspNetCoreConfiguration>());
        return configurations.AbpConfiguration.Get<IAbpAspNetCoreConfiguration>();
    }
}

2.2 服務實現替換

在 Abp 當中容許咱們替換一些他自己的一些實現,只要你是在模塊進行預加載的時候替換的話,都是能夠的。而 Abp 他自己在 IAbpStartupConfiguration 當中提供了一個方法叫作 ReplaceService() 方法專門來讓你替換服務。

咱們來看一下他的定義:

void ReplaceService(Type type, Action replaceAction);

emmmm,傳入一個 TypeAction,咋跟我看到的不同呢,Ctrl + N 搜索了一下,發如今模塊裏面使用的 ReplaceService() 方法是存放在 AbpStartupConfigurationExtensions 裏面編寫的一個靜態方法,其定義以下:

public static void ReplaceService<TType, TImpl>(this IAbpStartupConfiguration configuration, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
    where TType : class
    where TImpl : class, TType
{
    configuration.ReplaceService(typeof(TType), () =>
    {
        configuration.IocManager.Register<TType, TImpl>(lifeStyle);
    });
}

我來看看,傳入一個 Type 和 一個 ActionType 用來調用 IAbpStartupConfiguration 的同名方法,Action 則是用來註冊組件的。

原來如此,咱們再來到 IAbpStartupConfiguration.ReplaceService(Type type, Action replaceAction) 的具體實現:

public Dictionary<Type, Action> ServiceReplaceActions { get; private set; }

public void ReplaceService(Type type, Action replaceAction)
{
    ServiceReplaceActions[type] = replaceAction;
}

唔,就是一個字典嘛,咱們來看看在什麼地方用到過它。

public override void Initialize()
{
    foreach (var replaceAction in ((AbpStartupConfiguration)Configuration).ServiceReplaceActions.Values)
    {
        replaceAction();
    }
    
    // 其餘代碼
}

最後咱們看到在 AbpKernelModuleInitialize() 方法裏面就會遍歷這個字典,來調用以前存入的 Action

由於 Abp 全部組件的註冊都是在模塊 Initialize() 內部來進行註冊的,而這串代碼恰好又放在 AbpKernelModule 的初始化方法的第一行就開始執行,因此確保你替換的組件可以在 Abp 內部組件註冊前執行。

因此當你要替換 Abp 內置組件服務的時候必定要記住在模塊的 PreInitialize() 裏面執行哦~

3. 擴展:Abp 支持多數據庫

若是你的 Abp 項目有多個數據庫上下文實體的時候怎麼辦呢?

在 Abp 官方 Demo 當中就有說明,你能夠經過替換默認的 IConnectionStringResolver 來實現不一樣數據庫的解析哦~,咱們繼承 DefaultConnectionStringResolver 實現一個 MulitDbContextConnectionStringResolver

public class MulitDbContextConnectionStringResolver : DefaultConnectionStringResolver
{
    public HKERPConnectionStringResolver(IAbpStartupConfiguration configuration)
        : base(configuration)
    {
    }

    public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
    {
        if (args["DbContextConcreteType"] as Type == typeof(ADbContext))
        {
            var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());
            // 返回 ADbContext 的 ConnectionString
            return configuration.GetConnectionString(AllConsts.ADbConnectionStringName);
        }
        
        if (args["DbContextConcreteType"] as Type == typeof(BDbContext))
        {
            var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());
            // 返回 BDbContext 的 ConnectionString
            return configuration.GetConnectionString(HKERPCRMConsts.BDbConnectionStringName);
        }
        
        // 都不是則使用默認的數據庫鏈接字符串

        return base.GetNameOrConnectionString(args);
    }

}

而後在咱們的 EFCore 模塊的預加載方法當中加入如下代碼:

Configuration.ReplaceService(typeof(IConnectionStringResolver), () =>
{
    IocManager.IocContainer.Register(
        Component.For<IConnectionStringResolver, IDbPerTenantConnectionStringResolver>()
            .ImplementedBy<MulitDbContextConnectionStringResolver>()
            .LifestyleTransient()
        );
});

固然你也不要忘記在後面經過 AddDbContext() 方法來把你的數據庫上下文添加到 Abp 裏面去哦。

Configuration.Modules.AbpEfCore().AddDbContext<ADbContext>(options=>{ /*配置代碼*/});
Configuration.Modules.AbpEfCore().AddDbContext<BDbContext>(options=>{ /*配置代碼*/});

3.點此跳轉到總目錄

相關文章
相關標籤/搜索