JavaWeb—Servlet基礎(細節版,相當細節)

冰洋發表於2017-12-11

by:lubingyang

什麼是Servlet?

Servlet就是一個普通的類,只不過這個類能夠接受和處理請求,並且做出響應。提到Servlet就繞不開Servlet容器,那麼什麼又是Servlet容器呢?通俗的講就是實現Servlet標準管理輔助Servlet類工作的工具。Servlet和Servlet容器在我看來就是子彈和槍的關係,通過對標準化介面的實現互相配合,彼此依存又獨立發展。在大部分的情況下我們又稱Servlet容器為伺服器,常用的有Tomcat等。

一個HTTP來了又走經歷了什麼?

HTTP(超文字傳輸協議):是網際網路通訊的基礎,屬於 TCP/IP 模型中的應用層協議,當瀏覽器與伺服器進行互相通訊時,需要先建立TCP 連線,之後伺服器才會接收瀏覽器的請求資訊(request),當接收到資訊之後,伺服器返回相應的資訊(response)。最後瀏覽器接受對伺服器的資訊應答後,對這些資料進行解釋執行(解析HTML檔案和各種資源進行展示)。

HTTP是一個無狀態的連線協議。

所謂無狀態:

根據早期的HTTP協議,每次request-reponse時,都要重新建立TCP連線。TCP連線每次都重新建立,所以伺服器無法知道上次請求和本次請求是否來自於同一個客戶端。因此,HTTP通訊是無狀態(stateless)的。伺服器認為每次請求都是一個全新的請求,無論該請求是否來自同一地址。現在,雖然HTTP協議允許TCP連線複用,以節省建立連線所耗費的時間,但無狀態的特性依舊被保留。

準備階段

為了迎接HTTP的到來,首先我們需要有一個Servlet類,並且告訴Servlet容器自己的存在,這兩個準備步驟就是建立Servlet類和寫入配置檔案

正如上文所講,類和Servlet容器之間的配合是通過介面實現的,一個類只需要實現特定的介面,就可以稱為一個Servlet類,並且能夠被Servlet所接受,嗯,想來這就是介面的解耦和。

準備一個Servlet類

擁有一個Servlet類的三種方案

  1. 直接實現Servlet介面(interface)
  2. 繼承GenericServlet類(abstract)
  3. 繼承HttpServlet類(abstract)

在直接實現或者間接實現Servlet介面之後我們需要重寫其中的service方法,到此Servlet就準備好了。

寫入配置檔案

配置檔案是一個固定的寫法,主要就是為了告訴Servlet容器自己在哪

<servlet>
	<servlet-name>自定義Servlet的別名</servlet-name>
  	<servlet-class>Servlet類的全類名</servlet-class>
</servlet>
<servlet-mapping>
	<servelt-name>自定義Servlet的別名</servelt-name>
  	<url-pattern>自定義路徑</url-pattern>
</servlet-mapping>
複製程式碼

接受請求

Servlet容器開啟服務之後就可以迎接request的到來了,當這個HTTP請求到達Servlet容器(以Tomcat為例)的時候,Tomcat看到有HTTP來,就把它帶到要去的那個地方(專案名),到了地點之後,Tomcat會拿出花名冊(web.xml)讓request挑一個(0.0)。

結果,不用挑有指定的,那就好辦了。

Tomcat在部署檔案中找 servlet-mapping 中與之匹配的 url-pattern,根據這個 url-pattern 的 servlet-name 對映到真正的 servlet-class ,然後呼叫相應的 Servlet 類。

擴充套件內容:xml

xml全稱是Extensible Markup Language可擴充套件標記語言,看到這個難免會想起來HTML,他們有什麼關係呢?為什麼有了HTML語言還要xml語言呢?

認真的講他們最大的關係就是都以ml結尾了,哈哈,開個玩笑。

HTML我們是很熟悉的,在使用的時候不難發現其中的標籤都是定義好的,全世界的HTML文件用的是同一套標記的語法。

而xml更具有個性化,其中的標籤不僅可以用別人的定義好的,也可以自己定義。書寫一個xml有兩個相關的規則,一個是標籤規則,另一個就是校檢規則,校檢規則是用來告訴程式標籤之間的規則,這個東西被稱之為文件型別定義  Document Type Definition , 簡稱 DTD 這兩個規則都是可以自定義的(所謂擴充套件),所以我們在書寫不同的xml檔案的時候,會發現標籤規則是不一樣的。

從這些層面上來說HTML語言也可以說是xml中的一種,HTML5就是最新的校檢規則(不知道這麼理解有沒有問題???)

<!-- 我們不難發現很多xml文件的文件宣告中都有宣告其文件符合的校檢規則 -->
<!-- web.xml -->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
XML Schema是dtd的改進版
<!-- mybatis-config.xml -->
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >
複製程式碼

生命週期

通過以上的步驟Tomcat就找到了HTTP想要見到的那個Servlet了,但是這個類也許準備好了,也許沒有,我們假定這個request是第一次來。這時候就開始了Servlet的生命週期了。

  1. 因為是第一次請求,Tomcat會呼叫Servlet類的無參構造方法,建立這個Servlet的物件。

  2. 之後初始化,會呼叫init方法,這個方法會對Servlet類做一些初始化的工作,需要注意的這個方法在Servlet的一生中只會執行這麼一次。像初始化這麼重要的事兒只進行一次是有現實意義的,畢竟如果可以多次的話,我早就一米八了。

  3. 初始化之後一個Servlet就正式的進入服務狀態可以接客了,這時候就會呼叫service方法,接受HTTP的request,並對這個請求做一些服務專案,剪個頭髮之類啊,最後再把面目全非的請求送走,不,這時候應該叫響應response。聽說每次剪頭髮都像整容,可惜好久沒有剪過頭髮了。

    經過第一個請求之後,再有HTTP過來的時候,Servlet會直接呼叫service方法為其服務,畢竟誰一輩子也不能接一個客戶初始化一次吧。

  4. 最後當服務關閉的時候,會銷燬這個物件,在銷燬前會呼叫destroy方法。

