Servlet&JSP的那些事兒(五)

來源:互聯網
上載者:User

我們在Servlet&JSP的那些事兒(二)中討論過init()方法,其中提到了再init()方法中有一個類型為ServletConfig的參數,Servlet容器通過這個參數向Servlet傳遞配置資訊。Servlet使用ServletConfig對象從Web應用程式的配置資訊中擷取以名-值對形式提供的初始化參數。另外,在Servlet中,還可以通過ServletConfig對象擷取描述Servlet運行環境的ServletContext對象,使用該對象,Servlet可以和它的Servlet容器進行通訊。現在我們來深入瞭解一下為什麼要使用ServletConfig參數以及如何使用它的問題。

比如我希望我的email地址能出現在由servlet產生的網頁上,不過我的email可能要變,但是我又不想因為這個重新編譯我的servlet代碼,那麼我改如何呢?我想在部署描述檔案(DD)中配置我的email地址,而不是把它寫入程式碼到servlet類中。也即,我不希望這樣:

PrintWriter out = response.getWriter();out.println("shan@email.com");

寫入程式碼email地址很不好,因為如果我要改變email,就需要重新編譯一遍servlet類,這樣很麻煩,所以可以在DD檔案(web.xml)中做如下部署:

<servlet><servlet-name>TestServlet</servlet-name><servlet-value>com.shan.web.TestServlet</servlet-value><init-param><param-name>email</param-name><param-value>shan@email.com</param-value></init-param></servlet>

在servlet代碼中,添加如下語句:

out.println(getServletConfig().getInitParameter("email"));

getServletConfig()方法返回一個ServletConfig,getInitParameter("email")返回名為email的參數值。容器初始阿華一個servlet時,會為這個servlet建立一個唯一的ServletConfig。容器從DD讀出servlet初始化參數,並把這些參數交給ServletConfig,然後把ServletConfig傳遞給servlet的init()方法。注意,不能從servlet構造方法中調用getServletConfig()方法,因為在容器調用init()方法之前,它還不是一個完整的servlet。

那麼在容器如何初始化servlet參數時,發生了什麼呢?

1)容器在建立一個servlet時,它會讀取DD,包括servlet初始化參數<init-param>。
2)容器為這個servlet建立一個新的ServletConfig。
3)為每個servlet初始化參數建立一個String名/值對。
4)容器向ServletConfig提供名/值對初始化參數的引用。
5)容器建立servlet類的一個新執行個體。
6)容器調用servlet的init()方法,傳入ServletConfig的引用。

從這個過程中我們可以看出,容器建立servlet時,它會讀取DD,並為ServletConfig建立名/值對。之後,容器不會再讀取初始化參數了。所以,不能在servlet生命週期中改變email地址,這種方法還是太笨拙了。不過比起寫入程式碼,好了一些。如果需要改變email地址,重新部署應用即可。

JSP能得到servlet初始化參數嗎?

ServletConfig用於servlet配置,而不是JSPConfig。所以,想讓應用的其他部分使用你在DD中配置的servlet初始化參數的資訊,就需要使用我們在Servlet&JSP的那些事兒(四)中講到到請求轉寄機制。只需要在請求中設定一個屬性,接收轉寄請求的JSP就可以得到這個訊息。

String email = getServletConfig().getInitParameter("email");request.setAttribute("email",email);RequestDispatcher rd=request.getRequestDispatcher("/test.jsp");rd.fordward(request,response);

但是我們可能需要在整個應用中使用這個地址。一種方法是讓servlet讀取初始化參數,然後把它們儲存起來,這樣應用的其他部分都能使用,但是這樣一來,我們必須知道應用部署的時候首先運行哪個servlet,而且如果更改應用,一切都會被搞砸。所以,這樣是不行的。

鑒於此,我們採用上下文初始化參數。它和servlet初始化參數很類似,只不過上下文初始化參數對整個web應用而不是一個servlet可用。所以,servlet和jsp都可以訪問上下文初始化參數。我們需要在web.xml中做如下修改:

<servlet><servlet-name>TestServlet</servlet-name><servlet-value>com.shan.web.TestServlet</servlet-value></servlet><context-param><param-name>email</param-name><param-value>shan@email.com</param-value></context-param>

