1. 程式人生 > >javaweb學習總結——servlet開發

javaweb學習總結——servlet開發

一、Servlet簡介

  Servlet是sun公司提供的一門用於開發動態web資源的技術。
  Sun公司在其API中提供了一個servlet介面,使用者若想用發一個動態web資源(即開發一個Java程式向瀏覽器輸出資料),需要完成以下2個步驟:
  1、編寫一個Java類,實現servlet介面。
  2、把開發好的Java類部署到web伺服器中。
  按照一種約定俗成的稱呼習慣,通常我們也把實現了servlet介面的java程式,稱之為Servlet

二、Servlet的執行過程

Servlet程式是由WEB伺服器呼叫,web伺服器收到客戶端的Servlet訪問請求後:
  ①Web伺服器首先檢查是否已經裝載並建立了該Servlet的例項物件。如果是,則直接執行第④步,否則,執行第②步。
  ②裝載並建立該Servlet的一個例項物件。 
  ③呼叫Servlet例項物件的init()方法。
  ④建立一個用於封裝HTTP請求訊息的HttpServletRequest物件和一個代表HTTP響應訊息的HttpServletResponse物件,然後呼叫Servlet的service()方法並將請求和響應物件作為引數傳遞進去。
  ⑤WEB應用程式被停止或重新啟動之前,Servlet引擎將解除安裝Servlet,並在解除安裝之前呼叫Servlet的destroy()方法。 

三、Servlet呼叫圖

 Servlet呼叫圖

四、在Eclipse中開發Servlet

  在eclipse中新建一個web project工程,eclipse會自動建立下圖所示目錄結構:

 

4.1、Servlet介面實現類

  Servlet介面SUN公司定義了兩個預設實現類,分別為:GenericServlet、HttpServlet。

  HttpServlet指能夠處理HTTP請求的servlet,它在原有Servlet介面上添加了一些與HTTP協議處理方法,它比Servlet介面的功能更為強大。因此開發人員在編寫Servlet時,通常應繼承這個類,而避免直接去實現Servlet介面。
  HttpServlet在實現Servlet介面時,覆寫了service方法,該方法體內的程式碼會自動判斷使用者的請求方式,如為GET請求,則呼叫HttpServlet的doGet方法,如為Post請求,則呼叫doPost方法。因此,開發人員在編寫Servlet時,通常只需要覆寫doGet或doPost方法,而不要去覆寫service方法。

4.2、通過Eclipse建立和編寫Servlet

  選中gacl.servlet.study包,右鍵→New→Servlet,如下圖所示:

  

  

  

  這樣,我們就通過Eclipse幫我們建立好一個名字為ServletDemo1的Servlet,建立好的ServletDemo01裡面會有如下程式碼:

複製程式碼
 1 package gacl.servlet.study;
 2 
 3 import java.io.IOException;
 4 import java.io.PrintWriter;
 5 
 6
import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 public class ServletDemo1 extends HttpServlet { 12 13 /** 14 * The doGet method of the servlet. <br> 15 * 16 * This method is called when a form has its tag value method equals to get. 17 * 18 * @param request the request send by the client to the server 19 * @param response the response send by the server to the client 20 * @throws ServletException if an error occurred 21 * @throws IOException if an error occurred 22 */ 23 public void doGet(HttpServletRequest request, HttpServletResponse response) 24 throws ServletException, IOException { 25 26 response.setContentType("text/html"); 27 PrintWriter out = response.getWriter(); 28 out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); 29 out.println("<HTML>"); 30 out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); 31 out.println(" <BODY>"); 32 out.print(" This is "); 33 out.print(this.getClass()); 34 out.println(", using the GET method"); 35 out.println(" </BODY>"); 36 out.println("</HTML>"); 37 out.flush(); 38 out.close(); 39 } 40 41 /** 42 * The doPost method of the servlet. <br> 43 * 44 * This method is called when a form has its tag value method equals to post. 45 * 46 * @param request the request send by the client to the server 47 * @param response the response send by the server to the client 48 * @throws ServletException if an error occurred 49 * @throws IOException if an error occurred 50 */ 51 public void doPost(HttpServletRequest request, HttpServletResponse response) 52 throws ServletException, IOException { 53 54 response.setContentType("text/html"); 55 PrintWriter out = response.getWriter(); 56 out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); 57 out.println("<HTML>"); 58 out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); 59 out.println(" <BODY>"); 60 out.print(" This is "); 61 out.print(this.getClass()); 62 out.println(", using the POST method"); 63 out.println(" </BODY>"); 64 out.println("</HTML>"); 65 out.flush(); 66 out.close(); 67 } 68 69 }
複製程式碼

  這些程式碼都是Eclipse自動生成的,而web.xml檔案中也多了<servlet></servlet>和<servlet-mapping></servlet-mapping>兩對標籤,這兩對標籤是配置ServletDemo1的,如下圖所示:

