【譯】Stackoverflow:Java Servlet 工作原理問答

jopen 8年前發布 | 22K 次閱讀 Servlet Java開發

導讀

本文來自stackoverflow的問答,討論了Java Servlet的工作機制,如何進行實例化、共享變量和多線程處理。

問題:Servlet 是如何工作的?Servlet 如何實例化、共享變量、并進行多線程處理?

假設我有一個運行了大量 Servlet 的 web 服務器。通過  Servlet 之間傳輸信息得到  Servlet 上下文,并設置 session 變量。

現在,如果有兩名或更多使用者向這個服務發送請求,接下來 session 變量會發生什么變化?究竟是所有用戶都是用共同的變量?還是不同的用戶使用的變量都不一樣?如果是后者,服務器如何區分不同用戶?

另一個相似的問題,如果有 *n* 名用戶訪問一個特定的  Servlet ,那么該  Servlet 是僅在第一個用戶首次訪問的時候實例化,還是分別為每個用戶實例化?

回答(BalusC):

ServletContext

當 Servlet 容器(比如 Apache Tomcat )啟動后,會部署和加載所有 web 應用。當web 應用被加載,Servlet 容器會創建一次  ServletContext ,然后將其保存在服務器的內存中。web 應用的  web.xml 被解析,找到其中所有  servlet 、 filter 和  Listener 或  @WebServlet 、 @WebFilter 和  @WebListener 注解的內容,創建一次并保存到服務器的內存中。對于所有過濾器會立即調用  init() 。當 Servlet 容器停止,將卸載所有 web 應用,調用所有初始化的 Servlet 和過濾器的  destroy() 方法,最后回收  ServletContext 和所有  Servlet 、Filter 與  Listener 實例。

當問題中的 Servlet 配置的  load-on-startup 或者  @WebServlet(loadOnStartup) 設置了一個大于 0 的值,則同樣會在啟動的時候立即調用  init() 方法。“load-on-startup”中的值表示那些 Servlet 會以相同順序初始化。如果配置的值相同,會遵循  web.xml 中指定的順序或 @WebServlet 類加載的順序。另外,如果不設置 “load-on-startup” 值, init() 方法只在第一次 HTTP 請求命中問題中的 Servlet 時才被調用。

HttpServletRequest 與 HttpServletResponse

Servlet 容器附加在一個 web 服務上,這個 web 服務會在某個端口號上監聽 HTTP 請求,在開發環境中這個端口通常為 8080,生產環境中通常為 80。當客戶端(web 瀏覽器)發送了一個 HTTP 請求,Servlet 容器會創建新的 HttpServletRequest 和  HttpServletResponse 對象,傳遞給已創建好并且請求的 URL 匹配  url-pattern 的  Filter 和  Servlet 實例中的方法,所有工作都在同一個線程中處理。

request 對象可以訪問所有該 HTTP 請求中的信息,例如 request header 和 request body。response 對象為你提供需要的控制和發送 HTTP 響應方法,例如設置 header 和 body(通常會帶有 JSP 文件中的 HTML 內容)。提交并完成HTTP 響應后,將回收 request 和 response 對象。

HttpSession

當用戶第一次訪問該 web 應用時,會通過 request.getSession() 第一次獲得  HttpSession 。之后 Servlet 容器將會創建  HttpSession ,生成一個唯一的 ID(可以通過  session.getId() 獲取)并儲存在服務器內存中。然后 Servlet 容器在該次 HTTP 響應的  Set-Cookie 頭部設置一個 Cookie ,以  JSESSIONID 作為 Cookie 名字,那個唯一的 session ID 作為  Cookie 的值。

按照 HTTP cookie 規則 (正常 web 瀏覽器和 web 服務端必須遵循的標準),當 cookie 有效時,要求客戶端(瀏覽器)在后續請求的  Cookie 頭中返回這個 cookie。使用瀏覽器內置的 HTTP 流量監控器,你可以查看它們(在 Chrome、Firefox23+、IE9+ 中按 F12,然后查看 Net/Network 標簽)。Servlet 容器將會確定每個進入的 HTTP 請求的  Cookie 頭中是否存在名為 JSESSIONID 的 cookie,然后用它的值(session ID)從服務端內存中找到關聯的  HttpSession 。

你可以在 web.xml 中設置  session-timeout ,默認值為 30 分鐘。超時到達之前  HttpSession 會一直存活。所以當客戶端不再訪問該 web 應用超過 30 分鐘后,Servlet 容器就會回收這個 session。后續每個請求,即使指定 cookie 名稱也不能再訪問到相同的 session。Servlet 容器會創建一個新的  Cookie 。

另一方面,客戶端上的 session cookie 有一個默認存活時間,該事件和該瀏覽器實例運行時間一樣長。所以,當客戶端關閉該瀏覽器實例(所有標簽和窗口)后,這個 session 就會被客戶端回收。新瀏覽器實例不再發送與該 session 關聯的 cookie。一個新的 request.getSession() 將會返回新的  HttpSession 并設置一個擁有新  session ID 的 cookie。

概述

  • ServletContext  與 web 應用存活時間一樣長。它被所有 session 中的所有請求共享。
  • 只要客戶端一直與相同瀏覽器實例的web應用交互并且沒有超時,HttpSession就會存在。
  • HttpServletRequest  和  HttpServletResponse  的存活時間為客戶端發送完成到完整的響應(web 頁面)到達的這段時間。不會被其他地方共享。
  • 所有 Servlet 、 Filter  和  Listener  對象在 web 應用運行時都是活躍的。它們被所有 session 中的請求共享。
  • 你設置在  HttpServletRequest 、 HttpServletResponse  和  HttpSession  中的所有屬性在問題中的對象存活時都會一直保持存活。

線程安全

即便如此,你最關心的可能是線程安全。你現在應該學習到 Servlet 和 filter 被所有請求共享。那是 Java 的一個優點,使得多個不同線程(讀取 HTTP 請求)可以使用同一個實例。否則為每個請求重新創建線程的開銷實在過于昂貴。

但你應該也意識到永遠不要將任何 request 或 session 域中的數據賦值給 servlet 或 filter 的實例變量。它將會被所有其他 session 中的所有請求共享。那是非線程安全的!下面的示例對這種情況進行了展示:

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.setParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    }
}

請參考:

原文鏈接: stackoverflow

首發至: http://www.importnew.com/17025.html ,并已同步至 Github ,歡迎 Star 關注。

來自: http://www.cnblogs.com/honoka/p/5079955.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!