Servlet

1、Servlet簡介

  Servlet是sun公司提供的一門用於開發動態web資源的技術。
  Sun公司在其API中提供了一個servlet接口,用戶若想用發一個動態web資源(即開發一個Java程序向瀏覽器輸出數據),須要完成如下2個步驟:
  一、編寫一個Java類,實現servlet接口。
  二、把開發好的Java類部署到web服務器中。
  按照一種約定俗成的稱呼習慣,一般咱們也把實現了servlet接口的java程序,稱之爲Servlethtml

2、Servlet的運行過程

Servlet程序是由WEB服務器調用,web服務器收到客戶端的Servlet訪問請求後:
  ①Web服務器首先檢查是否已經裝載並建立了該Servlet的實例對象。若是是,則直接執行第④步,不然,執行第②步。
  ②裝載並建立該Servlet的一個實例對象。 
  ③調用Servlet實例對象的init()方法。
  ④建立一個用於封裝HTTP請求消息的HttpServletRequest對象和一個表明HTTP響應消息的HttpServletResponse對象,而後調用Servlet的service()方法並將請求和響應對象做爲參數傳遞進去。
  ⑤WEB應用程序被中止或從新啓動以前,Servlet引擎將卸載Servlet,並在卸載以前調用Servlet的destroy()方法。 java

  

 

3、Servlet調用圖

 Servlet調用圖

四:實現Servlet的方式(由咱們本身來寫!)

實現Servlet有三種方式:web

l  實現javax.servlet.Servlet接口;c#

l  繼承javax.servlet.GenericServlet類;api

l  繼承javax.servlet.http.HttpServlet類;瀏覽器

  一般咱們會去繼承HttpServlet類來完成咱們的Servlet,但學習Servlet還要從javax.servlet.Servlet接口開始學習。tomcat

Servlet.java安全

public interface Servlet [c1] {服務器

    public void init(ServletConfig config) throws ServletException;多線程

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res)

            throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();

}

 

3 建立helloservlet應用

咱們開始第一個Servlet應用吧!首先在webapps目錄下建立helloservlet目錄,它就是咱們的應用目錄了,而後在helloservlet目錄中建立準備JavaWeb應用所需內容:

l  建立/helloservlet/WEB-INF目錄;

l  建立/helloservlet/WEB-INF/classes目錄;

l  建立/helloservlet/WEB-INF/lib目錄;

l  建立/helloservlet/WEB-INF/web.xml文件;

 

接下來咱們開始準備完成Servlet,完成Servlet須要分爲兩步:

l  編寫Servlet類;

l  在web.xml文件中配置Servlet;

 

HelloServlet.java

public class HelloServlet implements Servlet {

    public void init(ServletConfig config) throws ServletException {}

    public ServletConfig getServletConfig() {return null;}

    public void destroy() {}

    public String getServletInfo() {return null;}

 

    public void service(ServletRequest req, ServletResponse res)

           throws ServletException, IOException {

       System.out.println("hello servlet!");

    }

}

 

  咱們暫時忽略Servlet中其餘四個方法,只關心service()方法,由於它是用來處理請求的方法。咱們在該方法內給出一條輸出語句!

 

web.xml(下面內容須要背下來)

    <servlet>

       <servlet-name>hello</servlet-name>

       <servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>

    </servlet>

    <servlet-mapping>

       <servlet-name>hello</servlet-name>

       <url-pattern>/helloworld</url-pattern>

    </servlet-mapping> 

 

在web.xml中配置Servlet的目的其實只有一個,就是把訪問路徑與一個Servlet綁定到一塊兒,上面配置是把訪問路徑:「/helloworld」與「cn.itcast.servlet.HelloServlet」綁定到一塊兒。

l  <servlet>:指定HelloServlet這個Servlet的名稱爲hello;

l  <servlet-mapping>:指定/helloworld訪問路徑因此訪問的Servlet名爲hello。

<servlet>和<servlet-mapping>經過<servlet-name>這個元素關聯在一塊兒了!

