一、概述
不管是什么语言开发的web应用程序,都是在解决一个问题,那就是用户输入url怎么把对应的页面响应出来,如何通过url映射到响应的类,由于自己做asp.net的时间也不短了,还算是对asp.net的整个流程还算是了解,所以在自学JavaWeb的时候也很好奇JavaWeb中是如何处理的。
二、asp.net的工作原理
下面的对asp.net的工作流程的介绍(红字)以及我个人的理解。这里也给学asp.net的推荐一本书<<asp.net本质论>>,这本书对http请求流程讲的比较详细,也是一本挺不错的书。
以IIS 6.0为例,在工作进程w3wp.exe中,利用Aspnet_ispai.dll加载.NET运行时(如果.NET运行时尚未加载)。IIS 6引入了应用程序池的概念,一个工作进程对应着一个应用程序池。一个应用程序池可以承载一个或者多个Web应用,每个Web应用映射到一个IIS虚拟目录。与IIS 5.x一样,每一个Web应用运行在各自的应用程序域中。
下图是我本地电脑的IIS应用程序池列表。
上图红线部分也显示的很清楚,应用程序池与工作进程相关联,包含一个或多个应用程序,并提供不同应用之间的隔离。在我本地Test应用池中就有2个应用。具体创建可以参考Nginx负载均衡篇http://www.cnblogs.com/5ishare/p/6129775.html.
如果HTTP.SYS接收到的HTTP请求是对该Web应用的第一次访问,当成功加载了运行时后,会通过AppDomainFactory为该Web应用创建一个应用程序域(AppDomain)。随后,一个特殊的运行时IsapiRuntime被加载。IsapiRuntime定义在程序集System.Web中,对应的命名空间为System.Web.Hosting。IsapiRuntime会接管该HTTP请求。
这里的应用程序域提供了四个重要的机制。
1.隔离 不同应用程序域直接不能直接访问
2.卸载 被加载的应用程序集只能以应用程序域为单位来卸载
3.安全 以应用程序域为边界的安全机制
4.配置 以应用程序域为边界的配置
IsapiRuntime会首先创建一个IsapiWorkerRequest对象,用于封装当前的HTTP请求,并将该IsapiWorkerRequest对象传递给ASP.NET运行时:HttpRuntime,从此时起,HTTP请求正式进入了ASP.NET管道。根据IsapiWorkerRequest对象,HttpRuntime会创建用于表示当前HTTP请求的上下文(Context)对象:HttpContext。
IsapiWorkerRequest是比较底层的对象,HttpRuntime接到请求之后会将其分析拆解,创建HttpRequest、HttpResponse对象,一次Http请求需要好几个对象,还有HttpServerUtility类型的对象处理网站虚拟路径和服务器文件系统之间的映射关系,为了管理这些对象,定义了HttpContext来统一处理参数的表示问题。
随着HttpContext被成功创建,HttpRuntime会利用HttpApplicationFactory创建新的或者获取现有的HttpApplication对象。实际上,ASP.NET维护着一个HttpApplication对象池,HttpApplicationFactory从池中选取可用的HttpApplication用户处理HTTP请求,处理完毕后将其释放到对象池中。HttpApplicationFactory负责处理当前的HTTP请求。HttpRuntime创建HttpContext成功之后,会创建HttpApplication对象,需要注意的是HttpApplication对象创建的来源,一是通过HttpApplicationFactory创建一种是HttpApplication对象池获取,它和Java Web中可不太一样。JavaWeb中的Servlet是单例多线程,通过Servlet对象只创建一个,通过线程池来响应请求。
在HttpApplication初始化过程中,会根据配置文件加载并初始化相应的HttpModule对象。对于HttpApplication来说,在它处理HTTP请求的不同的阶段会触发不同的事件(Event),而HttpModule的意义在于通过注册HttpApplication的相应的事件,将所需的操作注入整个HTTP请求的处理流程。ASP.NET的很多功能,比如身份验证、授权、缓存等,都是通过相应的HttpModule实现的。
当请求转入ASP.NET管道后,最终负责处理该请求的是与请求资源类型相匹配的HttpHandler对象,但是在Handler正式工作之前,ASP.NET会先加载并初始化所有配置的HttpModule对象。HttpModule在初始化的过程中,会将一些功能注册到HttpApplication相应的事件中,那么在HttpApplication整个请求处理生命周期中的某个阶段,相应的事件会被触发,通过HttpModule注册的事件处理程序也得以执行。所有的HttpModule都实现了IHttpModule接口。
这里的HttpModule起到了过滤的功能,这就和JavaWeb的Filter有点类型,利用它们可以做一些例如权限等一些处理。这种设计的好处也很明显,扩展性很强,用户可以自己选择使用。
而最终完成对HTTP请求的处理实现在另一个重要的对象中:HttpHandler。对于不同的资源类型,具有不同的HttpHandler。比如.aspx页对应的HttpHandler为System.Web.UI.Page,WCF的.svc文件对应的HttpHandler为System.ServiceModel.Activation.HttpHandler。
HttpHander有点类似JavaWeb的Servlet,最终处理还是需要它们,而且都是web的基础,JavaWeb中jsp最终还是会以Servlet对象来运行,asp.net中也是一样。
三、JavaWeb的工作流程
上面asp.net主要是关于池子的问题,下面JavaWeb主要是关于容器的问题。
Tomcat 的容器分为四个等级,真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程。这里有点类似asp.net的应用程序域。
一个 Web 应用对应一个 Context 容器,也就是 Servlet 运行时的 Servlet 容器,添加一个 Web 应用时将会创建一个 StandardContext 容器,并且给这个 Context 容器设置必要的参数。最重要的一个配置是 ContextConfig,这个类将会负责整个 Web 应用配置的解析工作,最后将这个 Context 容器加到父容器 Host 中。
1.其实我们在用eclipse将工程添加到tomcat服务器的过程就是在执行上面的工作。注意下面红线的部分,If server is started,publish changes immediately.服务器启动时,对Context 容器做的改变会立刻发布。能做到立刻发布,主要是靠观察者设计模式的Listener。ContextConfig 继承了 LifecycleListener 接口,所以对ContextConfig的修改也会立刻生效。
2.ContextConfig对象
关于ContextConfig对象,我们可以逆向的思考,我们创建一个Servlet类的同时需要在web.xml中进行配置servlet-name、servlet-calss、servlet-mapping、init-param,那Context容器又是怎么知道它们的对应关系和初始化参数呢?于是ContextConfig出现了。找到了这么多的初始化参数和映射关系,通过这些参数和映射将Servlet、Filter、Listener放入容器中,
3.Servlet实例化、初始化
上面虽然通过ContextConfig将Servlet各个初始化属性添加到Context容器中,但并没有将Servlet进行实例化初始化,所以还是不能使用。如果 Servlet 的 load-on-startup 配置项大于 0,那么在 Context 容器启动的时候就会被实例化。而实例化就要找到对应的类,对于jsp文件实质也是Servlet,servletClass 就是 conf/web.xml 中定义的 org.apache.jasper.servlet.JspServlet。而Servlet对象只会创建、初始化一次,如果有多个请求同时访问,会从线程池中获取一个线程还是用这个Servlet对象处理用户请求,算是单例多线程,这就会带来一个问题,线程安全问题,访问特别是修改Servlet类中的全局变量时会导致数据错误,所以尽量不使用在Servlet类中声明全局变量。实在不行就需要给线程加锁。
4.Servlet的生命周期
生命周期这个词在开发中很常见,Servlet也不例外。
Servlet 生命周期:Servlet 加载--->实例化(init())--->服务(Service())--->销毁(destroy())。
加载和实例化已在tomcat启动的时候完成,当客户端发起请求,Servlet是调用service()方法对请求进行响应的,service()方法中对请求的方式进行了匹配,选择调用doGet,doPost等这些方法,然后再进入对应的方法中调用逻辑层的方法,实现对客户的响应.
destroy(): 仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成.
5.Servlet的继承关系
我们创建的每一次Servlet都是继承abstract HttpServlet,而HttpServlet 又继承了javax.servlet.GenericServlet。GenericServlet是一个通用的,不特定于任何协议的Servlet,它实现了Servlet接口.
Servlet接口和GenericServlet是不特定于任何协议的,而HttpServlet是特定于HTTP协议的类,所以HttpServlet中实现了service()方法,并将请求ServletRequest、ServletResponse 强转为HttpRequest 和 HttpResponse。