Servlet的本質(zhì)與工作原理

一、Servlet 概述

Servlet 類是 JavaWeb 的三大組件之一逃默,它屬于動(dòng)態(tài)資源鹃愤。其作用是處理請求,服務(wù)器通常會把接收到的請求交由 Servlet 的實(shí)現(xiàn)類來處理笑旺,通常Servlet的實(shí)現(xiàn)類需要完成:

  1. 接收請求數(shù)據(jù)昼浦;
  2. 處理請求;
  3. 完成響應(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部署到容器后的工作流程:

  1. Web服務(wù)器接收 http 請求后將請求移交給 Servlet容器例证;
  2. Servlet 容器對請求的 URL 進(jìn)行解析并根據(jù)web.xml配置文件找到處理該應(yīng)請求的Servlet路呜,同時(shí)將 request、response對象一并傳遞給該類织咧;
  3. Servlet 根據(jù) request 對象可以得到客戶端發(fā)過來的數(shù)據(jù)并做出對應(yīng)的處理胀葱,之后將需要返回的信息放入 response 對象中并返回到客戶端;
  4. 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 的方式有以下三種

  1. 實(shí)現(xiàn) javax.servlet.Servlet 接口矩屁;
  2. 繼承 javax.servlet.GenericServlet 類辟宗;
  3. 繼承 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>

  1. PageContext;
  2. ServletRequest曹铃;
  3. HttpSession缰趋;
  4. 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;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末袍暴,一起剝皮案震驚了整個(gè)濱河市些侍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌政模,老刑警劉巖岗宣,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異淋样,居然都是意外死亡耗式,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門趁猴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刊咳,“玉大人,你說我怎么就攤上這事儡司∮榘ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵捕犬,是天一觀的道長跷坝。 經(jīng)常有香客問我,道長碉碉,這世上最難降的妖魔是什么柴钻? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮垢粮,結(jié)果婚禮上贴届,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好粱腻,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布庇配。 她就那樣靜靜地躺著,像睡著了一般绍些。 火紅的嫁衣襯著肌膚如雪捞慌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天柬批,我揣著相機(jī)與錄音啸澡,去河邊找鬼。 笑死氮帐,一個(gè)胖子當(dāng)著我的面吹牛嗅虏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播上沐,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼皮服,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了参咙?” 一聲冷哼從身側(cè)響起龄广,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蕴侧,沒想到半個(gè)月后择同,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡净宵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年敲才,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片择葡。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡紧武,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刁岸,到底是詐尸還是另有隱情脏里,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布虹曙,位于F島的核電站迫横,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏酝碳。R本人自食惡果不足惜矾踱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疏哗。 院中可真熱鬧呛讲,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雷逆,卻和暖如春弦讽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膀哲。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工往产, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人某宪。 一個(gè)月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓仿村,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兴喂。 傳聞我的和親對象是個(gè)殘疾皇子蔼囊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前言 這篇文章的出發(fā)點(diǎn)是為了整理Servlet相關(guān)知識點(diǎn),以免在相關(guān)概念混淆或分不清的時(shí)候到處查閱資料瞻想。 一压真、什么...
    maxwellyue閱讀 3,443評論 2 35
  • 本文包括: Servlet簡介關(guān)于Servlet的一些類 Servlet生命周期 ServletConfig獲得初...
    廖少少閱讀 3,843評論 1 67
  • 一贵少、Http協(xié)議 HTTP協(xié)議:超文本傳輸協(xié)議(HTTP呵俏,HyperText Transfer Protocol)...
    圣賢與無賴閱讀 1,219評論 0 13
  • Servlet、GenericServlet滔灶、HttpServlet Servlet 對于web容易來說普碎,所有se...
    六尺帳篷閱讀 1,357評論 0 4
  • 記得很小的時(shí)候,大概小學(xué)吧录平,父親在鄰村有一個(gè)很好的朋友麻车,許是結(jié)拜過吧,畢竟他們那個(gè)年代農(nóng)村里興結(jié)拜斗这。過年時(shí)...
    離群孤雁閱讀 128評論 0 3