Servlet簡述
Servlet 是一個 Java 類新症,通常在 Web 應用 MVC 模式中擔任 Controler 角色乾闰,它的任務是得到一個客戶的請求庐冯,再發(fā)回一個響應,在接受客戶請求后迹淌,調用模型對請求數據進行處理,將處理后的數據設置為請求屬性己单,再發(fā)送到控制頁面的 JSP 中唉窃。下面就通過一次完整的HTTP請求來介紹 Servlet 是如何工作的。
一次HTTP請求的到來
容器全盤控制著 Servlet 的一生纹笼,當用戶點擊一個鏈接比如:http://localhost:8080/testWeb/action.do 后纹份,這個請求到達服務器和容器,Tomcat看到用戶請求的是 testWeb 這個Web應用廷痘,于是到 testWeb 目錄下去找 Web.xml (我們一般稱之為部署描述文件,即DD)蔓涧,在DD中找 servlet-mapping 元素,與之匹配的 url-pattern,根據這個 url-pattern 的 servlet-name 映射到真正的 servlet-class 笋额,容器根據此依據調用相應的 Servlet 類元暴。
<servlet>
<servlet-name>ActionServletName</servlet-name>
<servlet-class>com.gyf.web.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ActionServletName</servlet-name>
<url-pattern>/action.do</url-pattern>
</servlet-mapping>
Servlet 的生命周期
通過上述過程,容器找到了應該調用的 Servlet 兄猩,如果這個 Servlet 類還沒有被加載茉盏,容器會從頭調用Servlet 的生命周期:
- 首先加載目標類(ActionServlet.class),接著調用Servlet的默認無參構造函數(注意我們不需要去覆蓋Servlet的構造函數)。
- 接著調用 init() 方法枢冤,這個方法在 Servlet 的一生中只調用一次援岩,如果你有其他的初始化代碼(如得到一個數據庫連接)。
- 接著調用 Service()方法掏导,如果容器當初發(fā)現 Servlet 類已經被加載就會跳過前面兩個步驟直接進入這個步驟享怀,每次有HTTP請求到來時,都會調用目標 Servlet 的Service 方法趟咆,這個Service方法每次調用都會開啟一個新線程添瓷,根據HTTP請求的類型決定是繼續(xù)調用doGet(),還是doPost()梅屉。Service 方法在 Servlet 的一生中可以調用多次。
- 最后調用destroy()方法殺死這個 Servlet 類鳞贷,在這個方法中可以進行垃圾回收清理資源坯汤。
注意在每個JVM上,每個特定的 Servlet 類只會有一個實例搀愧,所以不存在對于Servlet的每個實例這種說法惰聂。
ServletConfig 與 ServletContext
我們在 Servlet 輸出一些固定信息時,可能會這樣做
PrintWriter out=response.getWriter();
out.println("59833576*@qq.com");
如果我們要修改郵箱地址怎么辦咱筛,就只有修改源代碼搓幌,停止 Web 應用,重新編譯迅箩,再啟動 Web 應用溉愁。非常繁瑣,在實際生產環(huán)境中饲趋,能不去動源代碼就不去動源代碼拐揭,那么我們可以用 ServletConfig 與 ServletContext 來解決這個問題。
ServletConfig
在部署描述文件中這樣寫:
<servlet>
<servlet-name>ActionServletName</servlet-name>
<servlet-class>com.gyf.web.ActionServlet</servlet-class>
<init-param>
<param-name>adminEmail</param-name>
<param-value>59833576*@qq.com</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ActionServletName</servlet-name>
<url-pattern>/action.do</url-pattern>
</servlet-mapping>
可以看到 init-param 是在 servlet 標簽內的奕塑,這也意味著只能在該 servlet 類中使用堂污,并不是全局的。
在 Servlet 中我們這么使用:
out.println(getServletConfig().getInitParameter("adminEmail"));
注意龄砰,不能在構造函數中調用這個方法敷鸦,在init()后,Servlet 才得到ServletConfig對象寝贡。
ServletContext
ServletContext是全局有效的,這一點從它的部署位置就可以看出來:
<web-app
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>com.gyf.web.GetJarServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Test</servlet-name>
<url-pattern>/servlet-api.jar</url-pattern>
</servlet-mapping>
<context-param>
<param-name>adminEmail</param-name>
<param-value>59833576*@qq.com</param-value>
</context-param>
</web-app>
context-param 就在web-app 標簽下值依,所以它對所有的 Servlet 都是有效的圃泡,在 Servlet 代碼中:
out.println(getServletContext().getInitParamter("adminEmail"));
要注意區(qū)分ServletContext 和 ServletConfig的區(qū)別和寫法。
監(jiān)聽者Listener
如果我們想在應用部署時就馬上做一個事情要怎么做呢愿险?這是我們就需要一個監(jiān)聽者颇蜡。監(jiān)聽者分為很多種,每種的用途用法都不一樣辆亏,比如剛才說的應用部署時就要做一個事情就需要 ServletContextListener风秤。
ServletContextListener
package com.gyf;
import javax.servlet.*;
public class MyServletContextListener implements ServletContextListener
{
public void contextInitialized(ServletContextEvent event)
{
ServletContext sc=event.getServletContext();
String dogBreed=sc.getInitParameter("breed");
Dog d=new Dog(dogBreed);
sc.setAttribute("dog",d);
//得到數據庫連接
//將數據保存進數據庫
}
public void contextDestroyed(ServletContextEvent event)
{
//關閉數據庫
}
}
方法很簡單,我們只需要擴展 ServletContextListener 接口就行了扮叨,并將這個 .class文件 放進classes文件夾缤弦,最后在部署描述文件中寫上該監(jiān)聽類的名字就行了:
<listener>
<listener-class>
com.gyf.MyServletContextListener
</listener-class>
</listener>
還有很多監(jiān)聽者類可供使用
上下文初始化參數線程安全
現在有了一個問題,既然 ServletContext 是全局可見的彻磁,那么如何保證保證其線程安全呢碍沐?有的同學可能會想在 doPost() 或 doGet() 方法上加 synchronized 狸捅,但是仔細想一想,這樣做只能保證每個Servlet 只有一個線程在運行累提,但是一個Web應用可以有很多個Servlet尘喝,這樣的話仍然不能保證它的線程安全。正確方法應該是這樣做的:
synchronized (getServletContext())
{
getServletContext().setAttribute("foo",22);
getServletContext().setAttribute("bar",42);
}
每次使用ServletContext都要求先獲得它的鎖斋陪,這種方法才奏效