然後我們就可以通過瀏覽器訪問ServletDemo1這個Servlet,如下圖所示:

  

五、Servlet開發注意細節

5.1、Servlet訪問URL對映配置

  由於客戶端是通過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>
2     <servlet-name>ServletDemo1</servlet-name>
3     <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
4   </servlet>
5 
6   <servlet-mapping>
7     <servlet-name>ServletDemo1</servlet-name>
8     <url-pattern>/servlet/ServletDemo1</url-pattern>
9   </servlet-mapping>
複製程式碼

  同一個Servlet可以被對映到多個URL上,即多個<servlet-mapping>元素的<servlet-name>子元素的設定值可以是同一個Servlet的註冊名。 例如:

複製程式碼
 1  <servlet>
 2     <servlet-name>ServletDemo1</servlet-name>
 3     <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
 4   </servlet>
 5 
 6   <servlet-mapping>
 7     <servlet-name>ServletDemo1</servlet-name>
 8     <url-pattern>/servlet/ServletDemo1</url-pattern>
 9   </servlet-mapping>
10  <servlet-mapping>
11     <servlet-name>ServletDemo1</servlet-name>
12     <url-pattern>/1.htm</url-pattern>
13   </servlet-mapping>
14    <servlet-mapping>
15     <servlet-name>ServletDemo1</servlet-name>
16     <url-pattern>/2.jsp</url-pattern>
17   </servlet-mapping>
18    <servlet-mapping>
19     <servlet-name>ServletDemo1</servlet-name>
20     <url-pattern>/3.php</url-pattern>
21   </servlet-mapping>
22    <servlet-mapping>
23     <servlet-name>ServletDemo1</servlet-name>
24     <url-pattern>/4.ASPX</url-pattern>
25   </servlet-mapping>
複製程式碼

  通過上面的配置,當我們想訪問名稱是ServletDemo1的Servlet,可以使用如下的幾個地址去訪問:

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/servlet/ServletDemo1

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/1.htm

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/2.jsp

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/3.php

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/4.ASPX

  ServletDemo1被對映到了多個URL上。

5.2、Servlet訪問URL使用*萬用字元對映  

在Servlet對映到的URL中也可以使用*萬用字元,但是隻能有兩種固定的格式:一種格式是"*.副檔名",另一種格式是以正斜槓(/)開頭並以"/*"結尾。例如:

  

