javaweb Servlet開發(fā)總結(jié)(一)
一、Servlet簡介
Servlet是sun公司提供的一門用于開發(fā)動態(tài)web資源的技術(shù)。
Sun公司在其API中提供了一個servlet接口,用戶若想用發(fā)一個動態(tài)web資源(即開發(fā)一個Java程序向瀏覽器輸出數(shù)據(jù)),需要完成以下2個步驟:
1、編寫一個Java類,實現(xiàn)servlet接口。
2、把開發(fā)好的Java類部署到web服務(wù)器中。
按照一種約定俗成的稱呼習(xí)慣,通常我們也把實現(xiàn)了servlet接口的java程序,稱之為Servlet
二、Servlet的運行過程
Servlet程序是由WEB服務(wù)器調(diào)用,web服務(wù)器收到客戶端的Servlet訪問請求后:
?、賅eb服務(wù)器首先檢查是否已經(jīng)裝載并創(chuàng)建了該Servlet的實例對象。如果是,則直接執(zhí)行第④步,否則,執(zhí)行第②步。
?、谘b載并創(chuàng)建該Servlet的一個實例對象。
?、壅{(diào)用Servlet實例對象的init()方法。
④創(chuàng)建一個用于封裝HTTP請求消息的HttpServletRequest對象和一個代表HTTP響應(yīng)消息的HttpServletResponse對象,然后調(diào)用Servlet的service()方法并將請求和響應(yīng)對象作為參數(shù)傳遞進(jìn)去。
?、軼EB應(yīng)用程序被停止或重新啟動之前,Servlet引擎將卸載Servlet,并在卸載之前調(diào)用Servlet的destroy()方法。
三、Servlet調(diào)用圖
四、在Eclipse中開發(fā)Servlet
在eclipse中新建一個web project工程,eclipse會自動創(chuàng)建下圖所示目錄結(jié)構(gòu):
4.1、Servlet接口實現(xiàn)類
Servlet接口SUN公司定義了兩個默認(rèn)實現(xiàn)類,分別為:GenericServlet、HttpServlet。
HttpServlet指能夠處理HTTP請求的servlet,它在原有Servlet接口上添加了一些與HTTP協(xié)議處理方法,它比Servlet接口的功能更為強(qiáng)大。因此開發(fā)人員在編寫Servlet時,通常應(yīng)繼承這個類,而避免直接去實現(xiàn)Servlet接口。
HttpServlet在實現(xiàn)Servlet接口時,覆寫了service方法,該方法體內(nèi)的代碼會自動判斷用戶的請求方式,如為GET請求,則調(diào)用HttpServlet的doGet方法,如為Post請求,則調(diào)用doPost方法。因此,開發(fā)人員在編寫Servlet時,通常只需要覆寫doGet或doPost方法,而不要去覆寫service方法。
4.2、通過Eclipse創(chuàng)建和編寫Servlet
選中g(shù)acl.servlet.study包,右鍵→New→Servlet,如下圖所示:
這樣,我們就通過Eclipse幫我們創(chuàng)建好一個名字為ServletDemo1的Servlet,創(chuàng)建好的ServletDemo01里面會有如下代碼:
package gacl.servlet.study; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletDemo1 extends HttpServlet { /** * The doGet method of the servlet. <br> * * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(this.getClass()); out.println(", using the GET method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } /** * The doPost method of the servlet. <br> * * This method is called when a form has its tag value method equals to post. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(this.getClass()); out.println(", using the POST method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } }
這些代碼都是Eclipse自動生成的,而web.xml文件中也多了<servlet></servlet>和<servlet-mapping></servlet-mapping>兩對標(biāo)簽,這兩對標(biāo)簽是配置ServletDemo1的,如下圖所示:
然后我們就可以通過瀏覽器訪問ServletDemo1這個Servlet,如下圖所示:
五、Servlet開發(fā)注意細(xì)節(jié)
5.1、Servlet訪問URL映射配置
由于客戶端是通過URL地址訪問web服務(wù)器中的資源,所以Servlet程序若想被外界訪問,必須把servlet程序映射到一個URL地址上,這個工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
<servlet>元素用于注冊Servlet,它包含有兩個主要的子元素:<servlet-name>和<servlet-class>,分別用于設(shè)置Servlet的注冊名稱和Servlet的完整類名。
一個<servlet-mapping>元素用于映射一個已注冊的Servlet的一個對外訪問路徑,它包含有兩個子元素:<servlet-name>和<url-pattern>,分別用于指定Servlet的注冊名稱和Servlet的對外訪問路徑。例如:
<servlet> <servlet-name>ServletDemo1</servlet-name> <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/servlet/ServletDemo1</url-pattern> </servlet-mapping> 同一個Servlet可以被映射到多個URL上,即多個<servlet-mapping>元素的<servlet-name>子元素的設(shè)置值可以是同一個Servlet的注冊名。 例如: <servlet> <servlet-name>ServletDemo1</servlet-name> <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/servlet/ServletDemo1</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/1.htm</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/2.jsp</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/3.php</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/4.ASPX</url-pattern> </servlet-mapping>
通過上面的配置,當(dāng)我們想訪問名稱是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中也可以使用*通配符,但是只能有兩種固定的格式:一種格式是"*.擴(kuò)展名",另一種格式是以正斜杠(/)開頭并以"/*"結(jié)尾。例如:
<servlet> <servlet-name>ServletDemo1</servlet-name> <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/*</url-pattern>
*可以匹配任意的字符,所以此時可以用任意的URL去訪問ServletDemo1這個Servlet,如下圖所示:
對于如下的一些映射關(guān)系:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
問題:
當(dāng)請求URL為“/abc/a.html”,“/abc/*”和“/*”都匹配,哪個servlet響應(yīng)
Servlet引擎將調(diào)用Servlet1。
當(dāng)請求URL為“/abc”時,“/abc/*”和“/abc”都匹配,哪個servlet響應(yīng)
Servlet引擎將調(diào)用Servlet3。
當(dāng)請求URL為“/abc/a.do”時,“/abc/*”和“*.do”都匹配,哪個servlet響應(yīng)
Servlet引擎將調(diào)用Servlet1。
當(dāng)請求URL為“/a.do”時,“/*”和“*.do”都匹配,哪個servlet響應(yīng)
Servlet引擎將調(diào)用Servlet2。
當(dāng)請求URL為“/xxx/yyy/a.do”時,“/*”和“*.do”都匹配,哪個servlet響應(yīng)
Servlet引擎將調(diào)用Servlet2。
匹配的原則就是"誰長得更像就找誰"
5.3、Servlet與普通Java類的區(qū)別
Servlet是一個供其他Java程序(Servlet引擎)調(diào)用的Java類,它不能獨立運行,它的運行完全由Servlet引擎來控制和調(diào)度。
針對客戶端的多次Servlet請求,通常情況下,服務(wù)器只會創(chuàng)建一個Servlet實例對象,也就是說Servlet實例對象一旦創(chuàng)建,它就會駐留在內(nèi)存中,為后續(xù)的其它請求服務(wù),直至web容器退出,servlet實例對象才會銷毀。
在Servlet的整個生命周期內(nèi),Servlet的init方法只被調(diào)用一次。而對一個Servlet的每次訪問請求都導(dǎo)致Servlet引擎調(diào)用一次servlet的service方法。對于每次訪問請求,Servlet引擎都會創(chuàng)建一個新的HttpServletRequest請求對象和一個新的HttpServletResponse響應(yīng)對象,然后將這兩個對象作為參數(shù)傳遞給它調(diào)用的Servlet的service()方法,service方法再根據(jù)請求方式分別調(diào)用doXXX方法。
如果在<servlet>元素中配置了一個<load-on-startup>元素,那么WEB應(yīng)用程序在啟動時,就會裝載并創(chuàng)建Servlet的實例對象、以及調(diào)用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應(yīng)用寫一個InitServlet,這個servlet配置為啟動時裝載,為整個web應(yīng)用創(chuàng)建必要的數(shù)據(jù)庫表和數(shù)據(jù)。
5.4、缺省Servlet
如果某個Servlet的映射路徑僅僅為一個正斜杠(/),那么這個Servlet就成為當(dāng)前Web應(yīng)用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它們的訪問請求都將交給缺省Servlet處理,也就是說,缺省Servlet用于處理所有其他Servlet都不處理的訪問請求。 例如:
<servlet> <servlet-name>ServletDemo2</servlet-name> <servlet-class>gacl.servlet.study.ServletDemo2</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- 將ServletDemo2配置成缺省Servlet --> <servlet-mapping> <servlet-name>ServletDemo2</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
當(dāng)訪問不存在的Servlet時,就使用配置的默認(rèn)Servlet進(jìn)行處理,如下圖所示:
在<tomcat的安裝目錄>\conf\web.xml文件中,注冊了一個名稱為org.apache.catalina.servlets.DefaultServlet的Servlet,并將這個Servlet設(shè)置為了缺省Servlet。
<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> <!-- The mapping for the default servlet --> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
當(dāng)訪問Tomcat服務(wù)器中的某個靜態(tài)HTML文件和圖片時,實際上是在訪問這個缺省Servlet。
5.5、Servlet的線程安全問題
當(dāng)多個客戶端并發(fā)訪問同一個Servlet時,web服務(wù)器會為每一個客戶端的訪問請求創(chuàng)建一個線程,并在這個線程上調(diào)用Servlet的service方法,因此service方法內(nèi)如果訪問了同一個資源的話,就有可能引發(fā)線程安全問題。例如下面的代碼:
不存在線程安全問題的代碼:
package gacl.servlet.study; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletDemo3 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /** * 當(dāng)多線程并發(fā)訪問這個方法里面的代碼時,會存在線程安全問題嗎 * i變量被多個線程并發(fā)訪問,但是沒有線程安全問題,因為i是doGet方法里面的局部變量, * 當(dāng)有多個線程并發(fā)訪問doGet方法時,每一個線程里面都有自己的i變量, * 各個線程操作的都是自己的i變量,所以不存在線程安全問題 * 多線程并發(fā)訪問某一個方法的時候,如果在方法內(nèi)部定義了一些資源(變量,集合等) * 那么每一個線程都有這些東西,所以就不存在線程安全問題了 */ int i=1; i++; response.getWriter().write(i); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
存在線程安全問題的代碼:
package gacl.servlet.study; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletDemo3 extends HttpServlet { int i=1; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { i++; try { Thread.sleep(1000*4); } catch (InterruptedException e) { e.printStackTrace(); } response.getWriter().write(i+""); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
把i定義成全局變量,當(dāng)多個線程并發(fā)訪問變量i時,就會存在線程安全問題了,如下圖所示:同時開啟兩個瀏覽器模擬并發(fā)訪問同一個Servlet,本來正常來說,第一個瀏覽器應(yīng)該看到2,而第二個瀏覽器應(yīng)該看到3的,結(jié)果兩個瀏覽器都看到了3,這就不正常。
線程安全問題只存在多個線程并發(fā)操作同一個資源的情況下,所以在編寫Servlet的時候,如果并發(fā)訪問某一個資源(變量,集合等),就會存在線程安全問題,那么該如何解決這個問題呢?
先看看下面的代碼:
package gacl.servlet.study; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletDemo3 extends HttpServlet { int i=1; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /** * 加了synchronized后,并發(fā)訪問i時就不存在線程安全問題了, * 為什么加了synchronized后就沒有線程安全問題了呢? * 假如現(xiàn)在有一個線程訪問Servlet對象,那么它就先拿到了Servlet對象的那把鎖 * 等到它執(zhí)行完之后才會把鎖還給Servlet對象,由于是它先拿到了Servlet對象的那把鎖, * 所以當(dāng)有別的線程來訪問這個Servlet對象時,由于鎖已經(jīng)被之前的線程拿走了,后面的線程只能排隊等候了 * */ synchronized (this) {//在java中,每一個對象都有一把鎖,這里的this指的就是Servlet對象 i++; try { Thread.sleep(1000*4); } catch (InterruptedException e) { e.printStackTrace(); } response.getWriter().write(i+""); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
現(xiàn)在這種做法是給Servlet對象加了一把鎖,保證任何時候都只有一個線程在訪問該Servlet對象里面的資源,這樣就不存在線程安全問題了,如下圖所示:
這種做法雖然解決了線程安全問題,但是編寫Servlet卻萬萬不能用這種方式處理線程安全問題,假如有9999個人同時訪問這個Servlet,那么這9999個人必須按先后順序排隊輪流訪問。
針對Servlet的線程安全問題,Sun公司是提供有解決方案的:讓Servlet去實現(xiàn)一個SingleThreadModel接口,如果某個Servlet實現(xiàn)了SingleThreadModel接口,那么Servlet引擎將以單線程模式來調(diào)用其service方法。
查看Sevlet的API可以看到,SingleThreadModel接口中沒有定義任何方法和常量,在Java中,把沒有定義任何方法和常量的接口稱之為標(biāo)記接口,經(jīng)??吹降囊粋€最典型的標(biāo)記接口就是"Serializable",這個接口也是沒有定義任何方法和常量的,標(biāo)記接口在Java中有什么用呢?主要作用就是給某個對象打上一個標(biāo)志,告訴JVM,這個對象可以做什么,比如實現(xiàn)了"Serializable"接口的類的對象就可以被序列化,還有一個"Cloneable"接口,這個也是一個標(biāo)記接口,在默認(rèn)情況下,Java中的對象是不允許被克隆的,就像現(xiàn)實生活中的人一樣,不允許克隆,但是只要實現(xiàn)了"Cloneable"接口,那么對象就可以被克隆了。
讓Servlet實現(xiàn)了SingleThreadModel接口,只要在Servlet類的定義中增加實現(xiàn)SingleThreadModel接口的聲明即可。
對于實現(xiàn)了SingleThreadModel接口的Servlet,Servlet引擎仍然支持對該Servlet的多線程并發(fā)訪問,其采用的方式是產(chǎn)生多個Servlet實例對象,并發(fā)的每個線程分別調(diào)用一個獨立的Servlet實例對象。
實現(xiàn)SingleThreadModel接口并不能真正解決Servlet的線程安全問題,因為Servlet引擎會創(chuàng)建多個Servlet實例對象,而真正意義上解決多線程安全問題是指一個Servlet實例對象被多個線程同時調(diào)用的問題。事實上,在Servlet API 2.4中,已經(jīng)將SingleThreadModel標(biāo)記為Deprecated(過時的)。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助。
相關(guān)文章
springmvc+spring+mybatis實現(xiàn)用戶登錄功能(上)
這篇文章主要為大家詳細(xì)介紹了springmvc+spring+mybatis實現(xiàn)用戶登錄功能,比較基礎(chǔ)的學(xué)習(xí)教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07springboot 自定義異常并捕獲異常返給前端的實現(xiàn)代碼
在開發(fā)中,如果用try catch的方式,每個方法都需要單獨實現(xiàn),為了方便分類異常,返回給前端,采用了@ControllerAdvice注解和繼承了RuntimeException的方式來實現(xiàn),具體實現(xiàn)內(nèi)容跟隨小編一起看看吧2021-11-11關(guān)于Java中增強(qiáng)for循環(huán)使用的注意事項
for循環(huán)語句是java循環(huán)語句中最常用的循環(huán)語句,一般用在循環(huán)次數(shù)已知的情況下使用,這篇文章主要給大家介紹了關(guān)于Java中增強(qiáng)for循環(huán)使用的注意事項,需要的朋友可以參考下2021-06-06Springboot swagger配置過程詳解(idea社區(qū)版2023.1.4+apache-maven-3
這篇文章主要介紹了Springboot-swagger配置(idea社區(qū)版2023.1.4+apache-maven-3.9.3-bin),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07