Bootstrap源碼分析
我是從dudu的一篇文章里獲知此框架,主要用于分離程序主體代碼與程序啟動代碼.這與WebActivator框架很象,卻可適用于各類程序而不僅僅是Web程序,還可以自定義執行順序,執行條件等.是一款不可多得的好框架.
本文書寫時Bootstrap的版本號為2.0.3.2.
首先來看Bootstrap框架的主體類:Bootstrapper
private static readonly BootstrapperExtensions Extensions; public static object Container {get { return ContainerExtension == null ? null : ContainerExtension.Container; }} public static BootstrapperExtensions With { get { return Extensions; } } public static IBootstrapperContainerExtension ContainerExtension { get; set; } public static IExcludedAssemblies Excluding { get; private set; } public static IIncludedAssemblies Including { get; private set; } public static IIncludedOnlyAssemblies IncludingOnly { get; private set; } static Bootstrapper() { Extensions = new BootstrapperExtensions(); InitializeExcludedAndIncludedAssemblies(); } public static void Start() { foreach (var extension in Extensions.GetExtensions()) extension.Run(); } public static void Reset() { InitializeExcludedAndIncludedAssemblies(); foreach (var extension in Extensions.GetExtensions().Reverse()) extension.Reset(); }
可以看到,Bootstrap框架采取了插件式構造方式,其Start方法本質就是循環所有已注冊插件并分別啟動之.其Reset方法就是循環所有已注冊插件并分別重置之.Container屬性與ContainerExtension屬性為依賴映射容器,一個為Object類型,另一個為IBootstrapperContainerExtension類型,兩種訪問方式,同一個對象.用于存儲和解析如IStartupTask接口與實現類的映射關系.IExcludedAssemblies屬性,IIncludedAssemblies屬性,IIncludedOnlyAssemblies屬性則分別存儲了獲取實現類時需包括的/排除的/只包括的程序集.最后,程序通過With屬性獲取所有已注冊的插件集合.
下面再來看一看BootstrapperExtensions類
private readonly List<IBootstrapperExtension> extensions = new List<IBootstrapperExtension>(); public BootstrapperExtensions And { get { return this; } } public BootstrapperExtensions Extension(IBootstrapperExtension extension) { extensions.Add(extension); if(extension is IBootstrapperContainerExtension) Bootstrapper.ContainerExtension = extension as IBootstrapperContainerExtension; return this; } public void Start() { Bootstrapper.Start(); }
可以看到,此類通過List集合來實際存儲所有已注冊的插件類型.通過Extension方法注冊插件,并在方法內檢查是不是實現了IBootstrapperContainerExtension接口.從這里可以看出:1.其將IOC功能做成了插件形式;2.Bootstrap框架并未自行實現IOC功能,而是通過外部插件進行獲取.這一點還可以從后面的分析中得到證明;3.通過返回自身來完成對象的鏈式調用,即Fluent API,這種代碼風格在框架中隨處可見.Start方法則是調用主體類的Start方法.And屬性也可完成對象的鏈式調用.
再來提一下BootstrapperExtensionOptions類
public BootstrapperExtensions And {get { return Bootstrapper.With; }} public void Start() { Bootstrapper.Start();}
此對象很簡單,And屬性與Start方法均與上面所述一致.
下面再來看一看實際使用中用的最多的StartupTasks任務體系.它是以自帶的插件的形式存于在Bootstrapper框架中
使用者在實際使用中只需實現IStartupTask接口,此接口定義如下
void Run(); void Reset();
可以看到這又是一個類似于插件模式的定義.通過Run執行任務,通過Reset重置任務.
為了完成更多高級功能,在實際處理中此接口將以屬性的方式存于TaskExecutionParameters類中:
public IStartupTask Task { get; set; } public Type TaskType { get; set; } public int Position { get; set; } public int Delay { get; set; } public int Group { get; set; }
可以看到,這里補充了對某一任務的更多說明:任務實際類型,順序,延遲時間,分組信息.
多個任務最終以任務列表的形式存在于任務序列類:SequenceSpecification類中:
public List<TaskExecutionParameters> Sequence {get; private set; } public ISequenceSpecification First<T>() where T:IStartupTask { lastTask = new TaskExecutionParameters {TaskType = typeof (T)}; Sequence.Insert(0, lastTask); return this; } public ISequenceSpecial First() { return new SequenceSpecial(this, true); } public ISequenceSpecification Then<T>() where T:IStartupTask { lastTask = new TaskExecutionParameters { TaskType = typeof(T) }; Sequence.Add(lastTask); return this; } public ISequenceSpecial Then() { return new SequenceSpecial(this, false); }
可以看到,此類通過First,Then的泛型方法新增任務,而First,Then的非泛型方法則要小心使用,它返回了一個SequenceSpecial對象,如下
public ISequenceSpecification TheRest() { if (FirstInSequence) SequenceSpecification.First<IStartupTask>(); else SequenceSpecification.Then<IStartupTask>(); return SequenceSpecification; }
在其TheRest方法中,如果之前用的First方法,此會向任務列表的開頭插入一個IStartupTask類型的任務,如果之前用的Then方法,會在最后插入一個IStartupTask類型的任務.在后面的分析中可以看到,這將影響所有的任務設置.
與其相對應的還有一個TaskGroup類:
public List<TaskExecutionParameters> Tasks { get; set; } public Thread Thread { get; set; }
除了Tasks屬性外,還有一個線程屬性,這暗示了不同的序列將在不同的線程中執行.
這里定義的兩個相似的類是用于不同地方的.下面提到的StartupTasksOptions類使用SequenceSpecification類,用于任務配置.而StartupTasksExtension類則使用TaskGroup類,用于任務執行.任務執行模塊從任務配置模塊讀取相關配置來完成最終執行.
多個任務序列最終存在于StartupTasksOptions類中:
public List<ISequenceSpecification> Groups { get; set; } public StartupTasksOptions UsingThisExecutionOrder(Func<ISequenceSpecification, ISequenceSpecification> buildSequence) { buildSequence(Groups[Groups.Count-1]); return this; } public StartupTasksOptions WithGroup(Func<ISequenceSpecification, ISequenceSpecification> buildSequence) { if(Groups[0].Sequence.Count>0 ) Groups.Add(new SequenceSpecification()); return UsingThisExecutionOrder(buildSequence); }
可以看到,多個任務序列存儲于List集合中,通過UsingThisExecutionOrder方法設置單個序列或通過WithGroup方法新增序列.
現在我們來看一看StartupTasks任務體系的核心類:StartupTasksExtension,這個類比較大,我將分段分析
public StartupTasksOptions Options { get; private set; } private readonly List<TaskGroup> taskGroups; public void Run() { BuildTaskGroups(GetTasks()); if (taskGroups.Count == 1) RunGroup(taskGroups[0]); else { taskGroups.ForEach(g => g.Thread = new Thread(() => RunGroup(g))); taskGroups.ForEach(g => g.Thread.Start()); taskGroups.ForEach(g => g.Thread.Join()); } }
它有一個Options屬性,還有一個任務組集合taskGroups字段.最終此類將從Options屬性讀取相關設置,將待執行任務分組后執行.taskGroups字段將與Options屬性的Groups屬性對應.在Run方法中可以看到任務是分組多線程執行的.
private List<IStartupTask> GetTasks() { List<IStartupTask> tasks; if (Bootstrapper.ContainerExtension != null && Bootstrapper.Container != null) tasks = Bootstrapper.ContainerExtension.ResolveAll<IStartupTask>().OrderBy(t => t.GetType().Name).ToList(); else tasks = registrationHelper.GetInstancesOfTypesImplementing<IStartupTask>().OrderBy(t => t.GetType().Name).ToList(); return tasks; }
此方法將會查詢Bootstrapper框架是否注冊了IOC容器,如果是則從中獲取所有已注冊的實現了IStartupTask接口的類,否則則從當前應用程序域中獲取.當然,這里會考慮最開始配置的包括的/排除的/只包括的程序集.
public IEnumerable<Assembly> GetAssemblies() { return Bootstrapper.IncludingOnly.Assemblies.Any() ? Bootstrapper.IncludingOnly.Assemblies :AppDomain.CurrentDomain.GetAssemblies() .Where(a => !a.IsDynamic && IsNotExcluded(a)); } public List<T> GetInstancesOfTypesImplementing<T>() { var instances = new List<T>(); GetAssemblies().ToList() .ForEach(a => GetTypesImplementing<T>(a).ToList() .ForEach(t => instances.Add((T)Activator.CreateInstance(t)))); return instances; }
獲取了所有待執行的任務后,下一步則是構建任務組了.
private void BuildTaskGroups(List<IStartupTask> tasks) { taskGroups.Clear(); AddExecutionParameters(tasks) .OrderBy(t => t.Position) .GroupBy(t => t.Group) .ToList().ForEach(g => taskGroups.Add(new TaskGroup { Tasks = g.ToList(), ExecutionLog = new List<ExecutionLogEntry>() })); }
可以看到,這里細分了三步:構建補充信息,排序,分組,最后將構建好的任務組加入taskGroups屬性中.后兩步是Linq方法,很好理解,主要來看第一步的構建任務補充信息.
private IEnumerable<TaskExecutionParameters> AddExecutionParameters(List<IStartupTask> tasks) { var tasksWithParameters = new List<TaskExecutionParameters>(); tasks.ForEach(t => tasksWithParameters.Add(new TaskExecutionParameters { Task = t, Position = GetSequencePosition(t, tasks), Delay = GetDelay(t), Group = GetGroup(t) })); return AdjustDelayForTheRest(tasksWithParameters); }
這里的重點是獲取任務的排序,延遲和分組.三者邏輯非常類似,就以排序值為例吧
private int GetSequencePosition(IStartupTask task, ICollection tasks) { return GetFluentlyDeclaredPosition(task, tasks) ?? GetAttributePosition(task) ?? GetRestPosition(tasks) ?? DefaultPosition; }
這里再次將邏輯細分為:從定義處獲取,從Attribute處獲取,從Rest處獲取.首先看第一個
private int? GetFluentlyDeclaredPosition(IStartupTask task, ICollection tasks) { var group = Options.Groups.FirstOrDefault(g => g.Sequence.Any(t => t.TaskType == task.GetType())); if (group == null) return null; var sequence = group.Sequence.Select(s => s.TaskType).ToList(); if (!sequence.Contains(typeof(IStartupTask))) return sequence.IndexOf(task.GetType()) + 1; if (sequence.IndexOf(typeof(IStartupTask)) > sequence.IndexOf(task.GetType())) return sequence.IndexOf(task.GetType()) + 1; return tasks.Count + sequence.IndexOf(task.GetType()) - sequence.IndexOf(typeof(IStartupTask)); }
第一,二行,從Options屬性中獲取包含此類型的任務序列,如果包含不到則返回null
第三行,將任務序列(TaskExecutionParameters類型集合)轉換成簡單的Type集合,值一一對應
第四行,如果序列中未定義了IStartupTask類型,則返回本類型在集合中的位置
第五行,如果序列中定義了IStartupTask類型,且IStartupTask類型的位置大于本類型的位置,即IStartupTask類型排序在后,則仍舊返回本類型在集合中的位置
第六行,如果序列中定義了IStartupTask類型,且IStartupTask類型的位置小于本類型的位置,即IStartupTask類型排序在前,則會將本類型的排序值按原序移出集合.這意思不好直說,舉例子來說明吧
IStartupTask類型簡稱I,有A,B,C三個子類,如果在定義中的順序是C,I,B,A,他們的下標分別為0,1,2,3,那么經過這個方法后,那么的排序值就是0,1,5,6,也就是說,B與A的順序未變,但排序值將大于元素總個數.為何這么做?下面再分解!
如果從定義處取不到值,即返回null,則程序將從Attribute處取值
private static int? GetAttributePosition(IStartupTask task) { var attribute = task.GetType().GetCustomAttributes(false).FirstOrDefault(a => a is TaskAttribute) as TaskAttribute; if (attribute == null) return null; if (attribute.PositionInSequence == int.MaxValue) return null; return attribute.PositionInSequence; }
可以看到,這里使用了Bootstrapper框架自帶的自定義Attribute
[AttributeUsage(AttributeTargets.Class)] public class TaskAttribute: Attribute { public int PositionInSequence { get; set; } public int DelayStartBy { get; set; } public int Group { get; set; } public TaskAttribute() { PositionInSequence = int.MaxValue; DelayStartBy = 0; Group = 0; } }
很簡單,無需多述
如果從Attribute處也無法獲取,則就從Rest處獲取
private int? GetRestPosition(ICollection tasks) { var group = Options.Groups.FirstOrDefault(g => g.Sequence.Any(t => t.TaskType == typeof(IStartupTask))); if (group == null) return null; return tasks.Count; }
這里,會直接查看序列中有無定義了IStartupTask接口.如果有,則將本排序值設為集合個數.
將這個邏輯結合上面的邏輯一起思考就會明白開發者的設計意圖了.首先,序列中的IStartupTask類型表示所有實現了此接口但未被設置的類型,在序列中插入IStartupTask類型的方法除了手動插入外,還有上面所提到的TheRest方法,它想表示的語意是:剩下的.對于I及其實現類:A,B,C,D,E,如果想按E,B,C,D,A的順序執行,除了手動的一個一個賦值之外:
.UsingThisExecutionOrder(o => o.First<E>().Then<B>().Then<C>().Then<D>().Then<A>())
還可以如下設置
.UsingThisExecutionOrder(o => o.First<E>().Then().TheRest().Then<A>())
這時,設置里保存的是E,I,A,再結合上面的方法,最終的順序值為:A:6, B:5, C:5, D:5, E:0
總結一下,對于序列中定義了IStartupTask類型的,所有排在I之前的順序值不變,所有排在I之后的順序值按順序移到集合個數值之后,最后將所在程序中定義卻未在序列中配置的插入到集合個數值處.
任務延遲值與分組信息也是類似的處理.所在程序中定義卻未在序列中配置的將使用相同的延遲值(-1),并屬于相同的任務分組.
這里還有一個細節需要注意.在GetRestPosition方法第一行,只要有任意一分組中定義了IStartupTask類型,就會將所有分組里在程序中定義卻未在序列中配置的順序值改為各自集合的個數值.如第一組X,Y,定義了Y,第二組A,B,定義了B,A,則第一組的順序值為int.MaxValue,0,但如果將第二組改為B,I,A,則第一組的順序值為2,0.雖然順序沒有變,但值卻變了.
此任務插件通過BootstrapperStartupTasksHelper類向插件集合類BootstrapperExtensions注冊了擴展方法以方便調用.
public static StartupTasksOptions StartupTasks(this BootstrapperExtensions extensions) { var extension = new StartupTasksExtension(new RegistrationHelper()); extensions.Extension(extension); return extension.Options; }
如前所述,Bootstrapper框架將IOC定義為另一個插件并定義了若干接口,卻沒有完全實現它.理由是市面上已經有很多類似的框架了,寫個配置器直接使用即可,沒有必要重復造輪子.插件與接口定義部分在其源代碼Extensions的Containers文件夾下.就不做更具體的分析了.而IOC配置器,官網上也提供了很多與流行IOC框架的配置器的默認實現.大部分也都起一個中轉調用的過程.
例如Bootstrapper.Ninject框架,BootstrapperNinjectHelper類向插件集合類BootstrapperExtensions注冊了擴展方法以方便調用.而核心類NinjectExtension則是一個徹頭徹尾的配置器,如用的最多的Resolve<T>方法 :
private IKernel container; protected override void InitializeContainer() { container = new StandardKernel(); Container = container; } public override T Resolve<T>() { CheckContainer(); return container.Get<T>(); }
基本會用Ninject框架的人一看就懂,在這里也就不做更深入的分析了.
其還定義了一個INinjectRegistration接口,使用者通過實現此接口完成自定義IOC類型綁定,這樣Ninject就既為Bootstrapper服務也為應用程序自身服務.
void Register(IKernel container);
另外在最新的2.0.3.0版中,使用原生的Ninject語法也能獲取類型綁定了,原因是其中代碼中加了兩句代碼
RegisterAll<INinjectModule>();
container.Load(container.GetAll<INinjectModule>());
就個人選擇而言,我選擇使用此框架,并代替WebActivator框架,原因也如前所述,功能強大,分離耦合點.但卻不準備全面引入其官網上各種各樣的插件.我總覺得那些插件有過度設計的嫌疑.另外,我也暫時不為其引用專用的IOC容器,而是使用最基礎的程序集掃描,目的也是為了減化框架.
參考的文章:

浙公網安備 33010602011771號