Java Servlet工作原理問答,javaservlet
導讀
本文來自stackoverflow的問答,討論了Java Servlet的工作機制,如何進行執行個體化、共用變數和多執行緒。
問題:Servlet是如何工作的?Servlet 如何執行個體化、共用變數、並進行多執行緒?
假設我有一個運行了大量 Servlet 的 網頁伺服器。通過 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。當用戶端(網頁瀏覽器)發送了一個 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 服務端必須遵循的標準),當 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 中的所有請求共用。那是非安全執行緒的!下面的樣本對這種情況進行了展示:
| 1234567891011 |
public class ExampleServlet extends HttpServlet { private Object thisIsNOTThreadSafe; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object thisIsThreadSafe; thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests! thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe. } } |
全能程式員交流QQ群290551701,聚集很多互連網精英,技術總監,架構師,專案經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!