其他細節

會話跟蹤技術

寫到會話跟蹤要先從HTTP開始說起,在之前我們說過HTTP是無狀態的。因為其無狀態的特性,伺服器不能以狀態來區分和管理請求和響應,而且一次請求響應之後就會斷開連線,所以伺服器也不需要儲存狀態資訊,雖然這樣簡單不佔資源,適用性廣,但是不利之處在於我們沒有辦法根據HTTP本身對請求做一些區分。

所以為了在保留無狀態協議這個特徵的同時又解決類似記錄狀態的矛盾問題,出現了Cookie。

Cookie

cookie1.png

cookie2.png

從上圖我們知道,有幾個關鍵性的步驟是需要我們來做的:

  1. 建立Cookie

    //引數是cookie的標記和值,必須是英文
    Cookie cookie = new Cookie(flag,value);
    複製程式碼
  2. 響應資訊中加上Cookie

    response.addCookie(cookie);
    複製程式碼
  3. 再次請求到來的時候檢查Cookie

    //獲取request中所有的cookie資訊
    Cookies[] cookies = request.getCookies();
    //遍歷檢查cookie
    if(cookies != null){
      for(Cookie c : cookies){
      	String name = c.getName();
        String value = c.getValue();
    	}
    }
    複製程式碼

  1. cookie是有有效期的,一般會在瀏覽器關閉的時候自動清空

    設定cookie有效期,呼叫方法setMaxAge(60)

  2. cookie中的資料是不安全的,畢竟儲存到本地,可以顯式檢視

Session

session是伺服器為每一個瀏覽器建立的私人儲存空間,其中(session作用域)可以儲存瀏覽器的屬性和一些配置資訊,當瀏覽器拿到session之後(沒有更換持有的session),在不同的Servlet之間跳轉的時候,可以隨時取出放在作用域中的資料。

session.png

上圖是session的基本實現原理,我們可以看到session是通過cookie來實現的,具體的步驟是這樣的:

  1. 瀏覽器把登入資訊放入HTTP請求報文的實體部分,通常是以POST 方法把請求傳送給伺服器

  2. 伺服器建立session,並將使用者資訊和session進行繫結記錄在伺服器,然後把處理好的session放入cookie中隨著響應發給瀏覽器。

  3. 瀏覽器收到伺服器響應的帶有session資訊的cookie時,會將cookie存在本地,下次請求的時候自動攜帶,伺服器會通過接受其中的session對使用者進行驗證。

GET和POST的區別

從本質上來講,區別只有兩點:

  1. 引數的位置不同,GET是url後拼接引數,POST是請求報文主體傳遞引數
  2. 傳遞資料的過程不一樣,GET產生一個TCP資料包,POST產生兩個TCP資料包。

那麼我們常提到的區別是從哪裡來的呢?

從解釋上面的區別開始。

早期的HTTP協議只有GET方法。根據HTTP協議,伺服器接收到GET請求後,會將特定資源響應給瀏覽器,GET方法是通過改寫URL實現的,在URL後面加上要傳遞的資料(格式是URL?key=value&key=value......)。所以在使用GET方法請求資源的時候,請求往往是沒有主體的。

那麼問題來了,什麼是主體?

所謂主體就是request的報文主體,我們知道HTTP請求的報文格式(響應雷同)是這樣的

l  起始行

l  頭資訊

l  主體

GET方法的請求報文資訊一般只有起始行和頭資訊,如下:

<!-- 拼接後的url -->
http://localhost:8801/zhibaxm/servlet/LoginAction?username=%E5%BE%90%E9%80%9A%E8%BE%BE&pwd=123
<!-- 請求頭 -->
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Host:localhost:8801
Origin:http://localhost:8801
Referer:http://localhost:8801/zhibaxm/login.html
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36
複製程式碼

而對於POST方法,URL不再被改寫,相關的表單資料會位於http請求的主體。如下:

<!-- url -->
http://localhost:8801/zhibaxm/servlet/LoginAction
<!-- 請求頭 -->
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Host:localhost:8801
Origin:http://localhost:8801
Referer:http://localhost:8801/zhibaxm/login.html
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36
<!-- POST的主體資訊 -->
username=%E5%BE%90%E9%80%9A%E8%BE%BE&pwd=123
複製程式碼

我們知道每一個請求都是可以有三部分的:起始行,請求頭,主體,也就是說,GET和POST的區別不是語法上的,而是規範上的,簡單的說就是,你在使用POST的時候如果把引數寫在url上也是沒有問題的。

但是,我們在使用中確實有很多的不同,咋回事兒呢?這些區別並不是語法本身的不同,而是由於瀏覽器和伺服器差異造成的使用上的區別,例如:大部分瀏覽器的url長度限制在2K個位元組,而大部分伺服器最多處理64K大小的url。在使用GET方法時,如果你在報文主體寫入了資料,那麼不同伺服器的處理方式也是不同的,有些伺服器會接受有些不會。

造成區別的原因更多不是來自語法本身,而是不同瀏覽器伺服器的限制。

扯完了這些,補充一下應用上區別,畢竟遇到面試的時候,使用上的差別也是不能夠忘記的,使用上的區別如下:

GET POST
只可以接受字串 沒有限制
不安全 相對安全
有長度限制 沒有長度限制
... ...

相關文章