热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Bootstrap源码分析

我是从dudu的一篇文章里获知此框架,主要用于分离程序主体代码与程序启动代码.这与WebActivator框架很象,却可适用于各类程序而不仅仅是Web程序,还可以自定义执行顺序,执

      我是从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 extensiOns= new List();

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 Sequence {get; private set; }

public ISequenceSpecification First() 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() 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(); 
    else SequenceSpecification.Then();
    return SequenceSpecification;
}

      在其TheRest方法中,如果之前用的First方法,此会向任务列表的开头插入一个IStartupTask类型的任务,如果之前用的Then方法,会在最后插入一个IStartupTask类型的任务.在后面的分析中可以看到,这将影响所有的任务设置.

      与其相对应的还有一个TaskGroup类:

public List Tasks { get; set; }

public Thread Thread { get; set; }

      除了Tasks属性外,还有一个线程属性,这暗示了不同的序列将在不同的线程中执行.

      这里定义的两个相似的类是用于不同地方的.下面提到的StartupTasksOptions类使用SequenceSpecification类,用于任务配置.而StartupTasksExtension类则使用TaskGroup类,用于任务执行.任务执行模块从任务配置模块读取相关配置来完成最终执行.

      多个任务序列最终存在于StartupTasksOptions类中:

public List Groups { get; set; }

public StartupTasksOptions UsingThisExecutionOrder(Func buildSequence)
{
    buildSequence(Groups[Groups.Count-1]);
    return this;
}

public StartupTasksOptions WithGroup(Func 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 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 GetTasks()
{
    List tasks;
    if (Bootstrapper.ContainerExtension != null && Bootstrapper.Container != null)
        tasks = Bootstrapper.ContainerExtension.ResolveAll().OrderBy(t => t.GetType().Name).ToList();
    else
        tasks = registrationHelper.GetInstancesOfTypesImplementing().OrderBy(t => t.GetType().Name).ToList();
    return tasks;
}

      此方法将会查询Bootstrapper框架是否注册了IOC容器,如果是则从中获取所有已注册的实现了IStartupTask接口的类,否则则从当前应用程序域中获取.当然,这里会考虑最开始配置的包括的/排除的/只包括的程序集.

public IEnumerable GetAssemblies()
{
    return Bootstrapper.IncludingOnly.Assemblies.Any() 
            ? Bootstrapper.IncludingOnly.Assemblies
            :AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => !a.IsDynamic && IsNotExcluded(a));
}

public List GetInstancesOfTypesImplementing()
{
    var instances = new List();
    GetAssemblies().ToList()
        .ForEach(a => GetTypesImplementing(a).ToList()
            .ForEach(t => instances.Add((T)Activator.CreateInstance(t))));
    return instances;
}

      获取了所有待执行的任务后,下一步则是构建任务组了.

private void BuildTaskGroups(List 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() }));
}

      可以看到,这里细分了三步:构建补充信息,排序,分组,最后将构建好的任务组加入taskGroups属性中.后两步是Linq方法,很好理解,主要来看第一步的构建任务补充信息.

private IEnumerable AddExecutionParameters(List tasks)
{
    var tasksWithParameters = new List();
    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().Then().Then().Then().Then())

      还可以如下设置

      这时,设置里保存的是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方法 :

private IKernel container;     

protected override void InitializeContainer()
{
    container = new StandardKernel();
    Container = container;
}

public override T Resolve()
{
    CheckContainer();
    return container.Get();
}

      基本会用Ninject框架的人一看就懂,在这里也就不做更深入的分析了.

      其还定义了一个INinjectRegistration接口,使用者通过实现此接口完成自定义IOC类型绑定,这样Ninject就既为Bootstrapper服务也为应用程序自身服务.

void Register(IKernel container);

      另外在最新的2.0.3.0版中,使用原生的Ninject语法也能获取类型绑定了,原因是其中代码中加了两句代码

RegisterAll();

container.Load(container.GetAll());

 

      就个人选择而言,我选择使用此框架,并代替WebActivator框架,原因也如前所述,功能强大,分离耦合点.但却不准备全面引入其官网上各种各样的插件.我总觉得那些插件有过度设计的嫌疑.另外,我也暂时不为其引用专用的IOC容器,而是使用最基础的程序集扫描,目的也是为了减化框架.

 

      参考的文章:

      寂寞如此美丽:脱离Application_Start,让初始化代码更优美

      Bootstrapper官网


推荐阅读
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文详细介绍了使用C#实现Word模版打印的方案。包括添加COM引用、新建Word操作类、开启Word进程、加载模版文件等步骤。通过该方案可以实现C#对Word文档的打印功能。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
author-avatar
aadsasdass_998_268
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有