1. 程式人生 > >[Abp 源碼分析]四、模塊配置

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

數據 ack img type 流程 ice repr action 擴展方法

0.簡要介紹

在 Abp 框架當中通過各種 Configuration 來實現模塊的配置,Abp 本身提供的很多基礎設施功能的一些在運行時的行為是通過很多不同的 Configuration 來開放給用戶進行一些自定義配置的。

比如說緩存模塊,我要配置緩存的過期時間,Abp 默認是 1 個小時,但是我也可以自己來定義,直接賦值或者從配置項來讀取都是由具體使用者來控制的,所以 Abp 通過各種 Configuration 類來控制一些運行時參數。

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

技術分享圖片

1.啟動流程

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

那麽 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 接口賦值,代碼如下:

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 接口,讓你很方便的就可以在模塊的預加載的時候配置各種基礎設施的參數。舉個栗子:

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

技術分享圖片

可以看到這裏我們的 Configuration 屬性其實就是 IAbpStartupConfiguration 接口。

2.代碼分析

2.1自定義模塊配置

我們可以看到 IAbpStartupConfiguration 除了自己擁有大量基礎設施的配置類,同時他還繼承一個基類叫做 DictionaryBasedConfig ,那麽 Abp 框架為什麽要這麽寫呢?

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

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=>{ /*配置代碼*/});

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