JavaWeb與Asp.net工作原理比較分析
一、概述
不管是什麼語言開發的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。