web開發之Servlet 二

在上一篇文章中,咱們演示也證實了Servlet 是一種動態web資源開發的技術,即我能夠在瀏覽器中輸入URL,而後就能夠在瀏覽器中看到咱們編寫的Servlet資源。java

那當咱們在瀏覽器上一塊兒一個HTTP請求以後,具體的流程是怎麼樣的呢?借用LinkinStar博文中的圖:web

上面這副圖講解了整個HTTP請求到相應的過程,在這裏進行下解釋:瀏覽器

當在瀏覽器中輸入URL後,會經過hosts文件/dns服務器解析爲IP地址,進而找對應的服務器來提供服務,當服務器端收到請求後:tomcat

1)分析當前的這個請求訪問的是當前服務器中的那個WEB應用安全

這個能夠從請求行中的請求資源部分來分析出來。服務器

2)分析當前請求要訪問的這個WEB應用的那個資源:多線程

從上述請求行的資源部分能夠分析出請求的是什麼資源併發

3)查找web.xml文件,查看有沒有對應的虛擬路徑,若是有則用這個虛擬路徑對應的資源作出相應。app

4)服務器從response對象中獲取以前寫入的數據,而後組成響應發送到瀏覽器中。less

一個HTTP響應表明服務器向客戶端回送的數據,它包括:一個狀態行、若干消息頭、以及實體內容。

2、Servlet的運行過程及生命週期

Servlet程序由WEB服務器調用,web服務器收到客戶端的Servlet請求後,會首先檢查是否已經裝載並建立了該Servlet的實例對象;若是是,則直接跳到第四步,不然,執行第二步。

1)裝載並建立該Servlet實例對象【加載:容器經過類家長羣使用Servlet類對應的文件加載Servlet,建立:經過調用servlet構造函數建立一個Servlet對象】

2)調用Servlet實例對象的init方法

3)處理客戶請求:每當有一個客戶請求,容器會建立一個線程來處理客戶請求,用於封裝HTTP請求消息的HttpServletRequest對象和一個表明HTTP響應消息的HttpServletRespons對象,而後調用Servlet的service方法並將請求和響應對象做爲參數傳遞進去。

4)卸載:調用destroy方法讓servlet本身釋放其佔用的資源。

所以Servlet的生命週期能夠分爲5個階段:加載、建立、初始化、處理客戶請求、卸載。

(1)一般狀況下,服務器會再Servlet第一次調用時候加載並建立此Servlet的實例對象,同時調用init方法作初始化操做。

(2)一旦此Servlet實例建立出來後,該實例就駐留在內存中,爲後續對這個Servlet的請求作出相應的服務,每次對這個Servlet的訪問都會致使Servlet中service方法執行;

(3)當web應用被移除容器或者關閉服務器的時候,隨着web應用的銷燬,Servlet也會被銷燬,在銷燬以前服務器會調用Servlet的destroy方法作一些善後的工做。

下面3個方法能夠基本表明Servlet的生命週期:

*init方法,負責初始化Servlet對象

*service方法,負責響應客戶的請求(調用doGET 或 doPost 方法)

*destory方法,當Servlet對象退出生命週期的時候,負責釋放佔用的資源。

PS:在Servlet的整個生命週期內,Servlet的init方法只有在Servlet被建立的時候被調用一次,每次對這個Servlet的訪問都會致使Servlet中的service方法執行。

例如:如今瀏覽器連續訪問Servlet 5次,內存中只有一個Sevlet對象。Servlet對象由服務器建立(建立一次),request和response由Servlet容器建立(建立5次),會調用5次service方法。

發送一次請求:http://localhost:8000/Servlet_Demo01/servlet/ServletDemo01

咱們看下控制檯的輸出:

可見,正常Servlet是在被第一次訪問的時候加載並建立,同時開始調用service方法。

那咱們再發送5次請求看下結果如何:

可見沒有打印Servlet 初始化,但發送了請求,可見service方法就被調用了,緊接着咱們中止容器看下,是否會調用destory方法。

