Servlet技術 - Servlet應用

#轉發與重定向 瀏覽器把請求發送給ServletA,ServletA把請求傳遞給ServletB,由ServletB進行繼續處理,最後輸出資源響應。html

輸入圖片說明

#轉發 ##請求轉發web

  • forward
    ServletA調用forward方法把請求轉發給ServletB
  • 將當前的request和response對象交給指定的web組件處理
    瀏覽器不知道ServletA轉發請求給了ServletB,對於瀏覽器來講發出一次請求,獲取一次響應
  • 一次請求,一次響應
    請求轉發過程當中,瀏覽器URL地址欄不會發生變化

##轉發對象面試

  • RequestDispatcher對象
    由Servlet容器建立,用來封裝一個由路徑所標示的服務器資源,該對象有兩個比較重要的方法forward方法和include方法,forward方法是指轉發,include方法指包含,把請求轉發後,原有組件和新組件都輸出響應信息。 ###經過兩種方式獲取轉發對象
  • 經過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繼續爲用戶服務。併發

  • sendRedirect方法
    ServletA調用sendRedirect方法,將客戶端的請求重定向到ServletB
  • 請求重定向:經過response對象發送給瀏覽器一個新的URL地址,讓其從新請求
  • 兩次請求,兩次響應
    過程對用戶是透明的,瀏覽器默認把第二次請求作掉了,須要注意,在請求重定向後,瀏覽器地址欄會發生響應的改變。

##請求重定向實例 可使用絕對路徑,或者相對路徑

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

##轉發&重定向總結

  • 瀏覽器地址欄變化
    轉發地址欄不發生變化,重定向地址欄將會變成重定向的地址
  • 請求範圍
    轉發是在同一個web應用中進行轉發,而重定向既能夠重定向到本web應用,也能夠重定向到外部URL。
  • 請求過程
    請求轉發是一次請求一次響應,請求重定向是兩次請求兩次響應。

#過濾器與監聽器

#過濾器

  • 過濾請求與響應
  • 自定義過濾規則
    按照過濾器的規範,編寫對應代碼
  • 用於對用戶請求進行預處理,和對請求響應進行後處理的web應用組件
    過濾器可以對Servlet的請求和響應對象進行檢查和修改,Servlet過濾器自己並不生成請求和響應對象。提供過濾功能,過濾器可以在Servlet調用以前檢查Request對象,並可以修改Request header和Request內容,在Servlet被調用以後,可以檢查Response對象,修改Response的Header和內容。

##過濾器工做原理

  • 1.首先經過客戶端,發送原始請求到Servlet容器,因爲有過濾器,則請求發送給過濾器
  • 2 通過過濾器處理,請求轉發給對應的Servlet
  • 3 Servlet處理完成後,將原始響應發送給過濾器
  • 4 最後由過濾器發送過濾後的響應給客戶端

##過濾器應用場景

  • 用戶認證
    過濾非法用戶,肯定用戶有沒有登陸,是否有權限訪問頁面。
  • 編解碼處理
    若是咱們的請求有亂碼的問題,咱們能夠經過過濾器進行預處理,處理完成後將正確的結果發還給Servlet進行處理
  • 數據壓縮處理
    當咱們請求的數據比較,咱們能夠經過過濾器進行壓縮,而後再將數據發到對應的服務端,這樣減輕服務端的處理壓力

##過濾器生命週期 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對象。

###過濾器鏈請求過程

  • 客戶端原始請求首先發送給第一個Filter
  • 第一個filter調用了doFilter方法,就會把請求傳遞給下一個filter,若是第二個filter下還有filter,則繼續傳遞
  • 若是沒有filter則把過濾後的請求,傳遞給被調用資源或者servlet對象。
  • Servlet處理結果後,把原始響應傳送給對應的filter

輸入圖片說明

#監聽器 監聽器通常理解,有監聽器監聽信息,有終端接收信息。因此監聽器分爲兩個部分,咱們須要監聽的東西稱之爲事件源。另外就是咱們的監聽器。
監聽器首先向某個事件源進行註冊,把監聽器放到咱們須要監聽的地方,當事件發生後,事件源將會通知發送到對應的監聽器,監聽後的信息,咱們須要進行相應的處理。
輸入圖片說明 ##定義

  • 監聽事件發生,在事件發生先後可以作出響應處理的web應用組件

