JavaWeb三大組件之Servlet學習

JavaWeb三大組件之Servlet學習

平時直接用springmvc較多,都沒怎麼接觸底層的Servlet,致使對一些基本的知識點了解都不夠,因此今天專門的抽出時間來學習一下html

帶着問題出發,看下能夠怎麼玩java

  • 如何自定義一個Servlet
  • 自定義的Serlvet如何工做
  • servlet的優先順序怎麼斷定
  • servlet匹配是怎樣的 (url-mapping...)
  • 如何獲取參數(get請求參數,post請求參數,上傳文件)
  • 如何返回數據(返回頁面,返回文件,返回二進制)
  • 請求頭和返回頭的設置

I. 基本知識點

1. 什麼是Servlet

Servlet是JavaWeb的三大組件之一,它屬於動態資源。Servlet的做用是處理請求,服務器會把接收到的請求交給Servlet來處理,在Servlet中一般須要:web

  • 接受請求
  • 處理請求
  • 完成響應

2. 怎麼玩Servlet

通常來說,建立一個自定義的Servlet有兩個步驟,在web.xml中配置serverlt的聲明;實現Servlet接口,實現自定義的Servlet邏輯spring

一個簡單的case以下apache

web.xml中,添加配置跨域

<servlet>
    <servlet-name>doc-servlet</servlet-name>
    <servlet-class>com.yihui.study.DocServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>doc-servlet</servlet-name>
    <url-pattern>/study/*</url-pattern>
</servlet-mapping>

實現自定義Servlet緩存

public class DocServlet extends HttpServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        PrintWriter writer = resp.getWriter();
        writer.append("這是一個自定義servlet")
                .append("emoj😄==").flush();
        System.out.println("hereher!!!!");
    }
}

上面這個Servlet,實現了攔截 /study 下的全部請求, 而後返回一段文本,上面做爲演示,具體的展開下面說明服務器

3. Servlet接口說明

上面是直接繼承了HttpServlet,可能無法徹底的暴露一個Servlet的具體接口有哪些,以及它的生命週期是怎樣的,接下來則直接針對源頭進行說明cookie

public interface Servlet {
    // 初始化
    public void init(ServletConfig config) throws ServletException;
    
    // 獲取配置信息
    public ServletConfig getServletConfig();
    
    // 處理請求
    public void service(ServletRequest req, ServletResponse res)
	throws ServletException, IOException;
	
	  // Returns information about the servlet, such as author, version, and copyright
    public String getServletInfo();
    
    // 銷燬
    public void destroy();
}

五個方法,從命名也能夠看出對應的生命週期mvc

  • 首先是建立: init() 方法被建立
  • 建立完畢以後,請求來了,分給 service方法,執行對應的業務邏輯
  • 最後不想玩了,就銷燬掉,此時觸發 destroy方法

說明

在Servlet被建立後,服務器會立刻調用Servlet的void init(ServletConfig)方法。請記住, Servlet出生後立刻就會調用init()方法,咱們能夠把一些對Servlet的初始化工做放到init方法中,從此全部分配到這個Servlet的請求,都是公用這個Servlet的

4. ServletConfig

init()方法的參數

ServletConfig對象對應web.xml文件中的元素。例如你想獲取當前Servlet在web.xml文件中的配置名,那麼可使用servletConfig.getServletName()方法獲取

String getServletName():獲取Servlet在web.xml文件中的配置名稱,即指定的名稱; 
ServletContext getServletContext():用來獲取ServletContext對象,ServletContext會在後面講解; 
String getInitParameter(String name):用來獲取在web.xml中配置的初始化參數,經過參數名來獲取參數值; 
Enumeration getInitParameterNames():用來獲取在web.xml中配置的全部初始化參數名稱;

5. ServletRequest

請求對象,能夠從其中獲取請求數據,請求頭等

內部提供的方法挺多,一般咱們最關心的有:

  • 獲取參數: javax.servlet.ServletRequest#getParameter
  • 獲取頭 : javax.servlet.http.HttpServletRequest#getHeader
  • 獲取cookie: javax.servlet.http.HttpServletRequest#getCookies
  • 獲取請求 : javax.servlet.http.HttpServletRequest#getRequestURI
  • ...

還有一個比較重要的就是指定字符編碼,如咱們一般要求提交的參數知足utf8編碼,這時就能夠以下設置

// javax.servlet.ServletRequest#setCharacterEncoding
request.setCharacterEncoding("utf-8");

如咱們最經常使用的一個spring的fitler,關鍵代碼以下

// org.springframework.web.filter.CharacterEncodingFilter#doFilterInternal
// 
@Override
protected void doFilterInternal(
		HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {

	if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
		request.setCharacterEncoding(this.encoding);
		if (this.forceEncoding) {
			response.setCharacterEncoding(this.encoding);
		}
	}
	filterChain.doFilter(request, response);
}

6. ServletResponse

返回對象,返回響應給調用方的結構,設置返回頭

返回數據給調用方,主要就是利用這個東西了,內部提供的方法也很多,咱們主要關心的其實也並不太多

  • 設置返回頭:javax.servlet.http.HttpServletResponse#setHeader
  • 添加cookie: javax.servlet.http.HttpServletResponse#addCookie
  • 重定向 : javax.servlet.http.HttpServletResponse#sendRedirect
  • 異常 : javax.servlet.http.HttpServletResponse#sendError
  • 設置ContentType: javax.servlet.ServletResponse#setContentType
  • 設置返回流: javax.servlet.ServletResponse#getOutputStream, javax.servlet.ServletResponse#getWriter
  • 設置編碼: javax.servlet.ServletResponse#setCharacterEncoding

II. 進階

1. web.xml中配置

這個配置,主要針對 Servlet 的順序指定,URL匹配這兩個問題,因此有必要研究下這個配置中的說明

一般web.xml的配置,下面兩個是必須的

<!-- servlet的配置 -->
<servlet>
    <!-- servlet的內部名稱,自定義。儘可能有意義 -->
    <servlet-name>ServletDemo</servlet-name>
    <!-- servlet的類全名: 包名+簡單類名 -->
    <servlet-class>com.xxx.ServletDemo</servlet-class>
</servlet>
<!-- servlet的映射配置 -->
<servlet-mapping>
    <!-- servlet的內部名稱,必定要和上面的內部名稱保持一致!! -->
    <servlet-name>ServletDemo</servlet-name>
    <!-- servlet的映射路徑(訪問servlet的名稱) -->
    <url-pattern>/servlet</url-pattern>
</servlet-mapping>

其中 servlet-mapping 指定映射的路徑,知足條件的會匹配對應的Servlet,匹配規則有如下幾個定義

既然這個url匹配支持模糊匹配,那麼問題來了,若是兩個servlet都匹配了這個path路徑,那麼究竟是哪一個處理呢?

注意到前面有個配置參數:load-on-startup

  • 當值爲0或者大於0時,表示容器在應用啓動時就加載這個servlet;
  • 當是一個負數時或者沒有指定時,則指示容器在該servlet被選擇時才加載
  • 正數的值越小,啓動該servlet的優先級越高

注意 這個參數是加載順序,而不是最終的匹配順序

那麼匹配順序的優先級是:

  • 精確路徑匹配
    • 好比servletA 的url-pattern爲 /test,servletB的url-pattern爲 /* ,這個時候,若是我訪問的url爲http://localhost/test ,這個時候容器就會先 進行精確路徑匹配,發現/test正好被servletA精確匹配,那麼就去調用servletA,也不會去理會其餘的servlet了
  • 最長路徑匹配
  • 擴展匹配,若是url最後一段包含擴展,容器將會根據擴展選擇合適的servlet
  • 若是前面三條規則都沒有找到一個servlet,容器會根據url選擇對應的請求資源,即匹配defaultServlet

2. 參數獲取

參數獲取,則主要區分get請求參數,post提交表單,上傳的文件了

a. 經過 getQueryString

這種獲取參數的方式,只能獲取url上面的參數,沒法獲取到post的表單內容

String str = req.getQueryString();

b. 經過 getParameter

// 返回全部的請求參數
javax.servlet.ServletRequest#getParameterMap

這種使用姿式,和咱們在SpringMVC中常見的基本一致

c. 經過 getInputStream

獲取請求流,通常的使用姿式以下

InputStream stream = req.getInputStream();
byte[] bytes = new byte[stream.available()];
stream.read(bytes);
String str = new String(bytes);

而後就須要本身對上面的請求參數進行處理了;兩廂對比,常規的獲取方法,直接使用 getParameter方式更加優雅

注意

經過getInputStream方式獲取了請求數據以後,再經過 getParameter獲取不到參數的,也好理解,請求的流,被你讀取以後,其餘的地方就沒法獲取流中的數據了

d. 獲取上傳的文件

從請求參數中獲取上傳的文件,網上隨意搜索了一下,發現大部分都使用apache的fileupload包, 其實處理的依然是inputstream這個請求流,只是邏輯比較複雜,粗略的翻看了一下源碼,發現這一塊還挺有意思的,準備單獨的深刻看一下

3. 數據返回

返回數據,前面介紹HttpServletResponse的時候,就給出了兩個方法

a. getWriter

public PrintWriter getWriter() throws IOException;

b. getOutputStream

public ServletOutputStream getOutputStream() throws IOException;

下面簡單說一下上面的區別

PrintWriter ServletOutputStream
字符流返回 字節流返回
須要字符編碼 字節流直接返回(返回文件就很佔優點了)

說明

  1. 上面兩種方式互斥,只能使用其中一種case
  2. Servlet程序向ServletOutputStream或PrintWriter對象中寫入的數據將被Servlet引擎獲取,Servlet引擎將這些數據看成響應消息的正文,而後再與響應狀態行和各響應頭組合後輸出到客戶端
  3. Serlvet的service方法結束後,Servlet引擎將檢查getWriter或getOutputStream方法返回的輸出流對象是否已經調用過close方法,若是沒有,Servlet引擎將調用close方法關閉該輸出流對象

4. 返回頭設置

常見的請求頭和返回頭設置,對於servlet而言也是比較常見的,通常常見的幾個設置

  • 是否緩存,緩存時間
  • 設置cookie
  • 設置 corss-origin 相關,以支持跨域
  • 設置 content-type ...

而實際的使用也比較簡單了,以下便可

# javax.servlet.http.HttpServletResponse#addHeader
response.addHeader("Content-Type", "text/html; charset=UTF-8");

III. 實例測試

建立一個自定義的嗯Servlet,而後攔截全部 /study 下面的請求

public class DocServlet extends HttpServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        PrintWriter writer = resp.getWriter();
        writer.append("這是一個自定義servlet")
                .append("emoj😄==").flush();
        System.out.println("hereher!!!!");
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException {
        Map map = req.getParameterMap();
        System.out.println("arg: " + map);
        res.getWriter().append("success").flush();
    }
}

對應的xml配置以下

<servlet>
    <servlet-name>doc-servlet</servlet-name>
    <servlet-class>com.yihui.study.DocServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>doc-servlet</servlet-name>
    <url-pattern>/study/*</url-pattern>
</servlet-mapping>

實測演示:

show

IV. 其餘

參考

  1. servlet詳解(第一篇)

聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,看法不全,若有問題,歡迎批評指正

掃描關注,java分享

QrCode

相關文章
相關標籤/搜索