Servlet是sun公司提供的一門用於開發動態web資源的技術。
Sun公司在其API中提供了一個servlet接口,用戶若想用發一個動態web資源(即開發一個Java程序向瀏覽器輸出數據),須要完成如下2個步驟:
一、編寫一個Java類,實現servlet接口。
二、把開發好的Java類部署到web服務器中。
按照一種約定俗成的稱呼習慣,一般咱們也把實現了servlet接口的java程序,稱之爲Servlet。php
javax.servlet.Servlet定義了全部servlet必須實現的方法。servlet是運行在一個Web服務器一個小的Java程序。servlet接收並響應來自Web客戶端的請求,一般在HTTP、超文本傳輸協議。html
要實現這個接口,能夠編寫一個通用的servlet來實現servlet javax.servlet。或者繼承javax.servlet.http.HttpServlet GenericServlet或 javax.servletHttpServlet。java
這個接口定義了方法來初始化一個servlet,service接收請求,和從服務器刪除servlet。這些被稱爲生命週期方法和被稱爲在下列順序:web
一、servlet構造,而後用init初始化方法。 數據庫
二、任何來自客戶機的請求調用service方法處理。
三、而後執行destory方法,垃圾收集和完成。
四、除了生命週期方法,該接口提供了getServletConfig方法,servlet可使用它來獲得任何啓動信息,和getServletInfo方法,它容許servlet返回自身的基本信息,好比做者、版本和版權。apache
一、在Tomcat的webapps目錄下新建一個JavaWeb項目,而後在web項目中新建一個web-inf/classes目錄。瀏覽器
二、編譯Java文件,並添加web.xml文件,添加映射Servlet對外訪問路徑(Servlet3.0能夠用註解配置)tomcat
import java.io.*; import javax.servlet.*; public class FirstServlet extends GenericServlet { public void service(ServletRequest req,ServletResponse res) throws ServletException, java.io.IOException{ OutputStream out = res.getOutputStream(); out.write("FirstServlet".getBytes()); } }
三、啓動Tomcat容器。訪問Servlet。安全
一、瀏覽器向服務器發送請求。服務器
二、首次訪問Servlet建立Servlet實例對象
三、調用Service方法以前web容器準備好request和response對象
四、調用service方法
五、向repsonse方法寫入響應信息
六、service方法執行完成後返回
七、web容器讀取響應信息
八、web容器構建出http響應返回給瀏覽器
Servlet接口SUN公司定義了兩個默認實現類,分別爲:GenericServlet、HttpServlet。
HttpServlet指可以處理HTTP請求的servlet,它在原有Servlet接口上添加了一些與HTTP協議處理方法,它比Servlet接口的功能更爲強大。所以開發人員在編寫Servlet時,一般應繼承這個類,而避免直接去實現Servlet接口。
HttpServlet在實現Servlet接口時,覆寫了service方法,該方法體內的代碼會自動判斷用戶的請求方式,如爲GET請求,則調用HttpServlet的doGet方法,如爲Post請求,則調用doPost方法。所以,在編寫Servlet時,一般只須要覆寫doGet或doPost方法,而不要去覆寫service方法。
因爲客戶端是經過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/ServletDemo1
http://localhost:8080/JavaWeb/1.htm
http://localhost:8080/JavaWeb/2.jsp
http://localhost:8080/JavaWeb/3.php
http://localhost:8080/JavaWeb/4.ASPX
ServletDemo1被映射到了多個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。
匹配的原則就是"誰長得更像就找誰"
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應用建立必要的數據庫表和數據。
若是某個Servlet的映射路徑僅僅爲一個正斜槓(/),那麼這個Servlet就成爲當前Web應用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它們的訪問請求都將交給缺省Servlet處理,也就是說,缺省Servlet用於處理全部其餘Servlet都不處理的訪問請求。 例如:
1 <servlet> 2 <servlet-name>ServletDemo2</servlet-name> 3 <servlet-class>com.ts.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>
web瀏覽器在訪問web應用程序的時候其實都在訪問Servlet(jpg,html,js),在<tomcat的安裝目錄>\conf\web.xml文件中,註冊了一個名稱爲org.apache.catalina.servlets.DefaultServlet的Servlet,並將這個Servlet設置爲了缺省Servlet。
1 <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
當訪問Tomcat服務器中的某個靜態HTML文件和圖片時,其實是在訪問這個缺省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(過期的)。