編程中的容器我們可以理解為程序運行時需要的環境,那麼Tomcat 就是Servlet 的運行環境,就是一個Servlet 容器。Servlet 容器的作用是負責處理客戶請求,當Servlet 容器獲取到用戶請求後,調用某個Servlet,並把Servlet 的執行結果返回給用戶。
● 當用戶請求某個資源時,Servlet 容器使用ServletRequest 對象將用戶的請求信息封裝起來,然後調用 java Servlet API 中定義的Servlet 的生命周期方法,完成Servlet 的運行。
● Servlet 容器將Servlet 執行後需要返回用戶的結果封裝到 ServletResponse 對象中,最後由Servlet 容器發送給客戶,完成對客戶的一次服務過程。
● 每一個Servlet 都會執行 init()、service()、destory() 三個方法,在啟動時調用一次init) 方法對參數進行初始化,在該Servlet 生存期間每當收到對其的請求時都會調用Service() 方法對請求進行處理,當容器銷毀時自動調用 destory() 方法對Servlet 進行銷毀。
正是因為因為Servlet 中的service() 方法由Servlet 容器調用,所以一個 Servlet 的對象是無法調用另一個 Servlet 的方法的,但是在實際項目中,對於客戶端請求做出的響應可能會復雜,需要多個Servlet 來協作完成,這就需要請求轉發和請求包含技術了。但是,要注意,無論是請求轉發還是請求包含,都是表示由多個Servlet 共同處理同一個請求。
Servlet(源組件)先對客戶請求做一些預處理操作(一般是對響應頭進行處理),然後把請求轉發給其他Servlet(目標組件)來完成包括生成響應結果在內的後續操作。
實現方法:request.getRequestDispatcher(“接收請求的Servlet 路徑”). forward(request,response)
getRequestDispatcher(String path):該方法的返回值類型是RequestDispatcher,請求發送器,該方法的參數是指明要接收請求的Servlet 的路徑;
forward(ServletRequest req,ServletResponse res):該方法是RequestDispatcher 接口的方法,將請求從一個 servlet 轉發到服務器上的另一個資源(servlet、JSP 文件或 HTML 文件)。此方法允許一個 servlet 對請求進行初步處理,並使另一個資源生成響應。需要傳遞兩個參數,這兩個參數是當前Servlet 的request 對象和 response 對象傳遞過去的。
forward() 方法的處理流程:
● 清空用於存放響應正文(響應體)數據的緩沖區。
● 如果目標組件為Servlet 或JSP,就調用它們的service() 方法,把該方法產生的響應結果發送到客戶端,如果目標組件為文件系統中的靜態 html 文檔,就讀去文檔中的數據並把它發送到客戶端。
● 由於 forward() 方法先清空用於存放響應正文數據的緩沖區,因此servlet源組件生成的響應結果不會被發送到客戶端,只有目標組件生成的結果才會被發送到客戶端,所以對源組件叫“留頭不留體”,目標組件為“留體不留頭”。
● 如果源組件在進行請求轉發之前,已經提交了響應結果(例如調用了flush 或close() 方法),那麼forward() 方法會拋出IllegalStateException。為了避免該異常,不應該在源組件中提交響應結果,所以叫留體拋異常。
Servlet(源組件)把其他Servlet(目標組件)生成的響應結果包含到自身的響應結果中。
實現方式:request.getRequestDispatcher(“接收請求的Servlet 路徑”). include(request,response)
include(ServletRequest request,ServletResponse response):該方法是RequestDispatcher 接口的方法,表示包含。它的參數同forward() 方法的參數一樣都是由當前Servlet傳遞過去的。
包含與轉發相比,源組件與被包含的目標組件的輸出數據都會被添加到響應結果中,在目標組件中對響應狀態代碼或者響應頭所做的修改都會被忽略,所以對源組件來說是“留頭又留體”,對目標組件為“留體不留頭”。
注意:當Servlet 源組件調用 RequestDispatcher 的 forward 或 include 方法時,都要把當前的 ServletRequest 對象和ServletResponse 對象作為參數傳給 forward 或 include 方法,這就使得源組件和目標組件共享同一個ServletRequest 對象和ServletResponse 對象,就實現了多個Servlet 協同處理同一個請求。
RequestDispatcher 接口中定義了兩個方法::forward() 方法和 include() 方法,它們分別用於將請求轉發到 RequestDispatcher 對象封裝的資源和將 RequestDispatcher 對象封裝的資源作為當前響應內容的一部分包含進來.
AServlet(發送請求方):
package web.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); response.getWriter().print("您好!"); //response.getWriter().flush();//刷新會導致response的狀態為已提交! // 轉發不能在response提交之後,否則就會拋異常 request.getRequestDispatcher("/BServlet").forward(request, response); } }BServlet(接收請求方):
package web.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class BServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.getWriter().print("我很棒!"); } }
運行結果為:“我很棒!”(不會輸出“你好!”)。
對於發出請求的Aservlet是:留頭不留體(設置的響應頭可以留下,響應體被接收請求的 Bservlet 響應體覆蓋); 留體拋異常,就是說只要請求轉發了,就要將請求完全由 Bservlet 處理,Aservlet就不能插手了,如果Aservlet 也提交了對請求的處理,那麼Bservlet 就不能處理請求了(因為請求已經被Aservlet 處理了,還處理啥) ,你安排人家處理請求,最終卻由你處理了,當forward() 轉發請求時,發現請求已經被處理,就會拋出異常。
不過要注意,只有當AServlet 提交了處理(如例中手動flush將緩沖區內數據提交)才會拋出異常,如果沒有提交,說明之前A對請求的處理還在緩沖區中,B就會直接將A的緩沖區清空,然後覆蓋掉,就形成了之前的留頭不留體。
CServlet(發送請求方):
package web.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 請求包含:留頭又留體 */ public class CServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); //設置內容類型 response.getWriter().print("你好!"); request.getRequestDispatcher("/DServlet").include(request, response); } }
DServlet(接收請求方):
package web.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().print("我很棒!"); } }運行結果為:你好!我很棒!
結果說明請求包含是多個Servlet 共同處理一個請求的,並且發送方和接收方都能夠留下響應體。
請求轉發和請求包含都是在處理一個相同的請求,多個Servlet之間使用同一個 request 對象和 response 對象。
● 如果在AServlet中請求轉發到BServlet,那麼在AServlet中不允許再輸出響應體,即不能使用response.getWriter() 和response.getOutputStream() 向客戶端輸出,這一工作交由BServlet來完成;如果是由AServlet請求包含BServlet,則沒有這個限制。
● 請求轉發不能設置響應體,但是可以設置響應頭,簡單來說就是“留頭不留體”,例如:response.setContentType("text/html;charset=utf-8”) 是可以留下來的;請求包含不僅可以設置響應頭,還可以設置響應體,簡單來說就是“留頭又留體“。
● 請求轉發大多應用在Servlet中,轉發目標大多是jsp頁面;請求包含大多應用在jsp頁面中,完成多頁面的合並。一般情況下經常使用的是請求轉發。
● 在Servlet中向數據庫獲取數據,保存到request域中;
● 轉發到jsp頁面,jsp從request域中獲取數據,顯示在頁面上。
● 對於客戶端浏覽器來說,轉發是一個請求,重定向是兩個請求;
● 轉發浏覽器地址欄不變化,重定向會變成轉發後的URL ;
● 轉發只能在一個項目內,而重定向沒有限制,可以重定向到任意網址,如京東、淘寶等 ;
● 轉發可以使用request 域傳遞數據,而重定向不能。因為轉發是一個請求,重定向是兩個請求;
● 轉發只有一個請求,原來是什麼請求方式就是什麼方式;而重定向兩個請求,第一個可能為post 可能為get ,但是第二個請求一定是get 。