這裏咱們須要注意的是,Servlet監聽器註冊,不是註冊到事件源上,而是由Servlet容器負責註冊。開發人員只須要在部署描述符中進行配置,而後servlet容器就會自動的把對應的監聽器註冊到對應的事件源中。

##監聽器分類 Listener按照監聽對象進行整體劃分,還能夠繼續劃分

  • 監聽應用程序環境(ServletContext)
    • ServletContextListener
      對ServletContext對象建立銷燬進行監聽
    • ServletContextAttributeListener
      對ServletContext屬性監聽器,當這些對象對應的屬性有增刪改的變化的時候,這些監聽器就會被觸發。
  • 監聽用戶請求對象(ServletRequest)
    • ServletRequestListener
      對ServletRequest對象建立銷燬進行監聽
    • ServletRequestAttributeListener
      對ServletRequest屬性監聽器,當這些對象對應的屬性有增刪改的變化的時候,這些監聽器就會被觸發。
  • 監聽用戶會話對象(HTTPSession)
    • HttpSessionListener
      對HttpSession對象建立銷燬進行監聽
    • HttpSessionAttributeListener
      對HttpSession屬性監聽器,當這些對象對應的屬性有增刪改的變化的時候,這些監聽器就會被觸發。
    • HttpSessionActivationListener
      監聽Session在持久化時,磁盤或者從磁盤中重新加載到jvm中,觸發的監聽器。
    • HttpSessionBindingListener
      在Session對象進行調用attribute方法和removeattribute方法時進行調用。

##監聽器的應用場景

  • 應用統計
    對用戶登陸進行統計,每一個用戶對應一個session,因此咱們能夠經過監聽器對一個站點用戶登陸進行統計。
  • 任務觸發
    好比招聘系統中,發現招聘者的狀態發生了變化,舉例:面試者的狀態是面試成功,則給這個應聘者發送郵件通知。
  • 業務需求

##監聽器啓動順序 監聽器的啓動順序與過濾器是一致的,在部署描述符中出現的越靠前,咱們就會 越早的進行註冊(初始化)

輸入圖片說明

##監聽器、過濾器、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。

輸入圖片說明

##線程模型

  • 1 客戶端發送請求給Servlet容器
  • 2 Servlet容器,首先把請求傳遞給調度器,由調度器統一的進行請求派發。
  • 3 調度器會從Servlet容器中的線程池中,選取一個工做組線程 線程池,而後把請求派發給該線程,而後由該線程執行servlet的service方法。
  • 4 同時,若是客戶端發送第二份請求時,調度器會選取另一個工做組線程,來服務這個新的請求。若是發現同一servlet收到多個請求,service方法將會在多線程中併發執行。 當線程使用完畢後,會把線程在放回線程池中。
  • 5 若是如今線程池中的線程都在服務,若是這時有新的請求,通常狀況下進行排隊處理。
  • 6 Servlet容器能夠配置最大請求數量,超過這個數量,Servlet容器會直接拒絕這個請求。

輸入圖片說明

##Servlet併發處理

  • 單實例
    咱們知道Servlet只會初始化一次,只調用一次init方法。也就是說在整個Servlet中只會有一個Servlet對象,無論咱們有多少請求,只針對同一Servlet對象實例。
  • 多線程
    請求處理由多個工做線程進行處理,同時請求線程數量的大小,由線程池的配置決定
  • 線程不安全
    默認沒有加鎖操做,則多個線程同時對Servlet的屬性進行變動,發生不安全

##Servlet線程安全

  • 變量的線程安全

    • 參數變量本地化 - 儘可能使用局部變量
    • 使用同步塊synchronized - 進行加鎖處理
      加鎖時,咱們須要注意,儘可能縮小synchronized的代碼範圍,不要在Servlet上增長這個關鍵字,對性能損耗大
  • 屬性的線程安全

    • ServletContext線程不安全
      能夠多線程同時讀寫ServletContext屬性的
    • HttpSession理論上線程安全 - 實際不安全
      當發生同一瀏覽器,打開多個標籤頁,同時屢次訪問Servlet時,會啓動多線程對Servlet進行使用。則會對同一HttpSession進行操做,致使線程不安全。
    • ServletRequest線程安全
      每個工做線程只對應一個ServletRequest,則線程安全
  • 避免在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可以正確返回數據,可是因爲添加了同步鎖,致使須要以前的同步鎖執行結束後,纔可以執行當前操做內容,則耗時延長。 輸入圖片說明

相關文章
相關標籤/搜索