我是从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容器,而是使用最基础的程序集扫描,目的也是为了减化框架.
参考的文章: