Prism框架中的Module概述(中)
背景
在上篇中我們主要介紹了Prism8中的IModuleInfo和IModuleCatalog介面,通過這兩個介面使我們對Prism框架中的Module有一個比較大概的瞭解,今天這篇文章我們主要來看一下Prism8中的最重要的一個介面IModuleManager這個介面,這個介面對應的實現ModuleManager實現了對整個載入到應用程式中的所有Module進行一個統一的管理,我們來重點看下這個ModuleManager依賴於哪些注入物件以及自身通過實現介面實現了哪些功能?
原始碼分析
1 IModuleManager介面
我們先來看看這個介面中定義了哪些內容?
/// <summary> /// Defines the interface for the service that will retrieve and initialize the application's modules. /// </summary> public interface IModuleManager { /// <summary> /// Gets all the <see cref="IModuleInfo"/> classes that are in the <see cref="IModuleCatalog"/>. /// </summary> IEnumerable<IModuleInfo> Modules { get; } /// <summary> /// Initializes the modules marked as <see cref="InitializationMode.WhenAvailable"/> on the <see cref="IModuleCatalog"/>. /// </summary> void Run(); /// <summary> /// Loads and initializes the module on the <see cref="IModuleCatalog"/> with the name <paramref name="moduleName"/>. /// </summary> /// <param name="moduleName">Name of the module requested for initialization.</param> void LoadModule(string moduleName); /// <summary> /// Raised repeatedly to provide progress as modules are downloaded. /// </summary> event EventHandler<ModuleDownloadProgressChangedEventArgs> ModuleDownloadProgressChanged; /// <summary> /// Raised when a module is loaded or fails to load. /// </summary> event EventHandler<LoadModuleCompletedEventArgs> LoadModuleCompleted; }
我們可以看到這個裡面第一個定義了一個IEnumerable
我們先來看看這個ModuleManager的具體實現類。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Prism.Properties;
namespace Prism.Modularity
{
/// <summary>
/// Component responsible for coordinating the modules' type loading and module initialization process.
/// </summary>
public partial class ModuleManager : IModuleManager, IDisposable
{
private readonly IModuleInitializer moduleInitializer;
private IEnumerable<IModuleTypeLoader> typeLoaders;
private HashSet<IModuleTypeLoader> subscribedToModuleTypeLoaders = new HashSet<IModuleTypeLoader>();
/// <summary>
/// Initializes an instance of the <see cref="ModuleManager"/> class.
/// </summary>
/// <param name="moduleInitializer">Service used for initialization of modules.</param>
/// <param name="moduleCatalog">Catalog that enumerates the modules to be loaded and initialized.</param>
public ModuleManager(IModuleInitializer moduleInitializer, IModuleCatalog moduleCatalog)
{
this.moduleInitializer = moduleInitializer ?? throw new ArgumentNullException(nameof(moduleInitializer));
ModuleCatalog = moduleCatalog ?? throw new ArgumentNullException(nameof(moduleCatalog));
}
/// <summary>
/// The module catalog specified in the constructor.
/// </summary>
protected IModuleCatalog ModuleCatalog { get; }
/// <summary>
/// Gets all the <see cref="IModuleInfo"/> classes that are in the <see cref="IModuleCatalog"/>.
/// </summary>
public IEnumerable<IModuleInfo> Modules => ModuleCatalog.Modules;
/// <summary>
/// Raised repeatedly to provide progress as modules are loaded in the background.
/// </summary>
public event EventHandler<ModuleDownloadProgressChangedEventArgs> ModuleDownloadProgressChanged;
private void RaiseModuleDownloadProgressChanged(ModuleDownloadProgressChangedEventArgs e)
{
ModuleDownloadProgressChanged?.Invoke(this, e);
}
/// <summary>
/// Raised when a module is loaded or fails to load.
/// </summary>
public event EventHandler<LoadModuleCompletedEventArgs> LoadModuleCompleted;
private void RaiseLoadModuleCompleted(IModuleInfo moduleInfo, Exception error)
{
this.RaiseLoadModuleCompleted(new LoadModuleCompletedEventArgs(moduleInfo, error));
}
private void RaiseLoadModuleCompleted(LoadModuleCompletedEventArgs e)
{
this.LoadModuleCompleted?.Invoke(this, e);
}
/// <summary>
/// Initializes the modules marked as <see cref="InitializationMode.WhenAvailable"/> on the <see cref="ModuleCatalog"/>.
/// </summary>
public void Run()
{
this.ModuleCatalog.Initialize();
this.LoadModulesWhenAvailable();
}
/// <summary>
/// Loads and initializes the module on the <see cref="IModuleCatalog"/> with the name <paramref name="moduleName"/>.
/// </summary>
/// <param name="moduleName">Name of the module requested for initialization.</param>
public void LoadModule(string moduleName)
{
var module = this.ModuleCatalog.Modules.Where(m => m.ModuleName == moduleName);
if (module == null || module.Count() != 1)
{
throw new ModuleNotFoundException(moduleName, string.Format(CultureInfo.CurrentCulture, Resources.ModuleNotFound, moduleName));
}
var modulesToLoad = this.ModuleCatalog.CompleteListWithDependencies(module);
this.LoadModuleTypes(modulesToLoad);
}
/// <summary>
/// Checks if the module needs to be retrieved before it's initialized.
/// </summary>
/// <param name="moduleInfo">Module that is being checked if needs retrieval.</param>
/// <returns></returns>
protected virtual bool ModuleNeedsRetrieval(IModuleInfo moduleInfo)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
if (moduleInfo.State == ModuleState.NotStarted)
{
// If we can instantiate the type, that means the module's assembly is already loaded into
// the AppDomain and we don't need to retrieve it.
bool isAvailable = Type.GetType(moduleInfo.ModuleType) != null;
if (isAvailable)
{
moduleInfo.State = ModuleState.ReadyForInitialization;
}
return !isAvailable;
}
return false;
}
private void LoadModulesWhenAvailable()
{
var whenAvailableModules = this.ModuleCatalog.Modules.Where(m => m.InitializationMode == InitializationMode.WhenAvailable);
var modulesToLoadTypes = this.ModuleCatalog.CompleteListWithDependencies(whenAvailableModules);
if (modulesToLoadTypes != null)
{
this.LoadModuleTypes(modulesToLoadTypes);
}
}
private void LoadModuleTypes(IEnumerable<IModuleInfo> moduleInfos)
{
if (moduleInfos == null)
{
return;
}
foreach (var moduleInfo in moduleInfos)
{
if (moduleInfo.State == ModuleState.NotStarted)
{
if (this.ModuleNeedsRetrieval(moduleInfo))
{
this.BeginRetrievingModule(moduleInfo);
}
else
{
moduleInfo.State = ModuleState.ReadyForInitialization;
}
}
}
this.LoadModulesThatAreReadyForLoad();
}
/// <summary>
/// Loads the modules that are not initialized and have their dependencies loaded.
/// </summary>
protected virtual void LoadModulesThatAreReadyForLoad()
{
bool keepLoading = true;
while (keepLoading)
{
keepLoading = false;
var availableModules = this.ModuleCatalog.Modules.Where(m => m.State == ModuleState.ReadyForInitialization);
foreach (var moduleInfo in availableModules)
{
if ((moduleInfo.State != ModuleState.Initialized) && (this.AreDependenciesLoaded(moduleInfo)))
{
moduleInfo.State = ModuleState.Initializing;
this.InitializeModule(moduleInfo);
keepLoading = true;
break;
}
}
}
}
private void BeginRetrievingModule(IModuleInfo moduleInfo)
{
var moduleInfoToLoadType = moduleInfo;
IModuleTypeLoader moduleTypeLoader = this.GetTypeLoaderForModule(moduleInfoToLoadType);
moduleInfoToLoadType.State = ModuleState.LoadingTypes;
// Delegate += works differently between SL and WPF.
// We only want to subscribe to each instance once.
if (!this.subscribedToModuleTypeLoaders.Contains(moduleTypeLoader))
{
moduleTypeLoader.ModuleDownloadProgressChanged += this.IModuleTypeLoader_ModuleDownloadProgressChanged;
moduleTypeLoader.LoadModuleCompleted += this.IModuleTypeLoader_LoadModuleCompleted;
this.subscribedToModuleTypeLoaders.Add(moduleTypeLoader);
}
moduleTypeLoader.LoadModuleType(moduleInfo);
}
private void IModuleTypeLoader_ModuleDownloadProgressChanged(object sender, ModuleDownloadProgressChangedEventArgs e)
{
this.RaiseModuleDownloadProgressChanged(e);
}
private void IModuleTypeLoader_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
{
if (e.Error == null)
{
if ((e.ModuleInfo.State != ModuleState.Initializing) && (e.ModuleInfo.State != ModuleState.Initialized))
{
e.ModuleInfo.State = ModuleState.ReadyForInitialization;
}
// This callback may call back on the UI thread, but we are not guaranteeing it.
// If you were to add a custom retriever that retrieved in the background, you
// would need to consider dispatching to the UI thread.
this.LoadModulesThatAreReadyForLoad();
}
else
{
this.RaiseLoadModuleCompleted(e);
// If the error is not handled then I log it and raise an exception.
if (!e.IsErrorHandled)
{
this.HandleModuleTypeLoadingError(e.ModuleInfo, e.Error);
}
}
}
/// <summary>
/// Handles any exception occurred in the module typeloading process,
/// and throws a <see cref="ModuleTypeLoadingException"/>.
/// This method can be overridden to provide a different behavior.
/// </summary>
/// <param name="moduleInfo">The module metadata where the error happened.</param>
/// <param name="exception">The exception thrown that is the cause of the current error.</param>
/// <exception cref="ModuleTypeLoadingException"></exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1")]
protected virtual void HandleModuleTypeLoadingError(IModuleInfo moduleInfo, Exception exception)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
if (!(exception is ModuleTypeLoadingException moduleTypeLoadingException))
{
moduleTypeLoadingException = new ModuleTypeLoadingException(moduleInfo.ModuleName, exception.Message, exception);
}
throw moduleTypeLoadingException;
}
private bool AreDependenciesLoaded(IModuleInfo moduleInfo)
{
var requiredModules = this.ModuleCatalog.GetDependentModules(moduleInfo);
if (requiredModules == null)
{
return true;
}
int notReadyRequiredModuleCount =
requiredModules.Count(requiredModule => requiredModule.State != ModuleState.Initialized);
return notReadyRequiredModuleCount == 0;
}
private IModuleTypeLoader GetTypeLoaderForModule(IModuleInfo moduleInfo)
{
foreach (IModuleTypeLoader typeLoader in this.ModuleTypeLoaders)
{
if (typeLoader.CanLoadModuleType(moduleInfo))
{
return typeLoader;
}
}
throw new ModuleTypeLoaderNotFoundException(moduleInfo.ModuleName, string.Format(CultureInfo.CurrentCulture, Resources.NoRetrieverCanRetrieveModule, moduleInfo.ModuleName), null);
}
private void InitializeModule(IModuleInfo moduleInfo)
{
if (moduleInfo.State == ModuleState.Initializing)
{
this.moduleInitializer.Initialize(moduleInfo);
moduleInfo.State = ModuleState.Initialized;
this.RaiseLoadModuleCompleted(moduleInfo, null);
}
}
#region Implementation of IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <remarks>Calls <see cref="Dispose(bool)"/></remarks>.
/// <filterpriority>2</filterpriority>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the associated <see cref="IModuleTypeLoader"/>s.
/// </summary>
/// <param name="disposing">When <see langword="true"/>, it is being called from the Dispose method.</param>
protected virtual void Dispose(bool disposing)
{
foreach (IModuleTypeLoader typeLoader in this.ModuleTypeLoaders)
{
if (typeLoader is IDisposable disposableTypeLoader)
{
disposableTypeLoader.Dispose();
}
}
}
#endregion
}
}
看這個大的方法之前我們來看看這個ModuleManager中的建構函式,建構函式包含兩個部分一個是:IModuleInitializer和IModuleCatalog這個裡面IModuleCatalog在上一篇中就此已進行過分析,這裡不在贅述我們先來看看這個IModuleInitializer介面,這個顧名思義就是對介面型別進行初始化操作,我們先來看看這個介面。
2 IModuleInitializer介面
我們先來看看這個介面的定義
/// <summary>
/// Declares a service which initializes the modules into the application.
/// </summary>
public interface IModuleInitializer
{
/// <summary>
/// Initializes the specified module.
/// </summary>
/// <param name="moduleInfo">The module to initialize</param>
void Initialize(IModuleInfo moduleInfo);
}
通過這個介面定義我們知道這裡面的Initialize方法通過傳入IModuleInfo引數實現對當前傳入IModuleInfo進行初始化,我們來看看這個具體的過程。
using System;
using System.Globalization;
using Prism.Ioc;
namespace Prism.Modularity
{
/// <summary>
/// Implements the <see cref="IModuleInitializer"/> interface. Handles loading of a module based on a type.
/// </summary>
public class ModuleInitializer : IModuleInitializer
{
private readonly IContainerExtension _containerExtension;
/// <summary>
/// Initializes a new instance of <see cref="ModuleInitializer"/>.
/// </summary>
/// <param name="containerExtension">The container that will be used to resolve the modules by specifying its type.</param>
public ModuleInitializer(IContainerExtension containerExtension)
{
this._containerExtension = containerExtension ?? throw new ArgumentNullException(nameof(containerExtension));
}
/// <summary>
/// Initializes the specified module.
/// </summary>
/// <param name="moduleInfo">The module to initialize</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Catches Exception to handle any exception thrown during the initialization process with the HandleModuleInitializationError method.")]
public void Initialize(IModuleInfo moduleInfo)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
IModule moduleInstance = null;
try
{
moduleInstance = this.CreateModule(moduleInfo);
if (moduleInstance != null)
{
moduleInstance.RegisterTypes(_containerExtension);
moduleInstance.OnInitialized(_containerExtension);
}
}
catch (Exception ex)
{
this.HandleModuleInitializationError(
moduleInfo,
moduleInstance?.GetType().Assembly.FullName,
ex);
}
}
/// <summary>
/// Handles any exception occurred in the module Initialization process,
/// This method can be overridden to provide a different behavior.
/// </summary>
/// <param name="moduleInfo">The module metadata where the error happened.</param>
/// <param name="assemblyName">The assembly name.</param>
/// <param name="exception">The exception thrown that is the cause of the current error.</param>
/// <exception cref="ModuleInitializeException"></exception>
public virtual void HandleModuleInitializationError(IModuleInfo moduleInfo, string assemblyName, Exception exception)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
if (exception == null)
throw new ArgumentNullException(nameof(exception));
Exception moduleException;
if (exception is ModuleInitializeException)
{
moduleException = exception;
}
else
{
if (!string.IsNullOrEmpty(assemblyName))
{
moduleException = new ModuleInitializeException(moduleInfo.ModuleName, assemblyName, exception.Message, exception);
}
else
{
moduleException = new ModuleInitializeException(moduleInfo.ModuleName, exception.Message, exception);
}
}
throw moduleException;
}
/// <summary>
/// Uses the container to resolve a new <see cref="IModule"/> by specifying its <see cref="Type"/>.
/// </summary>
/// <param name="moduleInfo">The module to create.</param>
/// <returns>A new instance of the module specified by <paramref name="moduleInfo"/>.</returns>
protected virtual IModule CreateModule(IModuleInfo moduleInfo)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
return this.CreateModule(moduleInfo.ModuleType);
}
/// <summary>
/// Uses the container to resolve a new <see cref="IModule"/> by specifying its <see cref="Type"/>.
/// </summary>
/// <param name="typeName">The type name to resolve. This type must implement <see cref="IModule"/>.</param>
/// <returns>A new instance of <paramref name="typeName"/>.</returns>
protected virtual IModule CreateModule(string typeName)
{
Type moduleType = Type.GetType(typeName);
if (moduleType == null)
{
throw new ModuleInitializeException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedToGetType, typeName));
}
return (IModule)_containerExtension.Resolve(moduleType);
}
}
}
這個裡面我們發現這個初始化IModuleInfo的核心是根據當前IModuleInfo構造出一個IModule物件,並且呼叫IModule中定義的RegisterTypes和OnInitialized方法,通常在我們的程式碼中我們是這樣使用IModule這個介面的。
using ModuleA.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
namespace ModuleA
{
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
}
到了這裡我們整個IModuleInitializer介面的作用就全部分析完畢。
3 Run方法
這個方法是整個ModuleManager中最核心的內容,上面的程式碼貼出來了具體的細節,這裡就這個部分做一個總結:
- this.ModuleCatalog.Initialize();
這個ModuleCatalog在上一篇中已經介紹過就是通過Initialize方法將不同的型別的Module逐一進行解析並新增到ModuleCatalog集合中,這個在上一篇中有細緻的說明。 - LoadModulesWhenAvailable()
上面一步將整個應用程式中的Module解析並加入到ModuleCatalog中,這一步就是將這些Modules中初始化模式為InitializationMode.WhenAvailable型別的Module進行逐一Load操作,如果當前Module的初始化型別是OnDemand,那麼後面就是需要通過手動呼叫IModuleManager.LoadModule的方法進行手動載入,到了這裡重點就是這個LoadModule到底進行了什麼操作?經過分析程式碼總結如下:
- 通過this.ModuleCatalog.CompleteListWithDependencies方法找到所有InitializationMode.WhenAvailable型別的Module並通過當前方法找到當前Module的所有依賴Module,並形成一個確定的先後載入順序的Module依賴鏈條。
- 確定前一步獲取的Module的狀態是否Ready,如果以及Ready的話執行內部LoadModulesThatAreReadyForLoad()方法
- 如果上面一步Module滿足State未初始化完畢並且其依賴的Module全部初始化完畢則執行IModuleInitializer中的Initialize方法完成對當前IModuleInfo的整個呼叫過程。
總結
上面的文章以IModuleManager介面為起點進行分析,通過建構函式找到其依賴項,分析其作用,後面通過重點分析Run方法來看ModuleManager是如何初始化整個Module,後面的文章中我們會就其它的一些細節和當前ModuleManager中Run方法呼叫的時機來分析當前ModuleManager的呼叫方是如何控制整個系統的Module運作,至此整個過程將會有一個更加清晰的認知。