[读书笔记]组件设计-httpapplication详细

 这一章是全书基础和精神所在,其后的例子章节是为了验证这章的讲述和实践讲述的内容
其中第一节是讲述ASP.NET运行模式,这一节着眼于整个ASP.NET应用程序的运作模式,实际上,并不是在讲组件,但是却很重要,因为写组件的人必须清楚的知道ASP.NET应用程序是如何启动.如何处理请求,如何处理SESSION等这些细节问题的,但这一节对于一般读者来讲,可能十分晦涩.下面的讲解可能有助于你理解这一切.
一个ASP.NET的应用程序是开始于IIS的.
当你请求一个包含ASP.NET应用的网址时,IIS接受到请求(IIS是WEB服务守候进程),IIS收到请求后,会根据请求者请求的主机头或者IP或者端口号来找到对应的站点.
 
当找到站点后,如果你请求的资源是以ASPX为结尾的WEBFORM,时,IIS会将控制权交给一个ISAPI扩展.,名叫AspNet_ISAIP.DLL.这时,控制权由IIS交到ASPNET的ISAPI扩展上.,需要说明的是,ISAPI扩展的级别低于IIS,但高于用户站点,它独立于站点之外
 
ISAPI收到处理请求后,会启动一个ASP.NET工作进程.然后将请求者的请求信息转交给ASP.NET工作进程(名为ASPNET_WP.EXE).接下来,控制权由ASPNET_WP掌握.ASPNET_WP首先解出请求者的信息,如果请求者请求的ASP.NET应用程序(站点或虚拟目录,通俗一点)尚未拥有APPDOMAIN,ASPNET_WP就会建立一个APPDOMAIN,并且将被请求的ASP.NET应用所需的Assembly(就是那些DLL,例如System.Web.DLL等)载入到APPDOMAIN中
 
以上的步骤可以看到一个结论和规律:控制权是以流水式在各个请求处理者间传递,并且,前一个处理请求者必须负责传递后一个处理请求者所需的信息.而且要负责装载或初始化后一个处理者,这很像我们生活中的接力赛.
 
问题是,可能有许多人会问:干嘛要如此繁琐呢?直接由IIS把请求转交给ASPNET_WP如何呢?不是不可以,而是如此一来,这个处理过程的可扩展性就变低了.ASPNET ISAPI是IIS和ASPNET_WP之间的桥梁.虽然看起来它仅仅负责转交请求等工作.可是这样一来,就大大扩展延展性.
 
另外一个疑问是关于APPDOMAIN的,包括我,对于APPDOMAIN一开始的理解就曾陷入误区,APPDOMAIN这东东微软讲的也比较含糊,有人说跟进程一样,但我一开始理解成了IIS里的应用程序池,所以,走了不少弯路,实际上,APPDOMAIN既不是进程,也不是IIS里的应用程序池概念..NET下的所有应用程序都运行于APPDOMAIN之中(我自己的理解),每一个APPDOMAIN是一个 执行 的容器,每执行一个应用程序或者ASP.NET应用,.NET执行环境就会建立一个APPDOMAIN,然后把应用程序需要的一些DLL载入.APPDOMAIN的功能很像进程,但绝不是进程.你可以这样理解,APPDOMAIN就是ASP.NET应用程序的执行环境吧.
 
AspNet_WP不光负责建立APPDOMAIN(当然,如果已经存在的话,就直接使用这个DOMAIN了),另外,它在APPDOMAIN建立后,还会将请求转发至对应的APPDOMAIN中的ISAPIRuntime对象。(Isapiruntime对象是APPDOMAIN的一部分)。ISAPIRUNTIME专门负责解出请求的必要信息。它将信息和请求转交给HttpRuntime。在这里,需要说明的是IsapiRuntime是一个类,它的全称是System.Web.Hosting.ISAPIRuntime,而HttpRuntime也是一个类,它的全称是System.Web.HttpRuntime。因此,可以说,这两个对象是APPDOMAIN运行环境的一部分,在ASPNET_WP建立APPDOMAIN的同时,也会作为运行环境来建立这两个对象.
 
由于接二连三的讲述了几个对象,所以,当我第一遍看这本书特别是看到这部分时,觉得特别晕,因为第一对.NET FRAMEWORK的类库不甚了解,第二,对ASP.NET的运行原理初次接触.摸不着头脑,总想把这些对象名称与某个DLL或者某个实际上的文件来对应.其实不然,不管是ISAPIRuntime也好,还是HttpRuntime,它们在APPDOMAIN建立时,作为APPDOMAIN的一部分被实例化.所以它们代表的是内存中的一个类的实例,也就是对象.并且,这上面的一部分运作原理,似乎跟ASP.NET应用程序没有直接联系.似乎不入正题,很容易让初看者不知所云.实际上,可以说,由IIS到ISAPI是完成了请求的第一个部,也就是接纳客户请求.由ISAPI到APPDOMAIN,是第二部分,也就是初始化部分,旨在建立处理请求的大环境,为下面处理请求和运行ASP.NET应用程序作好准备.
 