複製程式碼
1  <servlet>
2     <servlet-name>ServletDemo1</servlet-name>
3     <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
4   </servlet>
5 
6    <servlet-mapping>
7     <servlet-name>ServletDemo1</servlet-name>
8    <url-pattern>/*</url-pattern>
複製程式碼

  *可以匹配任意的字元,所以此時可以用任意的URL去訪問ServletDemo1這個Servlet,如下圖所示:

  

對於如下的一些對映關係:
  Servlet1 對映到 /abc/* 
  Servlet2 對映到 /* 
  Servlet3 對映到 /abc 
  Servlet4 對映到 *.do 
問題:
  當請求URL為“/abc/a.html”,“/abc/*”和“/*”都匹配,哪個servlet響應
      Servlet引擎將呼叫Servlet1。
  當請求URL為“/abc”時,“/abc/*”和“/abc”都匹配,哪個servlet響應
      Servlet引擎將呼叫Servlet3。
  當請求URL為“/abc/a.do”時,“/abc/*”和“*.do”都匹配,哪個servlet響應
      Servlet引擎將呼叫Servlet1。
  當請求URL為“/a.do”時,“/*”和“*.do”都匹配,哪個servlet響應
      Servlet引擎將呼叫Servlet2。
  當請求URL為“/xxx/yyy/a.do”時,“/*”和“*.do”都匹配,哪個servlet響應
      Servlet引擎將呼叫Servlet2。
  匹配的原則就是"誰長得更像就找誰"

5.3、Servlet與普通Java類的區別  

  Servlet是一個供其他Java程式(Servlet引擎)呼叫的Java類,它不能獨立執行,它的執行完全由Servlet引擎來控制和排程。
  針對客戶端的多次Servlet請求,通常情況下,伺服器只會建立一個Servlet例項物件,也就是說Servlet例項物件一旦建立,它就會駐留在記憶體中,為後續的其它請求服務,直至web容器退出,servlet例項物件才會銷燬。
  在Servlet的整個生命週期內,Servlet的init方法只被呼叫一次。而對一個Servlet的每次訪問請求都導致Servlet引擎呼叫一次servlet的service方法。對於每次訪問請求,Servlet引擎都會建立一個新的HttpServletRequest請求物件和一個新的HttpServletResponse響應物件,然後將這兩個物件作為引數傳遞給它呼叫的Servlet的service()方法,service方法再根據請求方式分別呼叫doXXX方法。

  如果在<servlet>元素中配置了一個<load-on-startup>元素,那麼WEB應用程式在啟動時,就會裝載並建立Servlet的例項物件、以及呼叫Servlet例項物件的init()方法。
    舉例:
    <servlet>
        <servlet-name>invoker</servlet-name>
        <servlet-class>
            org.apache.catalina.servlets.InvokerServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

  用途:為web應用寫一個InitServlet,這個servlet配置為啟動時裝載,為整個web應用建立必要的資料庫表和資料。

5.4、預設Servlet

  如果某個Servlet的對映路徑僅僅為一個正斜槓(/),那麼這個Servlet就成為當前Web應用程式的預設Servlet。 
  凡是在web.xml檔案中找不到匹配的<servlet-mapping>元素的URL,它們的訪問請求都將交給預設Servlet處理,也就是說,預設Servlet用於處理所有其他Servlet都不處理的訪問請求。 例如:

複製程式碼
 1  <servlet>
 2     <servlet-name>ServletDemo2</servlet-name>
 3     <servlet-class>gacl.servlet.study.ServletDemo2</servlet-class>
 4     <load-on-startup>1</load-on-startup>
 5   </servlet>
 6   
 7   <!-- 將ServletDemo2配置成預設Servlet -->
 8   <servlet-mapping>
 9     <servlet-name>ServletDemo2</servlet-name>
10     <url-pattern>/</url-pattern>
11   </servlet-mapping>
複製程式碼

  當訪問不存在的Servlet時,就使用配置的預設Servlet進行處理,如下圖所示:

  

  在<tomcat的安裝目錄>\conf\web.xml檔案中,註冊了一個名稱為org.apache.catalina.servlets.DefaultServlet的Servlet,並將這個Servlet設定為了預設Servlet。

複製程式碼
 1     <servlet>
 2         <servlet-name>default</servlet-name>
 3         <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
 4         <init-param>
 5             <param-name>debug</param-name>
 6             <param-value>0</param-value>
 7         </init-param>
 8         <init-param>
 9             <param-name>listings</param-name>
10             <param-value>false</param-value>
11         </init-param>
12         <load-on-startup>1</load-on-startup>
13     </servlet>
14 
15  <!-- The mapping for the default servlet -->
16     <servlet-mapping>
17         <servlet-name>default</servlet-name>
18         <url-pattern>/</url-pattern>
19     </servlet-mapping>
複製程式碼

  當訪問Tomcat伺服器中的某個靜態HTML檔案和圖片時,實際上是在訪問這個預設Servlet。

5.5、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,這就不正常。

  

  執行緒安全問題只存在多個執行緒併發操作同一個資源的情況下,所以在編寫Servlet的時候,如果併發訪問某一個資源(變數,集合等),就會存線上程安全問題,那麼該如何解決這個問題呢?

先看看下面的程式碼:

複製程式碼
 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 
11 public class ServletDemo3 extends HttpServlet {
12 
13     int i=1;
14     public void doGet(HttpServletRequest request, HttpServletResponse response)
15             throws ServletException, IOException {
16         /**
17          * 加了synchronized後,併發訪問i時就不存線上程安全問題了,
18          * 為什麼加了synchronized後就沒有執行緒安全問題了呢?
19          * 假如現在有一個執行緒訪問Servlet物件,那麼它就先拿到了Servlet物件的那把鎖
20          * 等到它執行完之後才會把鎖還給Servlet物件,由於是它先拿到了Servlet物件的那把鎖,
21          * 所以當有別的執行緒來訪問這個Servlet物件時,由於鎖已經被之前的執行緒拿走了,後面的執行緒只能排隊等候了
22          * 
23          */
24         synchronized (this) {//在java中,每一個物件都有一把鎖,這裡的this指的就是Servlet物件
25             i++;
26             try {
27                 Thread.sleep(1000*4);
28             } catch (InterruptedException e) {
29                 e.printStackTrace();
30             }
31             response.getWriter().write(i+"");
32         }
33         
34     }
35 
36     public void doPost(HttpServletRequest request, HttpServletResponse response)
37             throws ServletException, IOException {
38         doGet(request, response);
39     }
40 
41 }
複製程式碼

  現在這種做法是給Servlet物件加了一把鎖,保證任何時候都只有一個執行緒在訪問該Servlet物件裡面的資源,這樣就不存線上程安全問題了,如下圖所示:

  

  這種做法雖然解決了執行緒安全問題,但是編寫Servlet卻萬萬不能用這種方式處理執行緒安全問題,假如有9999個人同時訪問這個Servlet,那麼這9999個人必須按先後順序排隊輪流訪問。

  針對Servlet的執行緒安全問題,Sun公司是提供有解決方案的:讓Servlet去實現一個SingleThreadModel介面,如果某個Servlet實現了SingleThreadModel介面,那麼Servlet引擎將以單執行緒模式來呼叫其service方法。
  檢視Sevlet的API可以看到,SingleThreadModel介面中沒有定義任何方法和常量,在Java中,把沒有定義任何方法和常量的介面稱之為標記介面,經常看到的一個最典型的標記介面就是"Serializable",這個介面也是沒有定義任何方法和常量的,標記介面在Java中有什麼用呢?主要作用就是給某個物件打上一個標誌,告訴JVM,這個物件可以做什麼,比如實現了"Serializable"介面的類的物件就可以被序列化,還有一個"Cloneable"介面,這個也是一個標記介面,在預設情況下,Java中的物件是不允許被克隆的,就像現實生活中的人一樣,不允許克隆,但是隻要實現了"Cloneable"介面,那麼物件就可以被克隆了。

  讓Servlet實現了SingleThreadModel介面,只要在Servlet類的定義中增加實現SingleThreadModel介面的宣告即可。  
  對於實現了SingleThreadModel介面的Servlet,Servlet引擎仍然支援對該Servlet的多執行緒併發訪問,其採用的方式是產生多個Servlet例項物件,併發的每個執行緒分別呼叫一個獨立的Servlet例項物件
  實現SingleThreadModel介面並不能真正解決Servlet的執行緒安全問題,因為Servlet引擎會建立多個Servlet例項物件,而真正意義上解決多執行緒安全問題是指一個Servlet例項物件被多個執行緒同時呼叫的問題。事實上,在Servlet API 2.4中,已經將SingleThreadModel標記為Deprecated(過時的)。  

文章轉載於:http://www.cnblogs.com/xdp-gacl/p/3760336.html

分類:  JavaWeb學習總結