JavaWeb基礎之Servlet全解析

Servlet 是在服務器上運行的小程序,其主要功能在于交互式地瀏覽和修改數據,生成動態Web內容。狹義的Servlet是指Java語言實現的一個接口,廣義的Servlet是指任何實現了這個Servlet接口的類。

本文內容:


1.Servlet開發步驟

學習Servlet,我們先看一下Servlet程序怎么開發,大體上可以分為五步,如下所示:

1)編寫java類,繼承HttpServlet類,重寫doGet和doPost方法

public class FirstServlet extends HttpServlet{  
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse response)
            throws ServletException, IOException {
        //向瀏覽器輸出內容
        response.getWriter().write("this is first servlet!");
    }
}

2)在web.xml文件中進行配置

<!-- 配置一個servlet -->
  <!-- servlet的配置 -->
  <servlet>
    <!-- servlet的內部名稱,自定義。盡量有意義 -->
    <servlet-name>FirstServlet</servlet-name>
    <!-- servlet的類全名: 包名+簡單類名 -->
    <servlet-class>cn.acamy.FirstServlet</servlet-class>
  </servlet>
  
  
  <!-- servlet的映射配置 -->
  <servlet-mapping>
    <!-- servlet的內部名稱,一定要和上面的內部名稱保持一致!! -->
    <servlet-name>FirstServlet</servlet-name>
    <!-- servlet的映射路徑(訪問servlet的名稱) -->
    <url-pattern>/first</url-pattern>
  </servlet-mapping>

3)部署,servlet程序的class碼拷貝到WEB-INF/classes目錄
4)啟動tomcat服務器
5)通過URL訪問
http://localhost:8080/demo/first

http:// :http協議
localhost: :到本地的hosts文件中查找是否存在該域名對應的IP地址(127.0.0.1)
8080 : 找到tomcat服務器
/demo : 在tomcat的webapps目錄下找 demo的目錄
/first: 資源名稱。
1)在demo的web.xml中查找是否有匹配的url-pattern的內容(/first)
2)如果找到匹配的url-pattern,則使用當前servlet-name的名稱到web.xml文件中查詢是否相同名稱的servlet配置
3)如果找到,則取出對應的servlet配置信息中的servlet-class內容:
字符串: cn.acamy.FirstServlet
通過反射:
a)構造FirstServlet的對象
b)然后調用FirstServlet里面的方法

2.本文涉及到的相關類

2.1 HttpServlet繼承體系

我們寫的Servlet程序都是要直接繼續HttpServlet,由下面的類圖可以看出該類有service方法的重寫和重載,以及doGet,doPost...方法,繼承于抽象類GenericServlet,GenericServlet實現了接口Servlet和ServletConfig,Servlet接口里面定義了init,service,destroy,getServletConfig,getServletInfo方法,ServletConfig里面定義了獲取配置信息的相關方法。

2.2 HttpServletRequest與HttpServletResponse

HttpServletRequest繼承于ServletRequest,封裝了Servlet程序的請求信息,HttpServletResponse繼承于ServletResponse,封裝了Servlet程序的響應信息。

2.3 ServletContext

ServletContext是Servlet上下文對象,封裝了當前的web應用環境。

3. Servlet的生命周期

Servlet程序的生命周期是由tomcat服務器控制的!

我們先來看一個Demo:

public class LifeDemo extends HttpServlet {

    /**
     * 1.構造方法
     */
    public LifeDemo(){
        System.out.println("1.servlet對象被創建了。");
    }

    /**
     * 2.init方法
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("2.init方法被調用");
    }
    
    /**
     * 3.service方法
     */
    @Override           
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        System.out.println("3.service方法被調用");
    }
    
    /**
     * 4.destroy方法
     */
    @Override
    public void destroy() {
        System.out.println("4.servlet對象銷毀了");
    }
}

效果如下:


1. 構造方法: 創建servlet對象的時候調用。默認情況下,第一次訪問servlet的時候創建servlet對象。只調用1次。證明servlet對象在tomcat是單實例的。
2. init方法: 創建完servlet對象的時候調用。只調用1次。
3. service方法: 每次發出請求時調用。調用n次。
4. destroy方法: 銷毀servlet對象的時候調用。停止服務器或者重新部署web應用時銷毀servlet對象。 只調用1次。

Servlet生命周期如下圖所示:

4.HttpServlet的service方法源碼

