Java Web基礎 --- Servlet 綜述(理論篇)

摘要:html

  Web 技術成爲當今主流的互聯網 Web 應用技術之一,而 Servlet 是 Java Web 技術的核心基礎。本文首先從請求/響應架構應用的大背景談起 Servlet 的由來,明確 Servlet 的產生動機,並揭示了 Servlet 的本質以及其在標準MVC模式中所扮演的角色。緊接着,給出了 Servlet族的繼承結構,並對族內的接口和抽象類做了進一步的介紹,並給出開發一個Servlet程序的經常使用作法。在此基礎上,咱們圖文並茂地介紹了 Servlet 的生命週期與執行流程,清晰展示 Servlet 的原理。特別地,因爲Servlet容器默認採用單實例多線程的方式來處理請求,咱們進一步介紹了Servlet 與併發的聯繫,並就Servlet容器如何同時來處理多個請求和如何開發線程安全的Servlet兩個問題進行討論和總結。最後,結合Java Web 應用的結構演變歷程,給出了MVC架構的基本組成要素、內在聯繫和做用分工,並給出了 Servlet 在其中所扮演的角色。前端

  本篇主要介紹 Servlet 理論方面的知識,更多關注於如下幾個問題:java

  • 爲何會有 Servlet;
  • Servlet 是什麼;
  • Servlet 如何實現預期效果;
  • Servlet 的做用原理;
  • Servlet 與 併發;
  • Servlet 與 Java Web 應用的結構演變歷程;
  • Servlet 與 MVC 的聯繫。

  更多關於Servlet使用、實踐方面的介紹以及Servlet新特性(Servlet 異步處理、Servlet 非阻塞IO 以及 Servlet 文件上傳等)的總結見本文的姊妹篇《Servlet 綜述(實踐篇)》。 
   
  更多關於Java併發相關知識請移步個人專欄《Java併發編程學習筆記》。該專欄全面記錄了Java併發編程的相關知識,並結合操做系統、Java內存模型和相關源碼對併發編程的原理、技術、設計、底層實現進行深刻分析和總結,並持續跟進併發相關技術。程序員


版權聲明:web

本文原創做者:書呆子Rico 
做者博客地址:http://blog.csdn.net/justloveyou_/數據庫


一. Servlet 概述

一、從請求/響應架構談 Servlet 的由來編程

  咱們以前在博文《Java Web基礎 — Jsp 綜述(下)》中對 請求/響應架構 有過比較系統的敘述,如今咱們做進一步敘述。小程序

  咱們平常所接觸到的應用有很大一部分都是基於 請求/響應架構 的,以下圖所示。在這種架構中,通常由兩個角色組成,即:Server 和 User Agent。特別地,根據 User Agent 的不一樣,咱們能夠將應用分爲 B/S模式(User Agent 爲瀏覽器時) 和 C/S模式。但不管哪一種模式,Server 與 User Agent 的交互使用的都是同一個請求和應答的標準,即 HTTP 協議後端

  通常地,以瀏覽器爲例,User Agent 的做用就是根據用戶的請求URL生成相應的 HTTP請求報文發送給服務器,並對服務器的響應進行解析(或渲染),使用戶看到一個豐富多彩的頁面。可是,若是咱們須要在網頁上完成一些業務邏輯(好比,登錄驗證),或者須要從服務器的數據庫中取一些數據做爲網頁的顯示內容,那麼除了負責顯示的HTML標記以外,必須還要有完成這些業務功能的代碼存在,這種網頁咱們稱之爲 動態網頁設計模式

  對於靜態網頁而言,服務器上存在的是一個個純HTML文件。當客戶端瀏覽器發出HTTP請求時,服務器能夠根據請求的URL找到對應的HTML文件,並將HTML代碼返回給客戶端瀏覽器。可是對於動態網頁,服務器上除了找到須要顯示的HTML標記外,還必須執行所須要的業務邏輯,而後將業務邏輯運算後的結果和須要顯示的HTML標記一塊兒生成新的HTML代碼。最後,將新的帶有業務邏輯運算結果的HTML代碼返回給客戶端。爲了實現動態網頁的目標,Servlet技術(利用輸出流動態生成 HTML 頁面)應運而生,它可以以一種可移植的方法來提供動態的、面向用戶的內容。

            請求響應.png-211.4kB