接下來,咱們編譯HelloServlet,注意,編譯HelloServlet時須要導入servlet-api.jar,由於Servlet.class等類都在servlet-api.jar中。

javac -classpath F:/tomcat6/lib/servlet-api.jar -d . HelloServlet.java

而後把HelloServlet.class放到/helloworld/WEB-INF/classes/目錄下,而後啓動Tomcat,在瀏覽器中訪問:http://localhost:8080/helloservlet/helloworld便可在控制檯上看到輸出!

l  /helloservlet/WEB-INF/classes/cn/itcast/servlet/HelloServlet.class;

 


 [c1]Servlet中的方法大多數不禁咱們來調用,而是由Tomcat來調用。而且Servlet的對象也不禁咱們來建立,由Tomcat來建立!

 

五: Servlet的生命週期

所謂xxx的生命週期,就是說xxx的出生、服務,以及死亡。Servlet生命週期也是如此!與Servlet的生命週期相關的方法有:

l  void init(ServletConfig);

l  void service(ServletRequest,ServletResponse);

l  void destroy();

 

1.1 Servlet的出生

服務器會在Servlet第一次被訪問時建立Servlet,或者是在服務器啓動時建立Servlet。若是服務器啓動時就建立Servlet,那麼還須要在web.xml文件中配置。也就是說默認狀況下,Servlet是在第一次被訪問時由服務器建立的。

並且一個Servlet類型,服務器只建立一個實例對象,例如在咱們首次訪問http://localhost:8080/helloservlet/helloworld時,服務器經過「/helloworld」找到了綁定的Servlet名稱爲cn.itcast.servlet.HelloServlet,而後服務器查看這個類型的Servlet是否已經建立過,若是沒有建立過,那麼服務器纔會經過反射來建立HelloServlet的實例。當咱們再次訪問http://localhost:8080/helloservlet/helloworld時,服務器就不會再次建立HelloServlet實例了,而是直接使用上次建立的實例。

在Servlet被建立後,服務器會立刻調用Servlet的void init(ServletConfig)方法。請記住, Servlet出生後立刻就會調用init()方法,並且一個Servlet的一輩子。這個方法只會被調用一次。這比如小孩子出生後立刻就要去剪臍帶同樣,並且剪臍帶一輩子只有一次。

咱們能夠把一些對Servlet的初始化工做放到init方法中!

 

1.2 Servlet服務

  當服務器每次接收到請求時,都會去調用Servlet的service()方法來處理請求。服務器接收到一次請求,就會調用service() 方法一次,因此service()方法是會被調用屢次的。正由於如此,因此咱們才須要把處理請求的代碼寫到service()方法中!

 

1.3 Servlet的離去

  Servlet是不會輕易離去的,一般都是在服務器關閉時Servlet纔會離去!在服務器被關閉時,服務器會去銷燬Servlet,在銷燬Servlet以前服務器會先去調用Servlet的destroy()方法,咱們能夠把Servlet的臨終遺言放到destroy()方法中,例如對某些資源的釋放等代碼放到destroy()方法中。

 

1.4 測試生命週期方法

修改HelloServlet以下,而後再去訪問http://localhost:8080/helloservlet/helloworld

public class HelloServlet implements Servlet {

    public void init(ServletConfig config) throws ServletException {

       System.out.println("Servlet被建立了!");

    }

    public ServletConfig getServletConfig() {return null;}

    public void destroy() {

       System.out.println("Servlet要離去了!");

    }

    public String getServletInfo() {return null;}

 

    public void service(ServletRequest req, ServletResponse res)

           throws ServletException, IOException {

       System.out.println("hello servlet!");

    }

}

 

在首次訪問HelloServlet時,init方法會被執行,並且也會執行service方法。再次訪問時,只會執行service方法,再也不執行init方法。在關閉Tomcat時會調用destroy方法。

 

2 Servlet接口相關類型

在Servlet接口中還存在三個咱們不熟悉的類型:

l  ServletRequest:service() 方法的參數,它表示請求對象,它封裝了全部與請求相關的數據,它是由服務器建立的;

l  ServletResponse:service()方法的參數,它表示響應對象,在service()方法中完成對客戶端的響應須要使用這個對象;

