一、Servlet 概述
Servlet 類是 JavaWeb 的三大組件之一逃默,它屬于動(dòng)態(tài)資源鹃愤。其作用是處理請求,服務(wù)器通常會把接收到的請求交由 Servlet 的實(shí)現(xiàn)類來處理笑旺,通常Servlet的實(shí)現(xiàn)類需要完成:
- 接收請求數(shù)據(jù)昼浦;
- 處理請求;
- 完成響應(yīng)筒主。
例如:當(dāng)客戶端發(fā)出登陸請求关噪,或者輸出注冊請求時(shí),這些請求都會交由一個(gè) Servlet 來處理乌妙。
Servlet 實(shí)現(xiàn)類中的方法需要由我們自己來實(shí)現(xiàn)使兔,且每個(gè) Servlet 實(shí)現(xiàn)類必須實(shí)現(xiàn)javax.servlet.Servlet
接口,之后由服務(wù)器來創(chuàng)建 Servlet 類對象并調(diào)用相應(yīng)的方法藤韵。
二虐沥、Servlet 的本質(zhì)
Servlet并不是一個(gè)很復(fù)雜東西,事實(shí)上 Servlet 就是一個(gè)Java接口:
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
而我們知道泽艘,Java中的接口常被我們視為規(guī)范的存在欲险,所以 Servlet 本身的作用也是作為一種規(guī)范存在。它并不能獨(dú)立運(yùn)行匹涮,我們所要實(shí)現(xiàn)的功能必須交由它的實(shí)現(xiàn)類實(shí)現(xiàn)并最終將實(shí)現(xiàn)類放置在一個(gè) Servlet容器(例如Tomcat天试、Jetty)中運(yùn)行。
Servlet 接口定義的就是一套處理網(wǎng)絡(luò)請求的規(guī)范然低,所有實(shí)現(xiàn) servlet 的類喜每,都需要實(shí)現(xiàn)它接口中所定義的方法。其中包含了兩個(gè)生命周期方法init()
和destory()
雳攘,還有一個(gè)處理請求的service()
带兜。
1. Servlet 的生命周期
Servlet是運(yùn)行在Servlet容器中的,由Servlet容器來負(fù)責(zé)Servlet實(shí)例的查找吨灭、創(chuàng)建以及整個(gè)生命周期的管理刚照。Servlet整個(gè)生命周期可以分為四個(gè)階段:
-
(1) 類裝載以及實(shí)例創(chuàng)建階段
默認(rèn)情況下,Servlet實(shí)例是在接收到第一個(gè)請求時(shí)進(jìn)行創(chuàng)建喧兄,并且在以后的請求中對這個(gè)實(shí)例進(jìn)行復(fù)用涩咖。如果有Servlet實(shí)例需要進(jìn)行一些復(fù)雜的操作(如打開文件,初始化網(wǎng)絡(luò)連接等)繁莹,可以配置在服務(wù)器啟動(dòng)時(shí)就創(chuàng)建實(shí)例檩互。具體配置方法為:在web.xml的聲明Servlet標(biāo)簽中添加
<load-on-startup>1</load-on-start-up>
標(biāo)簽。 -
(2) 實(shí)例初始化階段
一旦Servlet實(shí)例被創(chuàng)建咨演,將會調(diào)用Servlet中的
init(ServletConfig arg)
方法闸昨,傳入ServletConfig,即Servlet的相關(guān)配置信息薄风,init()方法在整個(gè)Servlet的生命周期中只會被調(diào)用一次饵较。 -
(3) 服務(wù)階段
實(shí)例初始化后,一旦由客戶端請求遭赂,Servlet 就會調(diào)用
service(ServletRequest req, ServletRespose res)
方法處理數(shù)據(jù)并響應(yīng)數(shù)據(jù)循诉。 -
(4) 實(shí)例銷毀階段
當(dāng)Servlet容器決定銷毀某個(gè)Servlet時(shí),將會調(diào)用 Servlet 實(shí)例中的
destory()
方法撇他,在destory()方法中進(jìn)行資源釋放茄猫。一旦Servlet實(shí)例的 destory() 方法被調(diào)用狈蚤,Servlet 容器將不會發(fā)任何請求給這個(gè)Servlet實(shí)例,若 Servlet 容器需要再次使用這個(gè) Servlet划纽,需要重新實(shí)例化該 Servlet 實(shí)例脆侮。
需要注意的是:
在每一個(gè)應(yīng)用中,每個(gè) Servlet 只能擁有一個(gè)實(shí)例勇劣。
對于每一個(gè)Servlet實(shí)例靖避,也有一個(gè)封裝了對應(yīng)配置的ServletConfig對象。
對于每一個(gè)應(yīng)用程序比默,Servlet容器還會創(chuàng)建一個(gè)ServletContext對象幻捏,這個(gè)對象中封裝了應(yīng)用環(huán)境(上下文)的數(shù)據(jù)詳情,每個(gè)應(yīng)用程序也只有一個(gè)ServletContext命咐。
2. Servlet 的工作流程
Servlet 是一個(gè)規(guī)范篡九,但是我們實(shí)現(xiàn)完 Servlet 后并不能直接處理請求,Servlet 并不會直接與客戶端打交道侈百。
那請求怎么來到 Servlet 的呢瓮下?答案是 Servlet 容器。比如我們最常用的Tomcat钝域,我們實(shí)現(xiàn)的Servlet最終都要部署到Tomcat中讽坏,否則壓根就不起作用。下面我們簡單了解一下 Servlet部署到容器后的工作流程:
- Web服務(wù)器接收 http 請求后將請求移交給 Servlet容器例证;
- Servlet 容器對請求的 URL 進(jìn)行解析并根據(jù)web.xml配置文件找到處理該應(yīng)請求的Servlet路呜,同時(shí)將 request、response對象一并傳遞給該類织咧;
- Servlet 根據(jù) request 對象可以得到客戶端發(fā)過來的數(shù)據(jù)并做出對應(yīng)的處理胀葱,之后將需要返回的信息放入 response 對象中并返回到客戶端;
- Servlet 一旦處理完請求笙蒙,Servlet 容器就會刷新 response 對象抵屿,并把控制權(quán)重新移交回給 web服務(wù)器。
所以 Tomcat 才是與客戶端直接打交道的家伙捅位,他監(jiān)聽了端口轧葛,請求過來后,根據(jù)url等信息艇搀,確定要將請求交給哪個(gè)servlet去處理尿扯,然后調(diào)用那個(gè)Servlet 的 service() 方法,service()方法返回一個(gè) response對象焰雕,tomcat再把這個(gè) response返回給客戶端衷笋。
三、如何編寫一個(gè)Servlet類
我們常見的實(shí)現(xiàn) Servlet 的方式有以下三種
- 實(shí)現(xiàn) javax.servlet.Servlet 接口矩屁;
- 繼承 javax.servlet.GenericServlet 類辟宗;
- 繼承 javax.servlet.http.HttpServlet 類爵赵;(一般使用這種方式,方便B选)
1. 實(shí)現(xiàn)Servlet接口
代碼演示:
public class AServlet implements Servlet {
@override
public void init(ServletConfig config) throws ServletException {
System.out.println("init()...")
}
@override
public void service(ServletRequest req, ServletRespose res) throws ServletException,IOException {
System.out.println("service()...")
}
@override
public void destory() {
System.out.println("destory()...")
}
@override
public ServletConfig getServletConfig() {
return null;
}
@override
public String getServletInfo() {
return "";
}
}
編寫完代碼后需要在web.xml中正確配置映射關(guān)系亚再。之后啟動(dòng)服務(wù)器郭膛,再多次訪問Servlet晨抡,console中輸出以下信息:
接下來關(guān)閉Servlet容器,控制臺打釉蛱辍:
注:Servlet中的方法實(shí)現(xiàn)后并不由我們自己來調(diào)用耘柱,而是由服務(wù)器來調(diào)用并做出反應(yīng)。
2. 繼承 GenericServlet/HttpServlet 類
我們知道瀏覽器最基本的請求有兩種棍现,Get/Post调煎。如果我們采用上面所述直接實(shí)現(xiàn)接口的方式,那么我們必須這么寫:
public void service(Requst request, Response response) {
String method = request.getMethod();
if (METHOD_GET.equals(method)) { // 如果是Get請求
// 調(diào)用 service 層實(shí)現(xiàn)業(yè)務(wù)邏輯...
} else if (METHOD_POST.equals(method)) { // 如果是Post請求
// 調(diào)用 service 層實(shí)現(xiàn)業(yè)務(wù)邏輯...
} else if (...) { // 其他類型的請求
}
}
上面只是簡單的一個(gè)小例子己肮,但是我們可以從中看出:直接實(shí)現(xiàn) Servlet 接口的效率并不高士袄。所以我們可以來看看有沒有辦法來簡化我們這些操作。
2.1 GenericServlet抽象類
首先我們找到一個(gè) GenericServlet 的抽象類谎僻,該類的源代碼如下:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable{
private static final long = 1L;
privare transient ServletConfig config;
public GenericServlet() { }
@override
public void destory() {}
@override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
@override
public ServletConfig getServletContext(){
return config;
}
@override
public ServletContext getServletContext(){
return getServletConfig().getServletContext();
}
@override
public String getServletInfo(){
return "";
}
@override
public void init(ServletConfig config) throws ServletException {
this.config = config;
init();
}
public void init() throws ServletException { }
public void log(String msg) {
getServletContext().log(getServletName() + ":" + message, t);
}
@override
public abstract void service(ServletRequest req, ServletResponse res) throws ServletExcrption, IOException;
@override
public String getServletName() {
return config.getServletName();
}
}
從上面代碼我們可以發(fā)現(xiàn) GenericServlet 做了如下改良:
- 提升了 init() 方法中原本是形參的 servletConfig 對象的作用域(成員變量)娄柳,方便其他方法進(jìn)行使用
- 定義了 init() 空參方法,繼承該類的子類可以覆蓋該方法艘绍,方便在創(chuàng)建 Servlet 時(shí)進(jìn)行一些初始化操作
- 還實(shí)現(xiàn)了
ServletConfig
接口赤拒,可以直接調(diào)用getInitParameter()
、getServletContext()
等接種定義的方法诱鞠。
但是我們也發(fā)現(xiàn)改抽象類并沒有實(shí)現(xiàn) service() 方法挎挖,也就說該類所定義的方法并不滿足我們剛所提到的簡化。
2.2 HttpServlet 抽象類
HttpServlet繼承了
public abstract class HttpServlet extends GenericServlet {
// ...其他代碼
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
// ... 請求 doGet()方法
} else if (method.equals("HEAD")) {
// ... 請求 doHead() 方法
} else if (method.equals("POST")) {
// ... 請求 doPost() 方法
} else if (method.equals("PUT")) {
...
}
// ...其他代碼
}
從上面這段代碼我們可以看到航夺,HttpServlet 抽象類中已經(jīng)幫我們實(shí)現(xiàn)了復(fù)雜的請求判斷蕉朵。所以我們在實(shí)現(xiàn)一個(gè)Servlet時(shí),可以直接繼承HttpServlet抽象類以便更加快捷地完成我們的業(yè)務(wù)邏輯(雖然現(xiàn)在應(yīng)該沒人直接把業(yè)務(wù)寫Servlet里啦...)阳掐。
四始衅、Servlet接口相關(guān)類型
在Servlet接口中還存在三個(gè)我們不熟悉的類型:
-
ServletConfig
:init() 方法中的參數(shù),它vlet配置對象锚烦,它對應(yīng)Servlet的配置信息觅闽,對應(yīng) web.xml 文件中的<servlet>元素。 -
ServletRequest
:service() 方法的參數(shù)涮俄,他表示請求對象蛉拙,它封裝了所有與請求相關(guān)的數(shù)據(jù),它是由服務(wù)器創(chuàng)建的彻亲。 -
ServletResponse
:service() 方法的參數(shù)孕锄,它表示響應(yīng)對象吮廉,在Servlet()方法中完成對客戶端的響應(yīng)需要這個(gè)對象。
1. ServletRequest 和 ServletResponse
ServletRequest 和 ServletResponse 是 service() 方法中的兩個(gè)參數(shù)畸肆,一個(gè)請求對象宦芦,一個(gè)響應(yīng)對象,可以從ServletRequest對象中獲取請求數(shù)據(jù)轴脐,可以使用ServletResponse對象完成響應(yīng)调卑。
ServletRequest和ServletResponse的實(shí)例由服務(wù)器創(chuàng)建,然后傳遞給
service()方法大咱。如果在service()方法中希望使用HTTP相關(guān)功能恬涧,那么可以把ServletRequest和ServletResponse強(qiáng)轉(zhuǎn)為HttpServletRequest和HttpServletResponse。這也說明我們經(jīng)常需要在service()方法中對ServletRequest和ServletResponse進(jìn)行強(qiáng)轉(zhuǎn)碴巾,不過上面提到的HttpServlet已經(jīng)解決了這個(gè)問題溯捆。
2. ServletConfig
ServleteConfig對象對應(yīng)web.xml文件中的<servlet>元素。例如你想獲取當(dāng)前Servlet對象在web.xml文件中的配置名厦瓢,那么可以使用ServletConfig.getServletName()方法獲忍嶙帷!
ServletConfig對象是由服務(wù)器創(chuàng)建的煮仇,然后傳遞給Servlet的init()方法劳跃,可以在init()方法中使用它!
-String getServletName():獲取Servlet在web.xml文件中的配置名稱欺抗,即<servlet-name>指定的名稱售碳;
-ServletContext getServletContext():用來獲取ServletContext對象。
-String getInitParameter(String name):用來獲取在web.xml中配置的初始化參數(shù)绞呈,通過參數(shù)名來獲取參數(shù)值贸人;
Enumeration getInitParameter(String name):用來獲取在web.xml中配置的所有初始化參數(shù)名稱。
五佃声、ServletContext
一個(gè)項(xiàng)目中只有一個(gè)ServletContext對象艺智!
1. ServletContext概述
服務(wù)器會為每個(gè)應(yīng)用創(chuàng)建一個(gè)ServletContext對象:
- ServletContext對象的創(chuàng)建是在服務(wù)器啟動(dòng)時(shí)完成的;
- ServletContext對象的銷毀是在服務(wù)器關(guān)閉時(shí)完成的圾亏。
-
ServletContext對象的作用是在整個(gè)Web應(yīng)用的動(dòng)態(tài)資源之間共享數(shù)據(jù)十拣!
例如在AServlet中向ServletContext對象中保存了一個(gè)值,然后在BServlet中就可以獲得這個(gè)值志鹃,這就是共享數(shù)據(jù)了夭问。
2. 獲取ServletContext
Servletconfig#getServletContext();
GenericServlet#getServletContext();
HttpSession#getServletContext();
ServletContextEvent#getServletContext().
public class MyServlet implement Servlet {
public void init(ServletConfig config) {
ServletContext context = config.getServletContext();
}
......
}
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) {
ServletContext context = this.getServletContext();
}
}
3. 域?qū)ο蟮墓δ?/h3>
ServletContext是javaWeb四大域?qū)ο笾唬?/p>
- PageContext;
- ServletRequest曹铃;
- HttpSession缰趋;
- ServletContext。
所有域?qū)ο蠖加写鎯?shù)據(jù)的功能,因?yàn)橛驅(qū)ο髢?nèi)部有一個(gè)Map秘血,用來存儲數(shù)據(jù)味抖,下面是ServletConext用來操作數(shù)據(jù)的方法:
-void setAttribute(String name, Object value):用來存儲一個(gè)對象,也可以稱之為存儲一個(gè)域?qū)傩曰伊福纾簊ervletContext.setAttribute("xxx", XXX),在ServletContext中保存了一個(gè)域?qū)傩宰猩驅(qū)傩悦Q為xxx,域?qū)傩缘闹禐閄XX粘舟。注:若多次調(diào)用該方法且使用相同的name熔脂,那么會覆蓋上一次的值,這一特性與Map相同蓖乘。
-Object getAttribute(String name):用來獲取ServletContext中的數(shù)據(jù)锤悄,但被獲取的數(shù)據(jù)需被存儲過才行韧骗。
-void removeAttribute(String name):用來移除ServletContext中的域?qū)傩约问悖绻麉?shù)name指定的域?qū)傩圆淮嬖冢敲幢痉椒ㄊ裁炊疾蛔觯?/p>
-Enumeration getAttributeNames():獲取所有域?qū)傩缘拿Q;