接下来,当APPDOMAIN初始化完成后,接下来就需要建立会话了吧,因此,请求由HttpRuntime来接受,HttpRunTime主要的工作便是为每一个提出请求的客户建立一个HttpContext对象.这个东东又管理着HttpSession对象.每一个访问者有各自的HttpContext对象和HttpSession对象,这些对象,你可以在.NET FRAMEWORK库中找到对应的类名,像System.Web.HttpContext,System.Web.HttpSessionState等.
 
可以看出,请求的处理过程非常类似于.NET中事件模型的处理过程.若干个处理模块被串接到一个事件上.在ASP.NET运行原理里,也是,若干个模块依次轮流处理一个请求,像流水线操作一样.
 
另外,作为组件开发者,还要明确一个HttpRuntime,HttpContext,HttpSession这些对象的层次关系和调用创建关系.细节部分无需了解,只要知道谁创建了谁,谁被谁调用即可
 
HttpRuntime负责创建HttpContext和HttpSession,httpContext负责管理httpSession
 
 
到HttpRuntime创建完httpContext为止,实际上,你的应用程序仍然没有运行,或者说,请求者的请求实际上并未真正的被处理,前面的工作都是些准备性或者辅助性的工作.
 
HttpRuntime除了创建上面的对象外,还要创建HttpApplication.至于创建Application对象的过程,是比较复杂的.你可以把其作为一个分支流程涉略一下
 
 
 
接下来,HttpApplication调用ProcessRequest方法来处理用户请求,此方法会调用对应的HttpHandler来处理用户请求,HttpHandler根据用户请求的文件的扩展名处理请求,并把请求的结果,也就是HTML发送到客户浏览器
 
另外,过程的复杂性远远超出了上面的描述,基本上,黄先生这本书的第三章第一节用了十几页文本在描述ASP.NET运行过程及原理,以及处理请求时用的一些手法,但总体上的过程如上面的描述那样,只不过,我没有将建立各种对象时的细节剥离出来展示给大家.黄先生原著上的这节内容实际上非常详细.但为何大家看起来均言吃力呢?一方面是因为原理部分一向比较麻烦,另外一方面,是因为黄先生在讲述时,没有先向大家概要的描述过程和纲领,然后再描述细节,再是直接把细节和纲领融合在一起.这样,如果看的时候,没有去将这节的各个小标题和内容串联起来并先总结出纲领来的话.看完后,就会头晕.实际上,整个讲述就是在描述ASP.NET处理请求的过程.如果隐藏所有技术性的细节,而只讲流程的话,大家可能很快理解.然后再将流程中的每一部分的技术细节展现出来,我想,容易理解的多.这好比讲故事,先将故事梗概说一下比较好吧.
 
当然,我不是说黄先生写的不好,实际上,这一节写的很透彻,看懂了,就很受用.流程是很重要的,它的重要性在于你知道了在何时发生何事,就可以在指定的时间点做一些处理.这一点,在黄先生本书以后的章节中讲述ASP.NET PAGE对象执行流程中更显重要.
 
下面的图对整个ASP.NET应用运行过程中的各个对象的职能以及流程做了图解.当然,图解抛弃了技术性的细节,例如,像HttpApplication如何建立等 
 
 
 

上一篇随笔<深入剖析ASP.NET组件设计>一书中第三章关于ASP.NET运行原理的补白总览了大体的结构及流程,看完后,相信,可以对整体的流程有所了解.但是,许多细节的问题,例如像HttpRuntime如何建立HttpApplication对象等问题,仍然没有解说清楚
当然,了解这些细节并不是必需的,就算你不知道HttpApplication对象是如何被创建的,你仍能够创建出好的组件.但是,如果你仔佃研究这些细节,相信你会吸收不少技术养分.例如,通过本文讲述的HttpApplication对象的创建过程,你就可以深切的体会到ASP.NET设计的精妙之处,并且,你可以学到Factory设计模式的运用,这也是黄先生这本书的另一个特点,就是,他不光讲述了ASP.NET的一些东西,同时教你一些编程的方法和技巧,对于一些学习设计模式,而又觉得难以理解设计模式的初学者来说,通过阅读黄先生的这本书,能够让你看到不少设计模式实地运用的讲解.

实际上HttpApplication并不是HttpRuntime所创建的.HttpRuntime只是向HttpApplicationFactory提出请求,要求返回一个HttpApplication对象.HttpApplicationFactory在接收到请求后,会先检查是否有已经存在并空闲(可以这样讲吗?比较形象一点)的HttpApplication对象,如果有,则从池中取出一个HttpApplication对象返回给HttpRuntime,如果没有的话,则要创建一个HttpApplication对象给HttpRunTime(英文叫Pooling,好比你养鱼一样,每个鱼是一个HttpApplication,而你就是HttpApplicationFactory,别人问你要鱼,你就会检查池中有没有现成的,并且是合适的鱼,有的话,捞一个给别人,没有的话,创造一条鱼,放入池中,再给别人,听起来不错,你好像威力无比哟)