l  ServletConfig:init()方法的參數,它表示Servlet配置對象,它對應Servlet的配置信息,那對應web.xml文件中的<servlet>元素。

2.1 ServletRequest和ServletResponse(第五天會詳細講解這兩個對象)

ServletRequest和ServletResponse是Servlet#service() 方法的兩個參數,一個是請求對象,一個是響應對象,能夠從ServletRequest對象中獲取請求數據,可使用ServletResponse對象完成響應。你之後會發現,這兩個對象就像是一對恩愛的夫妻,永遠不分離,老是成對出現。

ServletRequest和ServletResponse的實例由服務器建立,而後傳遞給service()方法。若是在service() 方法中但願使用HTTP相關的功能,那麼能夠把ServletRequest和ServletResponse強轉成HttpServletRequest和HttpServletResponse。這也說明咱們常常須要在service()方法中對ServletRequest和ServletResponse進行強轉,這是很心煩的事情。不事後面會有一個類來幫咱們解決這一問題的。

HttpServletRequest方法:

l  String getParameter(String paramName):獲取指定請求參數的值;

l  String getMethod():獲取請求方法,例如GET或POST;

l  String getHeader(String name):獲取指定請求頭的值;

l  void setCharacterEncoding(String encoding):設置請求體的編碼!由於GET請求沒有請求體,因此這個方法只只對POST請求有效。當調用request.setCharacterEncoding(「utf-8」)以後,再經過getParameter()方法獲取參數值時,那麼參數值都已經經過了轉碼,即轉換成了UTF-8編碼。因此,這個方法必須在調用getParameter()方法以前調用!

 

HttpServletResponse方法:

l  PrintWriter getWriter():獲取字符響應流,使用該流能夠向客戶端輸出響應信息。例如response.getWriter().print(「<h1>Hello JavaWeb!</h1>」);

l  ServletOutputStream getOutputStream():獲取字節響應流,當須要向客戶端響應字節數據時,須要使用這個流,例如要向客戶端響應圖片;

l  void setCharacterEncoding(String encoding):用來設置字符響應流的編碼,例如在調用setCharacterEncoding(「utf-8」);以後,再response.getWriter()獲取字符響應流對象,這時的響應流的編碼爲utf-8,使用response.getWriter()輸出的中文都會轉換成utf-8編碼後發送給客戶端;

