一、概述
不管是什么語言開發的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。