二、Servlet 的本質與角色

  Web 技術成爲當今主流的互聯網 Web 應用技術之一,而 Servlet 是 Java Web 技術的核心基礎。包括咱們在前面的博文中談到的JSP,也只是爲了彌補使用 Servlet 做爲表現層的不足而提出的。JSP規範經過實現普通靜態HTML和動態部分的混合編碼,使得邏輯內容與外觀相分離,大大簡化了表示層的實現。可是,JSP並無增長任何本質上不能用Servlet實現的功能,只是在JSP中編寫靜態HTML更加方便。事實上,JSP的本質仍然是Servlet,而且站在表現層的角度上來看,JSP 是 Servlet 的一種就簡化。

  Servlet 是 J2EE 標準的一部分,是一種運行在Web服務器端的小型Java程序,更具體的說,Servlet 是按照Servlet規範編寫的一個Java類,用於交互式地瀏覽和修改數據,生成動態Web內容。要注意的是,因爲 Servlet 是服務器端小程序,因此 Servlet 必須部署在 Servlet 容器中才能使用,例如 Tomcat,Jetty 等。

  在標準的MVC模式中,Servlet 僅做爲控制器使用,而控制器角色的做用是:負責接收客戶端的請求,它既不直接對客戶端輸出響應,也不處理用戶請求,只是調用業務邏輯組件(JavaBean)來處理用戶請求。一旦業務邏輯組件處理結束後,控制器會根據處理結果,調用不一樣的表現層頁面向瀏覽器呈現處理結果。


二. Servlet API

  關於 Servlet 的接口主要在如下兩個包中,Servlet 繼承結構以下圖所示:

  • javax.servlet.* :存放與HTTP 協議無關的通常性Servlet 類;

  • javax.servlet.http.* :除了繼承 javax.servlet.* 以外,還增長了與HTTP協議有關的功能。

      特別須要說明的是,全部的 Servlet 都必須實現 javax.servlet.Servlet 接口。若咱們開發的 Servlet程序與HTTP協議無關,那麼能夠直接繼承 javax.servlet.GenericServlet抽象類 ;不然,若咱們開發的Servlet程序和HTTP協議有關,那麼能夠直接繼承 javax.servlet.http.HttpServlet 抽象類

                Servlet-API.png-14.4kB              


  下面咱們分別看一下 Servlet接口、ServletConfig接口、GenericServlet抽象類 和 HttpServlet抽象類給咱們提供的接口:

(1) Interface Servlet

             InterfaceServlet.png-22.9kB

  Servlet 接口定義了全部Servlet都必須實現的方法。其中,destroy()方法、init()方法 和 service()方法 由Servlet容器來調用。特別地,service()方法用於處理並響應請求。


(2) Interface ServletConfig

  A servlet configuration object used by a servlet container to pass information to a servlet during initialization.

             InterfaceServletConfig.png-14kB


(3) Abstract Class GenericServlet

  GenericServlet implements the Servlet and ServletConfig interfaces. GenericServlet may be directly extended by a servlet, although it’s more common to extend a protocol-specific subclass such as HttpServlet.

  GenericServlet makes writing servlets easier. It provides simple versions of the lifecycle methods init and destroy and of the methods in the ServletConfig interface. GenericServlet also implements the log method, declared in the ServletContext interface.

  To write a generic servlet, you need only override the abstract service method.

             ServletGeneric.png-38.2kB