從上面的生命周期可以看出,service方法在瀏覽器每發送一次請求就被調用一次。但我如果要調用doGet,doPost,doHead等方法時,又該怎么實現呢?先看一下HttpServlet里面與該方法相關的源碼:

    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";

    //該方法是重寫Servlet接口中的方法,可以看到先把ServletRequest和
    //ServletResponse轉化為其子類HttpServletRequest,HttpServletResponse
    //然后調用重載的service(HttpServletRequest req, HttpServletResponse resp)
    @Override
    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);
    }
 
    //注意這個方法是上面service方法的重載,并且是protected面不是public修飾符修
    //飾,說明該方法只能被自身或子類來調用。
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
       
        //首先得到方法名
        String method = req.getMethod();
        
        // 然后看該方法名與實現的哪個方法相對應,就執行哪個方法
        // 如得到的方法名為POST,則對應到METHOD_POST,
        // 執行doPost(req, resp);
        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;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                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);
        }
    }

結論:無論我們需要調用的是GET,POST還是其它方法,我們首先通過調用service(ServletRequest req, ServletResponse res),然后指向service(HttpServletRequest req, HttpServletResponse resp),最終通過方法名來進行匹配,實現轉向,調用想doGET或POST或其它的方法。

5.Servlet的路徑問題與自動加載

5.1 Servlet 的映射路徑

<servlet-mapping>
    <!-- servlet的內部名稱,一定要和上面的內部名稱保持一致!! -->
    <servlet-name>FirstServlet</servlet-name>
    <!-- servlet的映射路徑(訪問servlet的名稱) -->
    <url-pattern>/first</url-pattern>
</servlet-mapping>

Servlet的映射路徑分為精確匹配和模糊匹配

注意:

1)url-pattern要么以 / 開頭,要么以*開頭。  例如, itcast是非法路徑。
2)不能同時使用兩種模糊匹配,例如 /demo/*.do是非法路徑
3)當有輸入的URL有多個servlet同時被匹配的情況下:
3.1 精確匹配優先。(長的最像優先被匹配)
3.2 以后綴名結尾的模糊url-pattern優先級最低!!!

5.2 Servlet 的缺省路徑

servlet的缺省路徑(<url-pattern>/</url-pattern>)是在tomcat服務器內置的一個路徑。該路徑對應的是一個DefaultServlet(缺省Servlet)。這個缺省的Servlet的作用是用于解析web應用的靜態資源文件。

問題: URL輸入http://localhost:8080/demo/index.html 如何讀取文件?
1)到當前demo應用下的web.xml文件查找是否有匹配的url-pattern。
2)如果沒有匹配的url-pattern,則交給tomcat的內置的DefaultServlet處理
3)DefaultServlet程序到demo應用的根目錄下查找是存在一個名稱為index.html的靜態文件。
4)如果找到該文件,則讀取該文件內容,返回給瀏覽器。
5)如果找不到該文件,則返回404錯誤頁面。
結論: 先找動態資源,再找靜態資源。

5.3 Servlet 的自動加載

默認情況下,第一次訪問servlet的時候創建servlet對象。如果servlet的構造方法或init方法中執行了比較多的邏輯代碼,那么導致用戶第一次訪問sevrlet的時候比較慢。
改變servlet創建對象的時機: 提前到加載web應用的時候!
在servlet的配置信息中,加上一個<load-on-startup>即可!

<servlet>
    <servlet-name>LifeDemo</servlet-name>
    <servlet-class>gz.itcast.c_life.LifeDemo</servlet-class>
    <!-- 讓servlet對象自動加載 -->
    <load-on-startup>1</load-on-startup>  注意: 整數值越大,創建優先級越低!!
</servlet>

自動加載效果如下圖所示,可以看到Servlet在Tomcat服務器啟動時就執行了構造方法和init方法:


6. Servlet的有參和無參init方法

有參數的init方法是servlet的生命周期方法,一定會被tomcat服務器調用。如果要編寫初始代碼,不需要覆蓋。無參數的init方法是servlet的編寫初始化代碼的方法。是Sun公司設計出來專門給開發者進行覆蓋,然后在里面編寫servlet的初始邏輯代碼的方法。

public class InitDemo extends HttpServlet {

    /*
     * @Override public void init(ServletConfig config) throws ServletException
     * { System.out.println("有參數的init方法"); }
     */

    @Override
    public void init() throws ServletException {
        // System.out.println("無參數的init方法");
    }

}

7.Servlet的多線程并發問題

servlet對象在tomcat服務器是單實例多線程的。因為servlet是多線程的,所以當多個servlet的線程同時訪問了servlet的共享數據,如成員變量,可能會引發線程安全問題。

解決辦法:
1)把使用到共享數據的代碼塊進行同步(使用synchronized關鍵字進行同步)
2)建議在servlet類中盡量不要使用成員變量。如果確實要使用成員,必須同步。而且盡量縮小同步代碼塊的范圍。(哪里使用到了成員變量,就同步哪里!!),以避免因為同步而導致并發效率降低。