实际上,我们再将HttpApplication的创建过程放大来看的话,还有不少细节.上面讲述的是HttpApplication是如何被请求以及如何被返回给HttpRunTime的,概括的来说,就是HttpRunTime不直接建立HttpApplication,而是把创建的权利交给HttpApplicationFactory,这里实际上运用的正是Factory模式,而且不是一般的Factory模式,而是带有Pool能力的Factory模式.HttpFactory对象负责建立并缓存HttpApplication对象同时返回合适的对象给HttpRuntime.这里就有一个问题了,也就是说,同一时刻,HttpApplicationFactory的"养鱼池"里可能有很多个HttpApplication对象.那最多他能够同时缓存多少个HttpApplication对象呢?默认情况下,HttpApplication对象的最大缓存数目为100,并且HttpApplicationFactory会循环释放超过此数量的HttpApplication对象(那是不是意味着超出100个人同时访问的系统,无论设计的再好,也会因此而遭遇到性能瓶颈呢?)

HttpApplicationFactory创建HttpRuntime的过程是一个Parse与Compile的过程.原书中黄先生说,HttpApplicationFactory会运用Parser对象来解析Global.asax,同时,加载Global.dll文件,同时,创建一个继承自此类的源代码,最后运用Compiler对象来编译源代码,再创建一个HttpApplication对象.这一点,也可以从黄先生书中配图可以看到.但是,估计许多人看到这里已经晕了.你可以这样理解这个过程:

首先,黄先生所言的Parser与Compiler对象其实是指Codeparser与CodeCompiler类的两个实例,它们是.net类库的一部分,Codeparser对象的作用是将一段文本转换为一段C#或VB源代码
(
看不懂吗?有没有想过,你在ASPX页面中用<ASP:Label runat=server></ASP:Label>中定义的ASP.NET控件在运行时究竟变成什么东东呢?Parser对象可以负责将<asp:Label>以及类似的控件以及<script runat="server">中的代码解析成对应的C#源码片断,也就是 Label label2=new Label();的形式,它返回一个CodeCompileUnit对象.
如果你仍然不理解ASP.NET的这种Parser行为的话,你就不了解ASP.NET控件的运作形式.你必须深刻的记住一点,那就是,处理对应的ASP.NET页面请求的不是ASP.NET也不是IIS,更不是ASP.NET页面本身.而是ASP.NET运用PARSER对象将控件标记转换成C#源码并且派生于PAGE类,被编译并实例外的一个对象.这一点非常重要,跟ASP不同,我们知道ASP的代码是被加载到内存并且被ASP运行时解析然后返回HTML的,但是,ASP.NET不是,ASP.NET处理页面请求的是一个类的实例,它是一个可以输出HTML的对象.同样的,在HttpApplication对象的创建过程中,也是运用了Parser,因为HttpApplication实际上要依赖于Global.ASAX文件,而这个文件,我们知道,如果不用CodeBehind来写的话,它就是一个<Script Runat="Server">,换句话来说,它也是一段标记,这段标记必须被转换成C#源代码,然后编译成一个类,再产生这个类的实例,这个类就是HttpApplication.实际上,像CodeParser,CodeCompiler以及Reflection等构成的CodeDom技术是.NET核心部分之一,.NET的运行非常依赖于这些部分
)

至于parser为什么要解析Global.ASAX同地又要加载Assembly,这一点上,许多人会想不通.因为如果使用CODEBehind技术的话,所有代码已经包含在Assembly中了呀,为什么还要解析Global.ASAX呢?如果你这样想,你就错了,首先,也可能Global.ASAX中定义了一部分函数,而Assembly中定义了另一部分,其次,没有人说Gloabal.ASAX中只允许包含代码呀,也有人喜欢在其中利用OBJECT标记来定义APPLICATION范围或者Session范围的对象呀,因此,parser有必要解析Global.ASAX文件,将其转换为C#代码片断,然后,加载Assembly,利用Reflection技术创建一个继承自类Global(默认情况下的类名)的新类的源代码,然后将两部分的源代码合并(Ghost Application Class Source),运用Compiler创建出一个新类(Ghost Application Class Assembly),并且生成新类的一个实例来返回给HttpRuntime.

原书中,黄先生说Parser会加载Global.dll文件,我想,这可能是笔误,很多人不明白Global.dll究竟是什么.写过ASP.NET的人更知道,根本没有这个文件.我想,Global.dll就是包含global.asax文件的工程编译产生的Assembly,由于此Assembly包含了CodeBehind方式下的Global类的信息.因此,Parser才需要加载它

至此,HttpApplication对象创建完毕了.另外,就AppDomain,HttpRuntime,HttpContext,HttpSession,HttpApplication,HttpModule这些对象的数量以及与用户数量的对应关系做以下描述
每一个ASP.NET只有一个AppDomain,每一个AppDomain对应一个httpRunTime,它们都与用户数量无关
每一个用户对应一个HttpApplication,一个HttpSession,一个HttpContext和一组HttpModule

如果对于HttpApplication对象生成过程中的Parser与Compiler不清楚的话(这部分很重要,包括AspX页面的处理方法也是类似的),下面的图或许有助理解:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值