一、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調用圖
四、在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(過時的)。
六、ServletConfig講解
6.1、配置Servlet初始化參數
在Servlet的配置文件web.xml中,可以使用一個或多個<init-param>標籤爲servlet配置一些初始化參數。
例如:
1 <servlet> 2 <servlet-name>ServletConfigDemo1</servlet-name> 3 <servlet-class>gacl.servlet.study.ServletConfigDemo1</servlet-class> 4 <!--配置ServletConfigDemo1的初始化參數 --> 5 <init-param> 6 <param-name>name</param-name> 7 <param-value>gacl</param-value> 8 </init-param> 9 <init-param> 10 <param-name>password</param-name> 11 <param-value>123</param-value> 12 </init-param> 13 <init-param> 14 <param-name>charset</param-name> 15 <param-value>UTF-8</param-value> 16 </init-param> 17 </servlet>
6.2、通過ServletConfig獲取Servlet的初始化參數
當servlet配置了初始化參數後,web容器在創建servlet實例對象時,會自動將這些初始化參數封裝到ServletConfig對象中,並在調用servlet的init方法時,將ServletConfig對象傳遞給servlet。進而,我們通過ServletConfig對象就可以得到當前servlet的初始化參數信息。
例如:
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 import java.util.Enumeration; 5 import javax.servlet.ServletConfig; 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 ServletConfigDemo1 extends HttpServlet { 12 13 /** 14 * 定義ServletConfig對象來接收配置的初始化參數 15 */ 16 private ServletConfig config; 17 18 /** 19 * 當servlet配置了初始化參數後,web容器在創建servlet實例對象時, 20 * 會自動將這些初始化參數封裝到ServletConfig對象中,並在調用servlet的init方法時, 21 * 將ServletConfig對象傳遞給servlet。進而,程序員通過ServletConfig對象就可以 22 * 得到當前servlet的初始化參數信息。 23 */ 24 @Override 25 public void init(ServletConfig config) throws ServletException { 26 this.config = config; 27 } 28 29 public void doGet(HttpServletRequest request, HttpServletResponse response) 30 throws ServletException, IOException { 31 //獲取在web.xml中配置的初始化參數 32 String paramVal = this.config.getInitParameter("name");//獲取指定的初始化參數 33 response.getWriter().print(paramVal); 34 35 response.getWriter().print("<hr/>"); 36 //獲取所有的初始化參數 37 Enumeration<String> e = config.getInitParameterNames(); 38 while(e.hasMoreElements()){ 39 String name = e.nextElement(); 40 String value = config.getInitParameter(name); 41 response.getWriter().print(name + "=" + value + "<br/>"); 42 } 43 } 44 45 public void doPost(HttpServletRequest request, HttpServletResponse response) 46 throws ServletException, IOException { 47 this.doGet(request, response); 48 } 49 50 }
運行結果如下:
七、ServletContext對象
WEB容器在啓動時,它會爲每個WEB應用程序都創建一個對應的ServletContext對象,它代表當前web應用。
ServletConfig對象中維護了ServletContext對象的引用,開發人員在編寫servlet時,可以通過ServletConfig.getServletContext方法獲得ServletContext對象。
由於一個WEB應用中的所有Servlet共享同一個ServletContext對象,因此Servlet對象之間可以通過ServletContext對象來實現通訊。ServletContext對象通常也被稱之爲context域對象。
八、ServletContext的應用
8.1、多個Servlet通過ServletContext對象實現數據共享
範例:ServletContextDemo1和ServletContextDemo2通過ServletContext對象實現數據共享
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 import javax.servlet.ServletContext; 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 ServletContextDemo1 extends HttpServlet { 11 12 public void doGet(HttpServletRequest request, HttpServletResponse response) 13 throws ServletException, IOException { 14 String data = "xdp_gacl"; 15 /** 16 * ServletConfig對象中維護了ServletContext對象的引用,開發人員在編寫servlet時, 17 * 可以通過ServletConfig.getServletContext方法獲得ServletContext對象。 18 */ 19 ServletContext context = this.getServletConfig().getServletContext();//獲得ServletContext對象 20 context.setAttribute("data", data); //將data存儲到ServletContext對象中 21 } 22 23 public void doPost(HttpServletRequest request, HttpServletResponse response) 24 throws ServletException, IOException { 25 doGet(request, response); 26 } 27 }
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 import javax.servlet.ServletContext; 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 ServletContextDemo2 extends HttpServlet { 11 12 public void doGet(HttpServletRequest request, HttpServletResponse response) 13 throws ServletException, IOException { 14 ServletContext context = this.getServletContext(); 15 String data = (String) context.getAttribute("data");//從ServletContext對象中取出數據 16 response.getWriter().print("data="+data); 17 } 18 19 public void doPost(HttpServletRequest request, HttpServletResponse response) 20 throws ServletException, IOException { 21 doGet(request, response); 22 } 23 }
先運行ServletContextDemo1,將數據data存儲到ServletContext對象中,然後運行ServletContextDemo2就可以從ServletContext對象中取出數據了,這樣就實現了數據共享,如下圖所示:
8.2、獲取WEB應用的初始化參數
在web.xml文件中使用<context-param>標籤配置WEB應用的初始化參數,如下所示:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 3 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> 4 <display-name></display-name> 5 <!-- 配置WEB應用的初始化參數 --> 6 <context-param> 7 <param-name>url</param-name> 8 <param-value>jdbc:mysql://localhost:3306/test</param-value> 9 </context-param> 10 11 <welcome-file-list> 12 <welcome-file>index.jsp</welcome-file> 13 </welcome-file-list> 14 </web-app>
獲取Web應用的初始化參數,代碼如下:
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 import javax.servlet.ServletContext; 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 ServletContextDemo3 extends HttpServlet { 12 13 public void doGet(HttpServletRequest request, HttpServletResponse response) 14 throws ServletException, IOException { 15 16 ServletContext context = this.getServletContext(); 17 //獲取整個web站點的初始化參數 18 String contextInitParam = context.getInitParameter("url"); 19 response.getWriter().print(contextInitParam); 20 } 21 22 public void doPost(HttpServletRequest request, HttpServletResponse response) 23 throws ServletException, IOException { 24 doGet(request, response); 25 } 26 27 }
運行結果:
8.3、用servletContext實現請求轉發
ServletContextDemo4
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 import javax.servlet.RequestDispatcher; 6 import javax.servlet.ServletContext; 7 import javax.servlet.ServletException; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 12 public class ServletContextDemo4 extends HttpServlet { 13 14 public void doGet(HttpServletRequest request, HttpServletResponse response) 15 throws ServletException, IOException { 16 String data = "<h1><font color='red'>abcdefghjkl</font></h1>"; 17 response.getOutputStream().write(data.getBytes()); 18 ServletContext context = this.getServletContext();//獲取ServletContext對象 19 RequestDispatcher rd = context.getRequestDispatcher("/servlet/ServletContextDemo5");//獲取請求轉發對象(RequestDispatcher) 20 rd.forward(request, response);//調用forward方法實現請求轉發 21 } 22 23 public void doPost(HttpServletRequest request, HttpServletResponse response) 24 throws ServletException, IOException { 25 } 26 }
ServletContextDemo5
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 import javax.servlet.ServletException; 5 import javax.servlet.http.HttpServlet; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 9 public class ServletContextDemo5 extends HttpServlet { 10 11 public void doGet(HttpServletRequest request, HttpServletResponse response) 12 throws ServletException, IOException { 13 response.getOutputStream().write("servletDemo5".getBytes()); 14 } 15 16 public void doPost(HttpServletRequest request, HttpServletResponse response) 17 throws ServletException, IOException { 18 this.doGet(request, response); 19 } 20 21 }
運行結果:
訪問的是ServletContextDemo4,瀏覽器顯示的卻是ServletContextDemo5的內容,這就是使用ServletContext實現了請求轉發
8.4、利用ServletContext對象讀取資源文件
項目目錄結構如下:
代碼範例:使用servletContext讀取資源文件
1 package gacl.servlet.study; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.text.MessageFormat; 8 import java.util.Properties; 9 import javax.servlet.ServletException; 10 import javax.servlet.http.HttpServlet; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 14 /** 15 * 使用servletContext讀取資源文件 16 * 17 * @author gacl 18 * 19 */ 20 public class ServletContextDemo6 extends HttpServlet { 21 22 public void doGet(HttpServletRequest request, HttpServletResponse response) 23 throws ServletException, IOException { 24 /** 25 * response.setContentType("text/html;charset=UTF-8");目的是控制瀏覽器用UTF-8進行解碼; 26 * 這樣就不會出現中文亂碼了 27 */ 28 response.setHeader("content-type","text/html;charset=UTF-8"); 29 readSrcDirPropCfgFile(response);//讀取src目錄下的properties配置文件 30 response.getWriter().println("<hr/>"); 31 readWebRootDirPropCfgFile(response);//讀取WebRoot目錄下的properties配置文件 32 response.getWriter().println("<hr/>"); 33 readPropCfgFile(response);//讀取src目錄下的db.config包中的db3.properties配置文件 34 response.getWriter().println("<hr/>"); 35 readPropCfgFile2(response);//讀取src目錄下的gacl.servlet.study包中的db4.properties配置文件 36 37 } 38 39 /** 40 * 讀取src目錄下的gacl.servlet.study包中的db4.properties配置文件 41 * @param response 42 * @throws IOException 43 */ 44 private void readPropCfgFile2(HttpServletResponse response) 45 throws IOException { 46 InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/gacl/servlet/study/db4.properties"); 47 Properties prop = new Properties(); 48 prop.load(in); 49 String driver = prop.getProperty("driver"); 50 String url = prop.getProperty("url"); 51 String username = prop.getProperty("username"); 52 String password = prop.getProperty("password"); 53 response.getWriter().println("讀取src目錄下的gacl.servlet.study包中的db4.properties配置文件:"); 54 response.getWriter().println( 55 MessageFormat.format( 56 "driver={0},url={1},username={2},password={3}", 57 driver,url, username, password)); 58 } 59 60 /** 61 * 讀取src目錄下的db.config包中的db3.properties配置文件 62 * @param response 63 * @throws FileNotFoundException 64 * @throws IOException 65 */ 66 private void readPropCfgFile(HttpServletResponse response) 67 throws FileNotFoundException, IOException { 68 //通過ServletContext獲取web資源的絕對路徑 69 String path = this.getServletContext().getRealPath("/WEB-INF/classes/db/config/db3.properties"); 70 InputStream in = new FileInputStream(path); 71 Properties prop = new Properties(); 72 prop.load(in); 73 String driver = prop.getProperty("driver"); 74 String url = prop.getProperty("url"); 75 String username = prop.getProperty("username"); 76 String password = prop.getProperty("password"); 77 response.getWriter().println("讀取src目錄下的db.config包中的db3.properties配置文件:"); 78 response.getWriter().println( 79 MessageFormat.format( 80 "driver={0},url={1},username={2},password={3}", 81 driver,url, username, password)); 82 } 83 84 /** 85 * 通過ServletContext對象讀取WebRoot目錄下的properties配置文件 86 * @param response 87 * @throws IOException 88 */ 89 private void readWebRootDirPropCfgFile(HttpServletResponse response) 90 throws IOException { 91 /** 92 * 通過ServletContext對象讀取WebRoot目錄下的properties配置文件 93 * “/”代表的是項目根目錄 94 */ 95 InputStream in = this.getServletContext().getResourceAsStream("/db2.properties"); 96 Properties prop = new Properties(); 97 prop.load(in); 98 String driver = prop.getProperty("driver"); 99 String url = prop.getProperty("url"); 100 String username = prop.getProperty("username"); 101 String password = prop.getProperty("password"); 102 response.getWriter().println("讀取WebRoot目錄下的db2.properties配置文件:"); 103 response.getWriter().print( 104 MessageFormat.format( 105 "driver={0},url={1},username={2},password={3}", 106 driver,url, username, password)); 107 } 108 109 /** 110 * 通過ServletContext對象讀取src目錄下的properties配置文件 111 * @param response 112 * @throws IOException 113 */ 114 private void readSrcDirPropCfgFile(HttpServletResponse response) throws IOException { 115 /** 116 * 通過ServletContext對象讀取src目錄下的db1.properties配置文件 117 */ 118 InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db1.properties"); 119 Properties prop = new Properties(); 120 prop.load(in); 121 String driver = prop.getProperty("driver"); 122 String url = prop.getProperty("url"); 123 String username = prop.getProperty("username"); 124 String password = prop.getProperty("password"); 125 response.getWriter().println("讀取src目錄下的db1.properties配置文件:"); 126 response.getWriter().println( 127 MessageFormat.format( 128 "driver={0},url={1},username={2},password={3}", 129 driver,url, username, password)); 130 } 131 132 public void doPost(HttpServletRequest request, HttpServletResponse response) 133 throws ServletException, IOException { 134 this.doGet(request, response); 135 } 136 137 }
運行結果如下:
代碼範例:使用類裝載器讀取資源文件
1 package gacl.servlet.study; 2 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.OutputStream; 7 import java.text.MessageFormat; 8 import java.util.Properties; 9 10 import javax.servlet.ServletException; 11 import javax.servlet.http.HttpServlet; 12 import javax.servlet.http.HttpServletRequest; 13 import javax.servlet.http.HttpServletResponse; 14 15 /** 16 * 用類裝載器讀取資源文件 17 * 通過類裝載器讀取資源文件的注意事項:不適合裝載大文件,否則會導致jvm內存溢出 18 * @author gacl 19 * 20 */ 21 public class ServletContextDemo7 extends HttpServlet { 22 23 public void doGet(HttpServletRequest request, HttpServletResponse response) 24 throws ServletException, IOException { 25 /** 26 * response.setContentType("text/html;charset=UTF-8");目的是控制瀏覽器用UTF-8進行解碼; 27 * 這樣就不會出現中文亂碼了 28 */ 29 response.setHeader("content-type","text/html;charset=UTF-8"); 30 test1(response); 31 response.getWriter().println("<hr/>"); 32 test2(response); 33 response.getWriter().println("<hr/>"); 34 //test3(); 35 test4(); 36 37 } 38 39 /** 40 * 讀取類路徑下的資源文件 41 * @param response 42 * @throws IOException 43 */ 44 private void test1(HttpServletResponse response) throws IOException { 45 //獲取到裝載當前類的類裝載器 46 ClassLoader loader = ServletContextDemo7.class.getClassLoader(); 47 //用類裝載器讀取src目錄下的db1.properties配置文件 48 InputStream in = loader.getResourceAsStream("db1.properties"); 49 Properties prop = new Properties(); 50 prop.load(in); 51 String driver = prop.getProperty("driver"); 52 String url = prop.getProperty("url"); 53 String username = prop.getProperty("username"); 54 String password = prop.getProperty("password"); 55 response.getWriter().println("用類裝載器讀取src目錄下的db1.properties配置文件:"); 56 response.getWriter().println( 57 MessageFormat.format( 58 "driver={0},url={1},username={2},password={3}", 59 driver,url, username, password)); 60 } 61 62 /** 63 * 讀取類路徑下面、包下面的資源文件 64 * @param response 65 * @throws IOException 66 */ 67 private void test2(HttpServletResponse response) throws IOException { 68 //獲取到裝載當前類的類裝載器 69 ClassLoader loader = ServletContextDemo7.class.getClassLoader(); 70 //用類裝載器讀取src目錄下的gacl.servlet.study包中的db4.properties配置文件 71 InputStream in = loader.getResourceAsStream("gacl/servlet/study/db4.properties"); 72 Properties prop = new Properties(); 73 prop.load(in); 74 String driver = prop.getProperty("driver"); 75 String url = prop.getProperty("url"); 76 String username = prop.getProperty("username"); 77 String password = prop.getProperty("password"); 78 response.getWriter().println("用類裝載器讀取src目錄下的gacl.servlet.study包中的db4.properties配置文件:"); 79 response.getWriter().println( 80 MessageFormat.format( 81 "driver={0},url={1},username={2},password={3}", 82 driver,url, username, password)); 83 } 84 85 /** 86 * 通過類裝載器讀取資源文件的注意事項:不適合裝載大文件,否則會導致jvm內存溢出 87 */ 88 public void test3() { 89 /** 90 * 01.avi是一個150多M的文件,使用類加載器去讀取這個大文件時會導致內存溢出: 91 * java.lang.OutOfMemoryError: Java heap space 92 */ 93 InputStream in = ServletContextDemo7.class.getClassLoader().getResourceAsStream("01.avi"); 94 System.out.println(in); 95 } 96 97 /** 98 * 讀取01.avi,並拷貝到e:\根目錄下 99 * 01.avi文件太大,只能用servletContext去讀取 100 * @throws IOException 101 */ 102 public void test4() throws IOException { 103 // path=G:\Java學習視頻\JavaWeb學習視頻\JavaWeb\day05視頻\01.avi 104 // path=01.avi 105 String path = this.getServletContext().getRealPath("/WEB-INF/classes/01.avi"); 106 /** 107 * path.lastIndexOf("\\") + 1是一個非常絕妙的寫法 108 */ 109 String filename = path.substring(path.lastIndexOf("\\") + 1);//獲取文件名 110 InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/01.avi"); 111 byte buffer[] = new byte[1024]; 112 int len = 0; 113 OutputStream out = new FileOutputStream("e:\\" + filename); 114 while ((len = in.read(buffer)) > 0) { 115 out.write(buffer, 0, len); 116 } 117 out.close(); 118 in.close(); 119 } 120 121 public void doPost(HttpServletRequest request, HttpServletResponse response) 122 throws ServletException, IOException { 123 124 this.doGet(request, response); 125 } 126 127 }
運行結果如下:
九、在客戶端緩存Servlet的輸出
對於不經常變化的數據,在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 public class ServletDemo5 extends HttpServlet { 11 12 public void doGet(HttpServletRequest request, HttpServletResponse response) 13 throws ServletException, IOException { 14 String data = "abcddfwerwesfasfsadf"; 15 /** 16 * 設置數據合理的緩存時間值,以避免瀏覽器頻繁向服務器發送請求,提升服務器的性能 17 * 這裏是將數據的緩存時間設置爲1天 18 */ 19 response.setDateHeader("expires",System.currentTimeMillis() + 24 * 3600 * 1000); 20 response.getOutputStream().write(data.getBytes()); 21 } 22 23 public void doPost(HttpServletRequest request, HttpServletResponse response) 24 throws ServletException, IOException { 25 26 this.doGet(request, response); 27 } 28 29 }