(4) Abstract Class HttpServlet

             HttpServlet.png-44.5kB

  經過繼承 HttpServlet 能夠很方便的幫助咱們建立一個 HTTP Servlet。咱們能夠看到,HttpServlet 爲各類Http請求都提供了相應的處理方式。可是,咱們從Servlet接口中咱們瞭解到,service()方法用於生成對客戶端的響應,那麼 service()方法與圖中所示的七種Http請求處理方法有什麼聯繫呢?下面請看 HttpServlet 對service()方法的實現:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < (lastModified / 1000 * 1000)) { maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // Error Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }

  咱們知道,service()方法用於生成對客戶端的響應。但對於 HTTPServlet 而言,service()方法會按照具體的請求類型將請求進一步分發到對應的處理方法進行處理。因爲在大部分時候,Servlet 對各類類型的請求的處理方式都是同樣的,所以咱們只需重寫service()方法便可響應客戶端的全部請求。或者,另外一種處理方式是,因爲客戶端的請求一般只有 GET 和 POST 兩種,所以咱們只需重寫doGet() 和 doPost()兩個方法便可。

  實際上,普通Servlet類裏的service()方法的做用,徹底等同於JSP轉譯所生成Servlet類裏的_jspService()方法。


三. Servlet的生命週期與執行流程

一、Servlet的生命週期

  當 Servlet 在容器中運行時,其實例的建立及銷燬等都不是由程序員所決定的,而是由Web容器進行控制的。一個Servlet對象的建立有兩個時機:用戶請求之時或應用啓動之時。

 (1) 客戶端第一次請求某個Servlet時,容器建立該Servlet實例,這也是大部分Servlet建立實例的時機; 
 (2) Web應用啓動時當即建立Servlet實例,即 load-on-startup Servlet。 

  但每一個Servlet都遵循一個生命週期,即:Servlet 實例化–>Servlet 初始化—>服務—>銷燬。具體流程以下圖所示:

  • 建立Servlet實例;

  • Web容器調用Servlet的init()方法,對Servlet進行初始化。特別地,在Servlet的生命週期中,僅執行一次init()方法;

  • Servlet 被初始化後,將一直存在於Web容器中,用於響應客戶端請求。默認的請求處理、響應方式是調用與HTTP請求的方法相應的do方法;

  • Web容器決定銷燬Servlet時,先調用Servlet的 destroy() 的方法回收資源,一般在關閉Web應用之時銷燬 Servlet。和 init() 方法相似,destroy()方法在Servlet的生命週期中也僅執行一次。當Servlet對象退出生命週期時,負責釋放佔用的資源。一個Servlet在運行service()方法時可能會產生其餘的線程,所以須要確保在調用destroy()方法時,這些線程已經終止或完成。

              Servlet生命週期.png-235.9kB

      Ps:本圖來源於CSDN博友何靜媛JAVA學習篇–Servlet詳解一文。


二、Servlet的執行流程

 Servlet的執行流程以下圖所示:

  (1) User Agent 向 Servlet容器(Tomcat)發出Http請求; 
  (2) Servle容器接收 User Agent 發來的請求; 
  (3) Servle容器根據web.xml文件中Servlet相關配置信息,將請求轉發到相應的Servlet; 
  (4) Servlet容器建立一個 HttpServlet對象,用於處理請求; 
  (5) Servlet容器建立一個 HttpServletRequest對象,將請求信息封裝到這個對象中; 
  (6) Servlet容器建立一個HttpServletResponse對象; 
  (7) Servlet容器調用HttpServlet對象的service方法,並把HttpServltRequest對象與HttpServltResponse對象做爲參數傳給 HttpServlet 對象; 
  (8) HttpServlet調用HttpServletRequest對象的有關方法,獲取Http請求信息; 
  (9) HttpServlet調用JavaBean對象(業務邏輯組件)處理Http請求; 
  (10) HttpServlet調用HttpServletResponse對象的有關方法,生成響應數據; 
  (11) Servlet容器將HttpServlet的響應結果傳給 User Agent。

             servlet執行流程.png-14.8kB


三、load-on-servlet

  load-on-servlet 指的是應用啓動時就建立的Servlet,這些Servlet一般是用於後臺服務的Servlet或者須要攔截不少請求的Servlet。也就是說,這些Servlet經常做爲應用的基礎Servlet使用,提供重要的後臺服務。例如:

@WebServlet(loadOnStartup=1) public class TimerServlet extends HttpServlet { public void init(ServletConfig config)throws ServletException { super.init(config); Timer t = new Timer(1000,new ActionListener() // 匿名內部類 { public void actionPerformed(ActionEvent e) { System.out.println(new Date()); } }); t.start(); } }

  咱們看到,這個 load-on-servlet 沒有提供 service()方法,這代表它不能響應用戶請求,因此無需爲它配置URL映射。因爲它不能接收用戶請求,因此只能在應用啓動時實例化。

  特別須要注意的是,loadOnStartup屬性用於標記容器是否在啓動的時候加載這個servlet。當值爲0或者大於0時,表示容器在應用啓動時就加載這個servlet;當是一個負數時或者沒有指定時,則指示容器在該servlet被選擇時才加載。其中,正數值越小,啓動該servlet的優先級越高。


四. Servlet的配置

  爲了讓Servlet可以響應用戶請求,必須將Servlet配置到Web應用中。從 J2EE 6(即 Servlet 3.0)開始,配置Servlet有兩種方式,即 @WebServlet註解配置 和 傳統的 web.xml 配置,這部份內容在個人下一篇博客《Servlet 綜述(實踐篇)》有詳細介紹,此不贅述。


五. Servlet 與 併發

一、Servlet容器如何同時來處理多個請求

  Servlet 採用多線程來處理多個請求同時訪問。更具體地,Servlet 依賴於一個線程池來服務請求,所謂線程池其實是一系列的工做者線程集合,該集合包含的是一組等待執行任務的線程。此外,Servlet 使用一個調度線程來管理這些工做者線程。

  當Servlet容器收到一個Servlet請求時,調度線程會從線程池中選出一個工做者線程,並將請求傳遞給該工做者線程,而後由該線程來執行Servlet的service()方法。當這個線程正在執行的時候,若是容器收到另一個請求,調度線程將一樣從線程池中選出另外一個工做者線程來服務新的請求,特別須要注意的是,容器並不關心這個請求是否訪問的是同一個Servlet。當容器同時收到對同一個Servlet的多個請求時,那麼這個Servlet的service()方法將在多線程中併發執行。

  Servlet容器默認採用單實例多線程的方式來處理請求,這樣減小產生Servlet實例的開銷,提高了對請求的響應時間,對於Tomcat容器,咱們能夠在其server.xml中經過元素設置線程池中的線程數目。


二、如何開發線程安全的Servlet

  Servlet容器採用多線程來處理請求,提升性能的同時也形成了線程安全問題。要開發線程安全的 Servlet應該從這幾個方面進行:

(1). 變量的線程安全: 多線程並不共享局部變量,因此咱們要儘量的在Servlet中使用局部變量;

(2). 代碼塊的線程安全: 可使用Synchronized、Lock 和 原子操做(java.util.concurrent.atomic)來保證多線程對共享變量的協同訪問;可是要注意的是,要儘量得縮小同步代碼的範圍,儘可能不要在service方法和響應方法上直接使用同步,這會嚴重影響性能;

(3). 屬性的線程安全: ServletContext,HttpSession,ServletRequest對象中屬性是線程安全的;

(4). 使用線程安全容器: 使用 java.util.concurrent 包下的線程安全容器代替 ArrayList、HashMap 等非線程安全容器;


  更多關於Java併發相關知識請移步個人專欄《 Java併發編程學習筆記》。該專欄全面記錄了Java併發編程的相關知識,並結合操做系統、Java內存模型和相關源碼對併發編程的原理、技術、設計、底層實現進行深刻分析和總結,並持續跟進併發相關技術。


六. Servlet 與 MVC

  Java Web 應用的結構經歷了 Model1 和 Model2 兩個時代。在 Model1 模式下,整個 Web 應用幾乎所有用JSP頁面組成,只用少許的JavaBean來處理數據庫鏈接、訪問等操做。從工程化角度來看,JSP 不但充當了表現層角色,還充當了控制器角色,將控制邏輯和表現邏輯混雜在一塊兒,致使代碼重用率極低,使得應用極難擴展和維護。

  Model2 已是基於MVC架構的設計模式。在 Model2 中,Servlet 做爲前端控制器,負責接收客戶端發送的請求,在 Servlet 中只包含控制邏輯和簡單的前端處理;而後,Servlet 調用後端的JavaBean(業務邏輯組件)來處理業務邏輯;最後,根據處理結果轉發到相應的JSP頁面處理顯示邏輯。在 Model2 模式下,模型(Model)由 JavaBean 充當,視圖(View)由JSP頁面充當,而控制器則由 Servlet 充當。Model2 的流程示意圖以下:

              Model2.png-32.7kB

  更具體地,在 Model2(標準MVC)中,角色分工以下:

  • Model:由 JavaBean 充當,全部的業務邏輯、數據庫訪問都在Model中實現;

  • View:由 JSP 充當,負責收集用戶請求參數並將應用處理結果、狀態數據呈現給用戶;

  • Controller:由 Servlet 充當,做用相似於調度員,即全部用戶請求都發送給 Servlet,Servlet 調用 Model 來處理用戶請求,並根據處理結果調用 JSP 來呈現結果;或者Servlet直接調用JSP將應用處理結果展示給用戶。


引用

《輕量級 JavaEE 企業應用實戰(第四版)》 
web.xml中load-on-startup的做用 
JAVA學習篇–Servlet詳解 
Servlet 生命週期、工做原理 
Servlet總結 
Servlet容器如何同時來處理多個請求

相關文章
相關標籤/搜索