public class TheradDemo extends HttpServlet {
    
    int count = 1;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        
        synchronized (TheradDemo.class) {//鎖對象必須唯一。建議使用類對象
            response.getWriter().write("你現在是當前網站的第"+count+"個訪客");   //線程1執行完  , 線程2執行
        
        //線程1還沒有執行count++
        /*try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
            count++;
        }
    }

}

8.ServletConfig對象

ServletConfig對象: 主要是用于加載servlet的初始化參數。在一個web應用可以存在多個ServletConfig對象(一個Servlet對應一個ServletConfig對象)

8.1 對象創建和得到

創建時機: 在創建完servlet對象之后,在調用init方法之前創建。
得到對象: 直接從有參數的init方法中得到!!!

8.2 servlet的初始化參數配置

 <servlet>
    <servlet-name>ConfigDemo</servlet-name>
    <servlet-class>gz.itcast.f_config.ConfigDemo</servlet-class>
    <!-- 初始參數: 這些參數會在加載web應用的時候,封裝到ServletConfig對象中 -->
    <init-param>
        <param-name>path</param-name>
        <param-value>e:/b.txt</param-value>
    </init-param>
  </servlet>

** Demo:**

public class ConfigDemo extends HttpServlet {
    /**
     * 以下兩段代碼GenericServlet已經寫了,我們無需編寫!!
     */
    /*private ServletConfig config;*/
    
    /**
     *  1)tomcat服務器把這些參數會在加載web應用的時候,封裝到ServletConfig對象中 
     *  2)tomcat服務器調用init方法傳入ServletConfig對象
     */
    /*@Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }*/
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * 讀取servlet的初始參數
         */
        String path = this.getServletConfig().getInitParameter("path");

        File file = new File(path);
        //讀取內容
        BufferedReader br = new BufferedReader(new FileReader(file));
        String str = null;
        while( (str=br.readLine())!=null ){
            System.out.println(str);
        }
        
        //查詢當前servlet的所有初始化參數
        Enumeration<String> enums = this.getServletConfig().getInitParameterNames();
        while(enums.hasMoreElements()){
            String paramName = enums.nextElement();
            String paramValue = this.getServletConfig().getInitParameter(paramName);
            System.out.println(paramName+"="+paramValue);
        }
        
        //得到servlet的名稱
        String servletName = this.getServletConfig().getServletName();
        System.out.println(servletName);
    }

}

9.ServletContext對象

Servlet的上下文對象。表示一個當前的web應用環境。一個web應用中只有一個ServletContext對象。

9.1 對象創建和得到

創建時機:加載web應用時創建ServletContext對象。
得到對象: 從當前Servlet或者ServletConfig對象的getServletContext方法得到

** Demo:**

public class ContextDemo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 1.得到ServletContext對象
        // ServletContext context = this.getServletConfig().getServletContext();
        ServletContext context = this.getServletContext(); // (推薦使用)

        // 2.得到web應用路徑 
        /**
         * web應用路徑:部署到tomcat服務器上運行的web應用名稱
         */
        String contextPath = context.getContextPath();

        System.out.println(contextPath);

        System.out.println("參數" + context.getInitParameter("AAA"));

        Enumeration<String> enums = context.getInitParameterNames();
        while (enums.hasMoreElements()) {
            String paramName = enums.nextElement();
            String paramValue = context.getInitParameter(paramName);
            System.out.println(paramName + "=" + paramValue);
        }

        // 嘗試得到ConfigDemo中的servlet參數
        String path = this.getServletConfig().getInitParameter("path");
        System.out.println("path=" + path);

        // 2.把數據保存到域對象中
        context.setAttribute("name", "eric");

        // 2.從域對象中取出數據
        // String name = (String) context.getAttribute("name");

        /**
         * 案例:應用到請求重定向
         */
        response.sendRedirect(contextPath + "/index.html");
    }

}

9.2 轉發與重定向

轉發與重定向都能實現頁面的跳轉,但在實際應用中大不相同,比較如下;

注意: 如果要使用request域對象進行數據共享,只能用轉發技術!!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容

  • 本文包括: Servlet簡介關于Servlet的一些類 Servlet生命周期 ServletConfig獲得初...
    廖少少閱讀 3,889評論 1 67
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,330評論 11 349
  • 0 系列目錄# WEB請求處理 WEB請求處理一:瀏覽器請求發起處理 WEB請求處理二:Nginx請求反向代理 本...
    七寸知架構閱讀 14,014評論 22 190
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,729評論 18 399
  • Servlet學習的大綱 servlet概念及相關接口簡介 servet 執行過程 servlet映射路徑 缺省s...
    奮斗的老王閱讀 1,211評論 1 51