註:<context-param>是針對整個web應用的,所以不嵌套在<servlet>中。

在servlet代碼中,添加如下語句:

out.println(getServletContext().getInitParameter("email"));

或者

ServletContext context = getServletContext();out.println(context.getInitParameter("email"));

注意servlet初始化參數和上下文初始化參數的區別。整個web應用中只有一個ServletContext,而且web應用中所有部分都能訪問它。不過,應用中的每個servlet都有自己的ServletConfig。部署web應用時,容器會建立一個ServletContext(註:如果你的應用分布在多個伺服器上,那麼web應用實際上可以有多個ServletContext,一個ServletContext的確只對應一個應用,但前提是應用在一個JVM中。),這個上下文對web應用中的每個servlet和jsp都可用。

說到這裡,如果我們希望應用初始化參數是一個資料庫DataSource呢?

上下文初始化參數只能是String。畢竟我們不能把一個對象塞到xml部署描述檔案中去。如果整個web應用有一個main方法就好了,可以添加一些在servlet或jsp之前啟動並執行代碼。雖然servlet沒有main方法,但是卻有監聽者(listener)。我們想要的實際上是監聽一個初始化事件,這樣就能得到上下文初始化參數,並在應用為客戶服務之前運行一些代碼。但是,應用中的哪部分能做監聽者的工作呢?我們不希望讓一個servlet來做這樣的工作,畢竟它的任務不是這個。

我們需要的其實是一個ServletContextListener。它能監聽ServletContext一生中的兩個關鍵事件:初始化(建立)和撤銷。它能做到:

在上下文初始化時(應用部署時)得到通知。

  • 從ServletContext得到上下文初始化參數。
  • 使用初始化參數尋找名建立一個資料庫連接
  • 把資料庫連接作為一個屬性,使得web應用的各部分都能訪問。

在上下文撤銷時(應用取消部署或結束)得到通知。

  • 關閉資料庫連接。

執行個體,一個簡單的ServletContextListener

因為我們現在不想建立一個資料庫,所以不使用串連資料庫的例子。我們的這個執行個體會將String初始化參數轉化為一個對象-Dog。監聽者的任務是得到有關狗品種的上下文初始化參數(藏獒、松獅、薩摩、中華田園犬等),然後使用這個String來構造一個Dog對象,監聽者再把這個Dog對象儲存到一個ServletContex屬性中,以便servlet擷取。問題的關鍵是servlet能訪問一個共用的應用對象(Dog),而且不用讀上下文參數,所以這個共用應用對象是不是資料庫連結就沒有關係了。

建立一個監聽者類:

package com.shan.listen;import com.shan.model.Dog;import javax.servlet.*;public class MyServletContextListener implements ServletContextListener {public void contextInitialized(ServletContextEvent event) {ServletContext context = event.getServletContext();String dogBreed = context.getInitParameter("breed");Dog dog = new Dog(dogBreed);context.setAttribute("dog",dog);}public void contextDestroyed(ServletContextEvent event) {}}

其中,Dog類代碼如下:

package com.shan.model;public class Dog {private String breed = null;public Dog(String breed) {this.breed = breed;}public String getBreed(){return breed;}}

編寫servlet類,代碼如下:

package com.shan.web;import com.shan.model.Dog;import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class TestListenerServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {response.setContentType("text/html;charset=gb2312");         PrintWriter out=response.getWriter();out.println("一個監聽者設定context屬性的測試:");out.println("<br/>");Dog dog = (Dog)getServletContext().getAttribute("dog");out.println("狗的品種是:"+dog.getBreed());out.close();}public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {doGet(request,response);}}

註:不能忘記強制轉換類型(Dog)。

最後編寫部署描述檔案web.xml

<?xml version='1.0' encoding='utf-8'?><web-app 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                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"  version="3.0"  metadata-complete="true"><servlet><servlet-name>TestListenerServlet</servlet-name><servlet-class>com.shan.web.TestListenerServlet</servlet-class></servlet><servlet-mapping><servlet-name>TestListenerServlet</servlet-name><url-pattern>/ListenTest.do</url-pattern></servlet-mapping><context-param><param-name>breed</param-name><param-value>藏獒</param-value></context-param><listener><listener-class>com.shan.listen.MyServletContextListener</listener-class></listener></web-app>