可見當中止web服務器的時候,servlet被銷燬了。

總結:能夠看到,Servlet只會初始化一次,以後的話,咱們屢次訪問的是同一個Servlet對象,此時,即便關掉網頁,Servlet也不會銷燬,只有tomcat服務器纔會銷燬servlet.

那麼咱們有沒有辦法,啓動web服務器的時候同時加載並建立servlet呢?方法是有的,須要在web.xml中配置:<load-on-startup>1</load-on-startup>

 <servlet>
    <description>This is the description of my J2EE component</description>
    <display-name>This is the display name of my J2EE component</display-name>
    <servlet-name>ServletDemo01</servlet-name>
    <servlet-class>mdj.servlet.study.ServletDemo01</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>ServletDemo01</servlet-name>
    <url-pattern>/servlet/ServletDemo01</url-pattern>
  </servlet-mapping>

而後咱們看下現象:

可見啓動的時候,就進行了建立和加載,那麼咱們再次訪問URL請求的時候,是否還會建立呢,答案應該是不會,看下面結果。

3、Servlet的結構

上面咱們講解了Servlet的運行過程和生命週期,下面咱們看看其結構:

1)Servlet接口有五個方法

init初始化,就是把servlet裝載到內存中,只會被調用一次

getServletConfig獲取servletConfig對象

service主要的服務方法,放業務邏輯,每次都會被調用

getServletInfo獲得servlet配置信息

destroy銷燬該servlet,從內存中清除掉

2)繼承GenericServlet

GenericServlet實現了servlet接口

而後只有一個抽象方法須要你本身去重寫

那就是service方法,因此相比來講init別的方法他都幫你實現好了,只要你寫service方法就能夠了。

至少看起來繼承GenericServlet比直接實現servlet接口要方便

3)繼承HttpServlet

由於後來發現servlet主要是爲了服務於http請求的,並且發現GenericServlet對於http來講還不夠好

因此有了HttpServlet,首先它是繼承自GenericServlet

而後它有不少http相關的方法,post,get,put等待

用戶能夠根據本身須要來實現這些方法

每一個過來的請求都會調用service方法,最後service會根據不用的請求分發到不一樣的地方去作。

相比較而言,HttpServlet覆寫了GenericServlet,service方法體內的代碼會自動判斷用戶的請求方式,如爲GET的請求則調用doGet方法,如爲Post請求,則調用doPost方法,所以開發人員在編寫servlet的時候,一般指須要繼承HttpServlet,而後覆寫doGet和doPost方法,不要去覆寫service方法。

咱們來看一下源碼:

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
    {
    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
        // servlet doesn't support if-modified-since, no reason
        // to go through further expensive logic
        doGet(req, resp);
        } else {
        long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
        if (ifModifiedSince < (lastModified / 1000 * 1000)) {
            // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
            maybeSetLastModified(resp, lastModified);
            doGet(req, resp);
        } else {
            resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        }
        }

    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);    
        
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
        
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
        
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
        
    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
    }

這個就是HttpServlet中service方法中的源碼;

首先咱們看到的就是String method = req.getMethod();這個就是經過request獲取方法,而後根據方法判斷調用哪個方法,要說明的是前面它已經定義好了這些字符串

  private static final String METHOD_DELETE = "DELETE"; 
   private static final String METHOD_HEAD = "HEAD"; 
   private static final String METHOD_GET = "GET"; 
   private static final String METHOD_OPTIONS = "OPTIONS"; 
   private static final String METHOD_POST = "POST"; 
   private static final String METHOD_PUT = "PUT"; 
   private static final String METHOD_TRACE = "TRACE"; 
須要指出的是,前面說過 HttpServlet是繼承自GenericServlet,而GenericServlet須要用戶實現一個service,剛纔咱們看到的是HttpServlet本身的。

在GenericServlet中也有一個service方法:

public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException
    {
    HttpServletRequest    request;
    HttpServletResponse    response;
    
    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException("non-HTTP request or response");
    }
    service(request, response);
    }

源碼是這樣的,它會把原來的ServletRequest 請求直接強轉換成HttpServletRequest而後再去調用它正真的service方法。

這點是須要指出的,HttpServletRequest比ServletRequest進行了進一步的封裝,方法更適合http。

4、Servlet映射匹配問題

因爲客戶端是經過URL的地址找到對應web服務器中的資源,因此Servlet程序若是想要被外界訪問,那麼必須把servlet程序映射到一個URL地址上,這個工做在web.xml文件中<servlet>和<servlet-mapping>元素組成。

<servlet>元素用於註冊Servlet,它包含有兩個主要的子元素:<servlet-name>和<servlet-class>,分別用於設置Servlet的註冊名稱和Servlet的完整類名。

一個<servlet-mapping>元素用於映射一個已註冊的Servlet的一個對外訪問路徑,它包含有兩個子元素:<servlet-name>和<url-pattern>,分別用於指定Servlet的註冊名稱和Servlet的對外訪問路徑。

須要注意的是:

1.一個servlet能夠對應多個servlet-mapping,從而一個servlet能夠有多個路徑訪問。

2.url-pattern中的路徑也可使用*通配符,可是隻有兩種固定的格式:一種格式「*.擴展名」,另外一種是正斜槓/ 開頭並以/* 結尾。

一般匹配的優先級是哪一個精確找哪一個,*.後綴的格式永遠匹配級最低。

5、線程安全問題

Servlet引擎採用多線程模式運行,它爲併發的每一個訪問請求都使用一個獨立的線程來進行響應。

可是因爲默認狀況下Servlet在內存中只有實例存在,所以當多個瀏覽器併發訪問Servlet的時候,就會有可能產生線程的安全問題。

Servlet線程不安全,至始至終,之維護一個實例對象,當同一個資源被多個線程同時訪問操做的時候,就可能會互相干擾。

解決的方法是:

一、SingleThreadModel接口(標記接口,單線程模型接口):不能真的防止線程安全問題(已過期)

Servlet實現了SingleThreadModel接口,那麼Servlet引擎將以單線程模式來調用Servlet的service方法。對於實現了SingleThreadModel接口的Servlet,Servlet引擎仍然支持對該Servlet的多線程併發訪問,其採用的方式是產生多個Servlet實例對象,併發的每一個線程分別調用獨立的一個Servlet實例對象

注:此接口在API 2.4中就已通過時,雖然解決了線程安全問題,可是消耗了大量性能(不一樣的客戶端同時訪問會建立不一樣的Servlet實例),因此此方法建議不用,實際開發中也不用。

二、使用同步代碼塊,但效率低。在Servlet中儘可能少用類變量(成員變量),若是必定要用類變量則用鎖來防止線程安全問題,可是要注意鎖住內容應該是形成線程安全問題的核心代碼,儘可能的少鎖主內容,減小等待時間提升servlet的響應速度。

 


幾個小題目:

下面有關servlet service描述錯誤的是?

一、不論是post仍是get方法提交過來的鏈接,都會在service中處理

    答:每次請求都會調用service方法,最終都會在service中處理,正確;

二、doGet/doPost 則是在 javax.servlet.GenericServlet 中實現的

     答:GenericServlet只是繼承了Servlet的接口,實現了它其中的5個方法,其中須要用戶重寫的是service方法,而doGet/doPost是由於以後出現了HttpServlet纔有的,是針對http請求才有了這個類,纔有了doPost和doGet,因此是錯誤的。


三、service()是在javax.servlet.Servlet接口中定義的

     答:Servlet接口一共定義了5個方法,其中就有service(),正確;


四、service判斷請求類型,決定是調用doGet仍是doPost方法

      答:正確,緣由見上面。

 

今天就複習到此,下一節講解Servlet中比較經常使用到的ServletConfig 和ServletContext 對象。

相關文章
相關標籤/搜索