l  void setHeader(String name, String value):向客戶端添加響應頭信息,例如setHeader(「Refresh」, 「3;url=http://www.itcast.cn」),表示3秒後自動刷新到http://www.itcast.cn;

l  void setContentType(String contentType):該方法是setHeader(「content-type」, 「xxx」)的簡便方法,即用來添加名爲content-type響應頭的方法。content-type響應頭用來設置響應數據的MIME類型,例如要向客戶端響應jpg的圖片,那麼能夠setContentType(「image/jepg」),若是響應數據爲文本類型,那麼還要臺同時設置編碼,例如setContentType(「text/html;chartset=utf-8」)表示響應數據類型爲文本類型中的html類型,而且該方法會調用setCharacterEncoding(「utf-8」)方法;

l  void sendError(int code, String errorMsg):向客戶端發送狀態碼,以及錯誤消息。例如給客戶端發送404:response(404, 「您要查找的資源不存在!」)。

 

2.1 ServletConfig

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

 

ServletConfig對象是由服務器建立的,而後傳遞給Servlet的init()方法,你能夠在init()方法中使用它!

l  String getServletName():獲取Servlet在web.xml文件中的配置名稱,即<servlet-name>指定的名稱;

l  ServletContext getServletContext():用來獲取ServletContext對象,ServletContext會在後面講解;

l  String getInitParameter(String name):用來獲取在web.xml中配置的初始化參數,經過參數名來獲取參數值;

l  Enumeration getInitParameterNames():用來獲取在web.xml中配置的全部初始化參數名稱;

在<servlet>元素中還能夠配置初始化參數:

  <servlet>

    <servlet-name>One</servlet-name>

    <servlet-class>cn.itcast.servlet.OneServlet</servlet-class>

    <init-param>

         <param-name>paramName1</param-name>

        <param-value>paramValue1</param-value>

    </init-param>

    <init-param>

        <param-name>paramName2</param-name>

        <param-value>paramValue2</param-value>

    </init-param>

[崔1]   </servlet>

 

在OneServlet中,可使用ServletConfig對象的getInitParameter()方法來獲取初始化參數,例如:

String value1 = servletConfig.getInitParameter(「paramName1」);//獲取到paramValue1


 [崔1]配置了兩個初始化參數

 

 

六:Servlet的線程安全問題

  • 不要在Servlet中建立成員!建立局部變量便可!
  • 能夠建立無狀態成員!
  • 能夠建立有狀態的成員,但狀態必須爲只讀的!

   

由於一個類型的Servlet只有一個實例對象,那麼就有可能會現時出一個Servlet同時處理多個請求,那麼Servlet是否爲線程安全的呢?答案是:「不是線程安全的」。這說明Servlet的工做效率很高,但也存在線程安全問題!

因此咱們不該該在Servlet中便宜建立成員變量,由於可能會存在一個線程對這個成員變量進行寫操做,另外一個線程對這個成員變量進行讀操做。

 

 

  當多個客戶端併發訪問同一個Servlet時,web服務器會爲每個客戶端的訪問請求建立一個線程,並在這個線程上調用Servlet的service方法,所以service方法內若是訪問了同一個資源的話,就有可能引起線程安全問題。例以下面的代碼:

不存在線程安全問題的代碼:

複製代碼
 1 package gacl.servlet.study;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.servlet.ServletException;
 6 import javax.servlet.http.HttpServlet;
 7 import javax.servlet.http.HttpServletRequest;
 8 import javax.servlet.http.HttpServletResponse;
 9 
10 public class ServletDemo3 extends HttpServlet {
11 
12     
13     public void doGet(HttpServletRequest request, HttpServletResponse response)
14             throws ServletException, IOException {
15         
16         /**
17          * 當多線程併發訪問這個方法裏面的代碼時,會存在線程安全問題嗎
18          * i變量被多個線程併發訪問,可是沒有線程安全問題,由於i是doGet方法裏面的局部變量,
19          * 當有多個線程併發訪問doGet方法時,每個線程裏面都有本身的i變量,
20          * 各個線程操做的都是本身的i變量,因此不存在線程安全問題
21          * 多線程併發訪問某一個方法的時候,若是在方法內部定義了一些資源(變量,集合等)
22          * 那麼每個線程都有這些東西,因此就不存在線程安全問題了
23          */
24         int i=1;
25         i++;
26         response.getWriter().write(i);
27     }
28 
29     public void doPost(HttpServletRequest request, HttpServletResponse response)
30             throws ServletException, IOException {
31         doGet(request, response);
32     }
33 
34 }
複製代碼

存在線程安全問題的代碼:

複製代碼
 1 package gacl.servlet.study;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.servlet.ServletException;
 6 import javax.servlet.http.HttpServlet;
 7 import javax.servlet.http.HttpServletRequest;
 8 import javax.servlet.http.HttpServletResponse;
 9 
10 public class ServletDemo3 extends HttpServlet {
11 
12     int i=1;
13     public void doGet(HttpServletRequest request, HttpServletResponse response)
14             throws ServletException, IOException {
15         
16         i++;
17         try {
18             Thread.sleep(1000*4);
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22         response.getWriter().write(i+"");
23     }
24 
25     public void doPost(HttpServletRequest request, HttpServletResponse response)
26             throws ServletException, IOException {
27         doGet(request, response);
28     }
29 
30 }
複製代碼

  把i定義成全局變量,當多個線程併發訪問變量i時,就會存在線程安全問題了,以下圖所示:同時開啓兩個瀏覽器模擬併發訪問同一個Servlet,原本正常來講,第一個瀏覽器應該看到2,而第二個瀏覽器應該看到3的,結果兩個瀏覽器都看到了3,這就不正常。

相關文章
相關標籤/搜索