重学JavaWeb —— Servlet,简单全面一发入魂
文章目錄
- Servlet
- 概述
- 基本使用
- 兩個重要對象
- 請求轉發
- 會話技術
- Cookie
- Session
- 對比小結
- 其它相關對象
- ServletContext
- ServletConfig
- 過濾器
- 概述
- 使用
- 配置參數
- 注意
- 應用場景
- 監聽器
Servlet
概述
狹義地說,Servlet就是定義在JavaEE規范中的一個接口,javax.servlet.Servlet,參見JavaEE的API文檔
package javax.servlet;import java.io.IOException;public interface Servlet {void init(ServletConfig config) throws ServletException;ServletConfig getServletConfig();void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;String getServletInfo();void destroy(); }Servlet接口定義的是一套處理網絡請求的規范。一個實現了Servlet接口的類(下面簡稱Servlet類),表明它是一個可以處理網絡請求的類。Servlet類需要實現Servlet接口中的5個方法,其中最主要的3個是:
- init()
- destroy()
- service()
它們分別定義了,一個Servlet類,
- 在初始化時需要干什么
- 在銷毀時需要干什么
- 在每次接收到請求時,要作何處理
實現了Servlet接口的類,就能處理網絡請求了嗎?
顯然沒這么簡單。
我們只知道,一個Servlet類在處理請求時,執行的是它的service()方法,但service()方法是由誰調用的,方法入參中的request和response對象又是怎么來的,這顯然不是Servlet類自己做的,而且Servlet中也沒有諸如監聽網絡端口等和網絡請求有關的操作。
所以,Servlet類要處理網絡請求,還需要依賴一個東西——Servlet容器。這個容器要負責監聽網絡端口,封裝網絡請求數據,管理眾多的Servlet類,進行請求的調度分發,返回響應數據等。最典型的Servlet容器就是Tomcat。
Servlet容器和Servlet類是如何協作處理一個HTTP請求的?
1. Servlet容器監聽網絡端口,接收到HTTP請求 2. 根據請求的url, 查找對應的Servlet類 3. 若對應的Servlet類不在容器中,則檢索并創建該Servlet實例,并初始化(調Servlet的init方法) 4. 容器構建該次請求的request和response對象, 調用對應Servlet的service方法,傳入這2個參數 5. Servlet類執行service方法, 對請求進行處理,并設置響應數據到response對象中 6. 容器取出response對象中的響應數據,封裝好HTTP響應報文,返回給客戶端我們可以看到,在HTTP請求的整個處理過程中,干活兒最多的,其實是Servlet容器。Servlet容器把處理請求的整個流程,框架,都搭好了,只是在中間留了很小的一部分空間,給Servlet類。所以,Servlet類負責的,其實只是整個流程中很小的一環。
不過,這也是因為,在處理網絡請求時,諸如監聽網絡端口,封裝請求數據,響應請求數據,這些操作,都是共性的。也就是說,所有的處理網絡請求的程序,都必須有這些相同的步驟,而唯一不同的地方,只是在于對請求的業務處理。于是就把這些相同的地方抽取出來,做成了Servlet容器,不同的地方,交給各個Servlet類。這也體現了編程領域中,抽取共性,封裝變化的設計理念。試想,若沒有Servlet容器,則每開發一個網絡程序,都要自己完整實現一套處理網絡請求和響應的流程,那開發的門檻就太高了,開發量也很大,并且都是重復的工作。
廣義地講,Servlet是一套規范體系,不僅僅是javax.servlet.Servlet接口,而是javax.servlet包下定義的全部規范。主要包括了JavaWeb三大組件,Servlet,Filter,Listener,以及其他一系列相關的接口。
下面,對Servlet的規范體系進行非常簡要的歸納總結。
基本使用
構建一個最基本的web程序,只需要有Servlet類(實現了javax.servlet.Servlet接口的類)即可
Servlet配置參數:
- urlPattern:該Servlet處理的url
- initParams:name - value,鍵值對形式,可通過ServletConfig獲取
- loadOnStartup:值為一個整數。若是0或正數,容器啟動時就立刻加載Servlet,數字越小越先加載。若是負數,容器可自由選擇何時加載(一般會懶加載,即用到該Servlet時才加載)
Servlet生命周期:
- init():容器加載Servlet實例,進行初始化時,調用執行Servlet的init()
- destroy():容器銷毀Servlet實例時,調用執行Servlet的destroy()
- service():容器將請求交由一個Servlet處理時,調用執行Servlet的service()
javax.servlet.Servlet只是最原始的接口規范,javax.servlet包下還定義了一些基本的抽象子類,如javax.servlet.GenericServlet,javax.servlet.http.HttpServlet,類的層次結構圖如下
由于構建的web應用幾乎都是基于HTTP協議的,我們創建一個Servlet時,通常只需要繼承HttpServlet,由于最常用的HTTP方法是GET和POST,通常我們只需要重寫doGet和doPost方法即可。本文下面的內容,默認基于HTTP協議。
兩個重要對象
在Servlet處理請求時,有兩個最重要的對象——request和response。前者封裝了HTTP請求數據,后者封裝了HTTP響應數據。這兩個對象分別是HttpServletRequest和HttpServletResponse類的實例。對于request,主要的操作是get,因為需要從其中獲取請求數據,對于response,主要的操作是set,因為需要往里面設置響應數據
它們中的常用方法下面列舉一二
HttpServletRequest:
- getRequestURL:獲取請求URL
- getMethod:獲取請求方法
- getParameter(String s):獲取請求參數
- getHeader(String s):獲取請求頭
- getInputStream() / getReader():獲取請求體
- …
HttpServletResponse
- addHeader(String k, String v):添加響應頭
- getOutputStream() / getWriter():獲取輸出流,以便往響應體里寫入數據
- setStatus(int sc):設置HTTP響應狀態碼
- sendRedirect(String s):設置重定向
- …
請求轉發
當一個請求,在一個Servlet中不能完成處理,需要進行請求轉發時,有2種轉發方式
-
服務端轉發
通過request.getRequestDispatcher("/xx").forward(request,response)
轉發到能處理/xx這種url的Servlet。發生在服務端內部,本質相當于方法調用,只能轉發到服務端內部的資源。
-
客戶端轉發
通過設置HTTP狀態碼為3xx(一般設置為302),并在HTTP響應頭添加Location指定重定向的url地址,瀏覽器收到3xx的HTTP響應,會自動重定向到Location頭部指定的url地址。實際發生了2次HTTP請求,可跳轉到任意url??梢哉{用response對象的sendRedirect方法完成。
會話技術
眾所周知,HTTP是無狀態的協議,即每組HTTP請求/響應,都是互相獨立的,HTTP協議本身不具備記憶能力。但有的場景需要在多次HTTP請求之間維護一些狀態信息,此時,就輪到會話技術登場了。根據狀態信息是保存在服務端,還是客戶端,會話技術分為了2種:Cookie和Session
Cookie
cookie是HTTP協議的擴展標準。本質就是一個簡單的k-v鍵值對。當瀏覽器收到的響應報文中,包含了Set-Cookie頭部時,會將該頭部中的k-v鍵值對,保存在瀏覽器端(默認保存在瀏覽器內存,在瀏覽器關閉后失效),下次再發起請求時,會自動添加Cookie頭部,攜帶先前保存下來的k-v鍵值對。
在Servlet中,通過response對象來向瀏覽器發送一個cookie,具體操作如下
Cookie cookie = new Cookie("userId","123"); response.addCookie(cookie);HTTP響應報文如下(簡化)
HTTP/1.1 200 OK Set-Cookie: userId=123下次請求時的HTTP請求報文如下(簡化)
GET / HTTP/1.1 Host: www.baidu.com Cookie: userId=123在Servlet中,通過request對象獲取請求中攜帶的cookie,具體操作如下
Cookie[] cookies = request.getCookies(); // 獲取請求攜帶的全部Cookie for(Cookie c : cookies) {String name = c.getName();String value = c.getValue(); }k-v鍵值對,是cookie最基本的信息。除此之外,cookie還可以設置以下屬性
- domain:設置允許攜帶cookie的域
- path:設置允許攜帶cookie的資源路徑
- maxAge:設置cookie最大存活時間,單位秒(若不設置,cookie默認在瀏覽器關閉時失效)
- httpOnly:設為true,可避免JS腳本竊取cookie
- secure:設為true,則只有使用HTTPS等安全的協議時,才攜帶Cookie
另:Cookie,翻譯過來是小餅干的意思,這樣的命名也意味著,它只能保存少量的信息(簡單的k-v鍵值對),且瀏覽器一般對cookie的大小,數量等都有所限制,而由于將信息保存在了瀏覽器端,也就意味著信息容易遭到竊取或篡改,不夠安全。
于是,將信息保存在服務端的Session技術,輪到它登場了。
Session
session是依賴于cookie的。它的大概原理是:一次請求到來,服務器端在處理請求時,生成一個session對象(保存在服務端的內存中),以及一個對應的sessionId。當服務端處理完畢,返回響應時,在HTTP響應報文添加這樣的頭部Set-Cookie:JSESSIONID=123。瀏覽器端保存下這個name為JSESSIONID的cookie,在下次請求時,攜帶這個cookie。服務端根據JSESSIONID,找到自己內存中對應的session對象,這樣,即可在多個HTTP請求之間共享數據。在tomcat的配置中,session的默認有效時間為30分鐘。session對象中也可以添加k-v鍵值對,但值不僅僅是字符串,還可以是任意的Object對象,這就比cookie能攜帶的信息要大得多了。且session對象的大小,理論上只會受到服務器內存大小的限制。且由于存在服務端,安全性相對就高了許多。
在Servlet中,一個session對象是一個HttpSession類的實例,通過request對象來生成或獲取一個session對象,如下
// 1. 若此次請求中沒有攜帶name為JSESSIONID的cookie, 則getSession會新創建一個session對象 // 并把對應的id添加到響應頭 // 2. 若此次請求中有攜帶name為JSESSIONID的cookie, 則getSession會根據這個id的值 // 從內存中找到該id對應的session對象 HttpSession session = request.getSession();session對象中常用的方法:
- setAttribute(String s, Object o):往session對象中添加一組鍵值對數據
- getAttribue(String s)
- setMaxInactiveInterval(int i):設置session對象最大有效時長,單位秒。(負數表示永不失效)
- invalidate():使該session對象失效
另:
對比小結
| 數據存儲位置 | 客戶端(瀏覽器端) | 服務端 |
| 限制 | 少量數據,簡單的字符串鍵值對 | 可存很多數據,任意Object類型 |
| 有效性 | 默認在瀏覽器關閉后失效 | 默認有效時長30分鐘(tomcat中) |
| 安全性 | 低 | 高 |
其它相關對象
上面的會話技術,是在同一用戶的多次請求之間進行數據共享。若要在整個web應用中共享數據,則可以通過ServletContext對象實現。
ServletContext
一個Web應用,對應一個ServletContext對象,根據命名也能看出,這是整個web應用的上下文環境。這個對象中的常用方法,下面列舉一二
- setAttribue(String s, Object o):添加一組鍵值對數據
- getAttribe(String s)
- getInitParameter(String s):獲取web應用的全局初始化參數(web.xml中的<context-param>標簽)
- …
小應用:統計網站訪問量。每收到一個請求,就對ServletContext中的一個屬性進行累加操作
ServletConfig
ServletConfig對象用于向Servlet傳遞一些參數,以便在Servlet初始化時使用。它主要包含了如下方法
- getServletContext():獲取web應用全局上下文
- getInitParameter(String s):獲取Servlet初始化參數(web.xml中的<init-param>標簽,也可通過@WebInitParam注解進行設置)
過濾器
概述
Servlet,Filter,Listener并稱JavaWeb三大組件。
其中的Filter,指的就是javax.servlet.Filter接口。它可以在請求被處理(請求的資源可以是Servlet,也可以是HTML等靜態資源)的之前,之后,進行一些額外的操作。
它的工作流程如下圖所示
過濾器可以配置不止一個,當有多個過濾器時,它們就形成了一個鏈,如下圖
與Servlet的生命周期類似,Filter也有如下3個方法
- init():過濾器被創建時,該方法被調用
- destroy():過濾器被銷毀時,該方法被調用
- doFilter():過濾器對請求/響應進行過濾處理時,該方法被調用
Filter相關的一共有3個類
- javax.servlet.Filter:核心接口
- javax.servlet.FilterConfig:過濾器配置接口,可以通過該接口給過濾器傳遞一些初始化參數
- javax.servlet.FilterChain:過濾器鏈接口
使用
創建一個過濾器的步驟如下
新建一個類,實現javax.servlet.Filter接口
重寫init(),destroy(),doFilter()方法
配置過濾器
-
xml方式:web.xml
<filter><filter-name>myFilter</filter-name><filter-class>filter.LogFilter</filter-class></filter><filter-mapping><filter-name>myFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping> -
注解方式:@WebFilter
@WebFilter(servletNames = {"ElServlet"}) public class LogFilter implements Filter {// 代碼略 }
配置參數
只需要配置過濾器在何時起作用即可,可以指定其urlPattern,對滿足某一格式的url進行攔截;也可以指定servletNames,對指定的Servlet進行攔截(二者選其一即可)
-
urlPattern
-
servletNames
如下
@WebFilter(servletNames = {"ElServlet"}) public class LogFilter implements Filter {// 代碼略 } @WebFilter(urlPatterns = "/*") public class LogFilter implements Filter {// 代碼略 }注意
由于攔截某一請求的過濾器,可能有多個,這就形成一條過濾器鏈,當一個過濾器處理完畢后,應該調用鏈上的下一個過濾器,或者直接進入到資源處理(當該過濾器是鏈中的最后一個時)。
所以,在某個過濾器的doFilter()方法中,完成了處理后,應該調用FilterChain的doFilter()方法,將請求/響應在過濾器鏈上傳遞下去(若不調用,則請求不會到達最終的web資源)。在調用FilterChain的doFilter()方法之前,請求還未被處理,在其后,請求已完成處理。這就對應了先前說的,Filter可以在請求被處理的之前,之后,進行一些額外的操作。
@WebFilter(urlPatterns = "/*") public class LogFilter implements Filter {public void destroy() {}public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {System.out.println("before servlet");chain.doFilter(req, resp);System.out.println("after servlet");}public void init(FilterConfig config) throws ServletException {} }應用場景
Filter的常見應用場景列舉如下
-
進行日志記錄(記錄被調用的接口,請求參數,以便進行問題排查,數據統計等工作)
-
統一設置編碼格式(如處理中文亂碼問題等)
-
過濾敏感詞匯
-
數據壓縮
-
數據加密
-
身份認證
監聽器
JavaWeb三大組件的最后一個,Listener。
用于監聽一些重要事件的發生。以便在事件發生時,能夠做一些額外操作。
Servlet API中針對如下對象,提供了對應的監聽器接口
-
ServletContext對象
ServletContextListener,ServletContextAttributeListener
-
HttpSession對象
HttpSessionListener,HttpSessionAttributeListener,HttpSessionIdListener
-
ServletRequest對象
ServletRequestListener,ServletRequestAttributeListener
比如,可以監聽HttpSession對象的創建和銷毀,來實現一個網站在線人數統計的功能(若用戶請求的是一個JSP頁面,JSP頁面在默認情況下,會為每一個新的請求創建一個session對象,這是下面進行人數統計的基礎,這個默認行為,可以通過JSP指令<%@ page session="false" %>來取消)
import javax.servlet.ServletContext; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import java.time.LocalDateTime;/*** 統計網線在線人數* Session創建, 就全局人數+1, Session銷毀, 就全局人數-1* **/ @WebListener // 加上注解后,就不必在web.xml中進行配置 public class OnLineCounter implements HttpSessionListener {private final String COUNT = "count";@Overridepublic void sessionCreated(HttpSessionEvent httpSessionEvent) {HttpSession session = httpSessionEvent.getSession();System.out.println(LocalDateTime.now() + ", 新來了一個人, id = " + session.getId());ServletContext servletContext = session.getServletContext();Object o = servletContext.getAttribute(COUNT);int count = o == null ? 0 : (int) o;servletContext.setAttribute(COUNT, ++count);}@Overridepublic void sessionDestroyed(HttpSessionEvent httpSessionEvent) {HttpSession session = httpSessionEvent.getSession();System.out.println(LocalDateTime.now() + ", 走了一個人, id = " + session.getId());ServletContext servletContext = session.getServletContext();int count = (int) servletContext.getAttribute(COUNT);servletContext.setAttribute(COUNT, --count);} }總結
以上是生活随笔為你收集整理的重学JavaWeb —— Servlet,简单全面一发入魂的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ssh免密码登录全过程
- 下一篇: Java开发常用的在线工具