#轉發與重定向 瀏覽器把請求發送給ServletA,ServletA把請求傳遞給ServletB,由ServletB進行繼續處理,最後輸出資源響應。html
#轉發 ##請求轉發web
##轉發對象面試
HttpServletRequest
獲取ServletContext
獲取##轉發實例 ###經過request.getRequestDispatcher 轉發路徑:注意這裏是轉發Servlet路徑,能夠填寫絕對路徑和相對路徑的apache
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 經過request對象獲取轉發對象 RequestDispatcher requestDispatcher = req.getRequestDispatcher("/forwardExample"); // 轉發 requestDispatcher .forward(req, resp); }
經過URL進行訪問與測試瀏覽器
http://localhost:8080/web_project_template/forward?user=123
輸出結果安全
Class ServletForward Method init Class ServletForward Method doGet [request]:org.apache.catalina.connector.RequestFacade@5f36101b Class ServletForwardExample Method init Class ServletForwardExample Method doGet [request]:org.apache.catalina.core.ApplicationHttpRequest@51eeeb5b
能夠看到實現了轉發功能並顯示出ServletForwardExample的返回結果服務器
###經過ServletContext獲取轉發對象 ServletContext有兩種方式獲取轉發對象,經過this.getServletContext().getNamedDispatcher
是須要知道Servlet名稱(備註:在web.xml查看)。經過this.getServletContext.getRequestDispatcher
只可以填寫絕對路徑session
// ServletContext能夠經過兩種方式獲取轉發對象 requestDispatcher = this.getServletContext().getNamedDispatcher("ServletForwardExample"); requestDispatcher = this.getServletContext().getRequestDispatcher("/forwardExample");
##用戶登陸流程 瀏覽器發送登陸請求給服務器,服務器返回登陸響應,在登陸驗證完成以後,咱們一般會發現咱們的瀏覽器會跳轉到另外的頁面。並且瀏覽器上的地址欄也改變了。這裏咱們作了一次請求,在咱們登陸驗證完成以後,服務器端向瀏覽器返回了另一個URL地址的響應信息。瀏覽器在接收到該響應信息後,會自動的向服務器請求返回地址,而後服務器端會返回對應的跳轉結果。瀏覽器進入到另一個頁面。多線程
#請求重定向 服務器是但願用戶在登陸後,進入到用戶界面。也就是說服務器端但願ServletA處理結束,ServletB繼續爲用戶服務。併發
##請求重定向實例 可使用絕對路徑,或者相對路徑
resp.sendRedirect("redirectExample");
瀏覽器訪問路徑
http://localhost:8080/web_project_template/redirect?user=123
Chrome開發者模式,能夠看到有兩次應答
注意:請求轉發是同一個請求對象,只進行一次響應;請求重定向是兩次請求,兩次響應。若是在跳轉中的URL並無對應的parameter,則獲取的值爲空。
經過Chrome查看第一次響應的Location內容
###重定向絕對路徑問題
resp.sendRedirect("/redirectExample");
若是這樣填寫重定向的絕對路徑,若是使用maven啓動,或者在部署時,並非部署到ROOT。則會發生訪問的路徑爲
http://localhost:8080/redirectExample
而不是,咱們所須要的
http://localhost:8080/web_project_template/redirectExample
##轉發&重定向總結
#過濾器與監聽器
#過濾器
##過濾器工做原理
##過濾器應用場景
##過濾器生命週期 filter的建立和銷燬,一樣由Servlet容器負責。Web應用啓動的時候,Servlet容器會根據部署描述符的配置,建立filter的實例對象。調用filter的init方法,完成過濾器的初始化。爲後續的攔截請求作準備。過濾器在生命週期中只會建立一次,因此init方法只會執行一次。
跟Servlet同樣,在部署描述符的filter當中,也能夠配置filterconfig的對象,用來存儲filter的一些配置信息。在filter完成初始化工做以後,進入正式的過濾操做doFilter方法。這個方法與servlet的service方法相似,完成過濾的實際操做。對每一個請求響應作出響應處理。
當客戶端請求訪問與過濾器相關聯的URL時,Servlet過濾器就會執行對應的doFilter方法。咱們能夠在這個方法當中作前置處理和後置處理。
過濾器當Web應用被移除,或者容器服務重啓時,執行destory銷燬方法。當Servlet容器卸載掉對應的Filter對象以前,destory方法將會被調用。只執行一次。釋放過濾器資源。
##過濾器實例 Servlet部署描述符(web.xml)配置filter,部署描述符當中的Filter對象下的init-param所添加的參數,在TestFilter當中的doFilter方法經過FilterConfig參數傳遞並使用。
filter-mapping的url-pattern使用規則與servlet一致
<filter> <init-param> <param-name>filterParam</param-name> <param-value>111</param-value> </init-param> <filter-name>TestFilter</filter-name> <filter-class>com.netease.server.example.web.controller.filter.TestFilter</filter-class> </filter> <filter-mapping> <filter-name>TestFilter</filter-name> <url-pattern>/hello/world/*</url-pattern> </filter-mapping>
建立TestFilter並繼承Filter接口
public class TestFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("Class TestFilter Method init"); String value = filterConfig.getInitParameter("filterParam"); System.out.println("Class TestFilter Method init [filter.config key=filterParam]:" + value); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Class TestFilter Method doFilter"); // TODO } @Override public void destroy() { System.out.println("Class TestFilter Method destroy"); }
登陸過的用戶,直接跳轉,若是沒有登陸的用戶跳轉到登陸界面
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Class TestFilter Method doFilter"); // 登陸過的用戶,直接跳轉,若是沒有登陸的用戶跳轉到登陸界面 HttpServletRequest req = (HttpServletRequest) request; HttpSession session = req.getSession(); if (session.getAttribute("userName") == null) { HttpServletResponse res = (HttpServletResponse) response; res.sendRedirect("../index.html"); } else { chain.doFilter(request, response); } }
**注意:**相對路徑../index.html
的..
爲返回上一層路徑
FilterChain參數,有doFilter方法,主要是把請求向下傳遞,請求將會傳遞到下一個過濾器,或者是客戶端所請求的Servlet。
當咱們沒有登陸的時候,在瀏覽器中輸入http://localhost:8080/web_project_template/hello/world
會自動跳轉登陸界面。
登陸結束後,當咱們再次在瀏覽器中輸入http://localhost:8080/web_project_template/hello/world
會跳轉到對應的資源頁面。
##過濾器鏈 咱們在Web應用中會有多個filter,這些filter組成filter鏈。一個請求經過filter-mapping匹配到多個filter,這個時候web服務器就會根據filter在部署描述符中的前後順序,決定首先調用哪一個filter。當第一個filter方法被調用時,servlet容器會建立一個表明filter鏈的FilterChain對象,傳遞給Filter的doFilter方法。若是開發者在doFilter方法中,調用了FilterChain對象的doFilter方法。則會傳遞給下一個Filter或者被調用資源的Servlet對象。
###過濾器鏈請求過程
#監聽器 監聽器通常理解,有監聽器監聽信息,有終端接收信息。因此監聽器分爲兩個部分,咱們須要監聽的東西稱之爲事件源。另外就是咱們的監聽器。
監聽器首先向某個事件源進行註冊,把監聽器放到咱們須要監聽的地方,當事件發生後,事件源將會通知發送到對應的監聽器,監聽後的信息,咱們須要進行相應的處理。
##定義
這裏咱們須要注意的是,Servlet監聽器註冊,不是註冊到事件源上,而是由Servlet容器負責註冊。開發人員只須要在部署描述符中進行配置,而後servlet容器就會自動的把對應的監聽器註冊到對應的事件源中。
##監聽器分類 Listener按照監聽對象進行整體劃分,還能夠繼續劃分
##監聽器的應用場景
##監聽器啓動順序 監聽器的啓動順序與過濾器是一致的,在部署描述符中出現的越靠前,咱們就會 越早的進行註冊(初始化)
##監聽器、過濾器、Servlet啓動順序 先建立監聽器、在建立過濾器、最後建立Servlet
##監聽器實例 建立監聽器
public class TestListener implements HttpSessionAttributeListener, ServletContextListener, ServletRequestListener { // ServletRequestListener @Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("listener: request destroy"); } @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("listener: request init"); } // ServletContextListener @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("listener: context init"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("listener: context destroy"); } // HttpSessionAttributeListener @Override public void attributeAdded(HttpSessionBindingEvent event) { System.out.println("listener: session attribute added."); } @Override public void attributeRemoved(HttpSessionBindingEvent event) { System.out.println("listener: session attribute removed"); } @Override public void attributeReplaced(HttpSessionBindingEvent event) { System.out.println("listener: session attribute replaced"); } }
在對應的部署描述符中配置
<listener> <listener-class>com.netease.server.example.web.controller.listener.TestListener</listener-class> </listener>
課程提供的代碼修復BUG後的打印輸出(該BUG在使用session前就把session註銷了)。
listener: context init Class TestFilter Method init Class TestFilter Method init [filterconfig key=filterParam]:111 init /hello/* init /hello listener: request init listener: request destroy listener: request init listener: request destroy listener: request init init /hello/world Class TestFilter Method doFilter service method doGet method listener: request destroy listener: request init second login: 123 listener: session attribute removed listener: session attribute added. listener: request destroy
#Servlet併發處理 使用應用開發過程當中,多個客戶端同時請求同一Servlet。
##線程模型
##Servlet併發處理
##Servlet線程安全
變量的線程安全
屬性的線程安全
避免在Servlet中建立線程
多個Servlet訪問外部對象加鎖
Servlet安全問題主要因爲使用實例變量,儘可能避免使用實例變量。若是在程序設計中沒法避免使用實例變量,則使用同步的操做來保護咱們須要使用的實例變量,爲了保證系統性能。注意同步的範圍。
##Servlet線程安全實例 ###Servlet不安全實例 人爲製造不安全的Servlet
public class ConcurrentServlet extends HttpServlet { String name; @Override public void init() throws ServletException { System.out.println("Class ConcurrentServlet Method init"); super.init(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Class ConcurrentServlet Method doGet"); // synchronized (this) { // 從URL獲取username存放在屬性中 name = req.getParameter("username"); PrintWriter out = resp.getWriter(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } out.println("username: " + name); // } } @Override public void destroy() { System.out.println("Class ConcurrentServlet Method destroy"); super.destroy(); } }
部署描述符(web.xml)配置
<!-- Concurrent Servlet --> <servlet> <servlet-name>ConcurrentServlet</servlet-name> <servlet-class>com.netease.server.example.web.controller.ConcurrentServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ConcurrentServlet</servlet-name> <url-pattern>/concurrent</url-pattern> </servlet-mapping>
首先測試的URL:http://localhost:8080/web_project_template/concurrent?username=ddd
等待5秒後打印了ddd,Chrome開發者模式:
咱們在先訪問http://localhost:8080/web_project_template/concurrent?username=ddd
,再快速訪問(5秒內,實際併發遠比這個時間小的多)http://localhost:8080/web_project_template/concurrent?username=aaa
,Chrome開發者模式:
###Servlet修改爲爲安全實例 添加synchronized同步塊進行處理
synchronized (this) { }
Servlet部分代碼
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Class ConcurrentServlet Method doGet"); synchronized (this) { // 從URL獲取username存放在屬性中 name = req.getParameter("username"); PrintWriter out = resp.getWriter(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } out.println("username: " + name); } }
Chrome測試結果:
咱們能夠看到,優先訪問的ddd,可以按照正確的時間進行返回,並返回正確的數據內容。
咱們也能夠看到aaa可以正確返回數據,可是因爲添加了同步鎖,致使須要以前的同步鎖執行結束後,纔可以執行當前操做內容,則耗時延長。