貌似沒有在xml元素來指明所監聽的事件類型?其實容器會檢查類,並注意監聽者介面(或多個介面,一個監聽者可以實現多個監聽者介面),以此明確監聽什麼類型的事件。

我們建立web應用的過程和前面敘述的相同,唯一變化的是添加了新的java包com.shan.listen,用以存放監聽類,最後的部署也和前面的相同,在此就不再贅述。

不過我們還需要執行如下語句來編譯上述類(先切換到工程所在目錄):

javac -d classes src\com\shan\model\Dog.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar;classes -d classes src\com\shan\listen\MyServletContextListener.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar;classes -d .\classes src\com\shan\web\TestListenerServlet.java

啟動tomcat,從而部署應用。測試結果如下:

圖1 執行個體運行結果

我們來討論一下整個完整的情境。大概分為以下幾步:

1)容器讀取該應用的部署描述檔案,包括<listen>和<context-param>元素。
2)容器為這個應用建立一個新的ServletContext,應用的所有部分都會共用這個上下文。
3)容器為每個上下文初始化參數建立一個String名/值對,這裡假設只有一個參數。
4)容器將名/值參數的引用交給ServletContext。
5)容器建立MyServletContextListener類的一個新執行個體。
6)容器調用監聽者的contextInitialized()方法,傳入新的ServletContextEvent。這個事件對象有一個ServletContext引用,所以事件處理代碼可以從事件上下文,並從上下文得到上下文初始化參數。
7)監聽者向ServletContextEvent要ServletContext的一個引用。
8)監聽者向ServletContext要上下文初始化參數"breed"。
9)監聽者使用初始化參數來構造一個新的Dog對象。
10)監聽者把Dog設定為ServletContext中的一個屬性。
11)容器建立一個新的servlet(也就是說,利用初始化參數建立一個新的ServletConfig,為這個ServletConfig提供ServletContext的一個引用,然後調用servlet的init()方法)。
12)servlet得到一個請求,向ServletContext請求屬性"dog"。
13)servlet在Dog上調用getBreed(),並將結果輸出到HttpResponse。

除了上下文監聽者介面之外,還有其他的監聽者介面。

情境 監聽者介面 事件類型
你想知道一個web應用上下文中是否增加、刪除或替換了一個屬性 javax.servlet.ServletContextAttributeListener
attributeAdded
attributeRemoved
attributeReplaced
ServletContextAttributeEvent
你想知道有多少個並發使用者。也就是說,你想跟蹤活動的會話 javax.servlet.http.HttpSessionListener
sessionCreated
sessionDestroyed
HttpSessionEvent
每次請求到來時你都想知道,以便建立日誌 javax.servlet.ServletRequestListener
requestInitialized
requestDestroyed
ServletRequestEvent
你想知道什麼時候增加、刪除或替換一個請求屬性 javax.servlet.ServletRequestAttributeListener
attributeAdded
attributeRemoved
attributeReplaced
ServletRequestAttributeEvent
你有一個屬性類(這個類表示的對象將被放在一個屬性中),而且你希望這個類型的對象綁定到一個會話或從會話刪除時得到通知 javax.servlet.http.HttpSessionBindingListener
valueBound
valueUnbound
HttpSessionBindingEvent
你想知道什麼時候增加、刪除或替換一個會話屬性 javax.servlet.http.HttpSessionAttributeListener
attributeAdded
attributeRemoved
attributeReplaced
HttpSessionBindingEvent(註:這裡的命名不一致!)
你想知道是否建立或撤銷了一個上下文 javax.servlet.ServletContextListener
contextInitialized
contextDestroyed
ServletContextEvent
你有一個屬性類,而且希望此類對象綁定的會話遷移到另一個JVM時得到通知 javax.servlet.http.HttpSessionActivationListener
sessionDidActivate
sessionWillPassivate
HttpSessionEvent(註:這裡的命名不一致!)

轉載請註明出處:http://blog.csdn.net/iAm333

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.