Servlet運行原理


一:servlet定義

Servlet是一個Java應用程序,運行在服務器端,用來處理客戶端請求並作出響應的程序。

Servlet多線程體系結構是建立在Java多線程機制之上的,它的生命周期是由Web容器負責的。

      當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置文件實例化這個Servlet類,此時它貯存於內存中。。當有新的客戶端請求該Servlet時,一般不會再實例化該Servlet類,也就是有多個線程在使用這個實例。 這樣,當兩個或多個線程同時訪問同一個Servlet時,可能會發生多個線程同時訪問同一資源的情況,數據可能會變得不一致。所以在用Servlet構建的Web應用時要注意線程安全的問題。每一個請求都是一個線程,而不是進程,因此,Servlet對請求的處理的性能非常高。

        對於Servlet,它被設計為多線程的(如果它是單線程的,你就可以想象,當1000個人同時請求一個網頁時,在第一個人獲得請求結果之前,其它999個人都在郁悶地等待),如果為每個用戶的每一次請求都創建 一個新的線程對象來運行的話,系統就會在創建線程和銷毀線程上耗費很大的開銷,大大降低系統的效率。

因此,Servlet多線程機制背后有一個線程池在支持,線程池在初始化初期就創建了一定數量的線程對象,通過提高對這些對象的利用率,避免高頻率地創建對象,從而達到提高程序的效率的目的。(由線程來執行Servlet的service方法,servlet在Tomcat中是以單例模式存在的, Servlet的線程安全問題只有在大量的並發訪問時才會顯現出來,並且很難發現,因此在編寫Servlet程序時要特別注意。線程安全問題主要是由實例變量造成的,因此在Servlet中應避免使用實例變量。如果應用程設計無法避免使用實例變量,那么使用同步來保護要使用的實例變量,但為保證系統的最佳性能,應該同步可用性最小的代碼路徑)

 Struts2的Action是原型,非單實例的;會對每一個請求,產生一個Action的實例來處理。

          解決servlet線程安全的方案:同步對共享數據的操作 Synchronized (this){...}、避免使用實例變量

①客戶端向服務器端發出請求;

②這個過程比較重要,這時Tomcat會創建兩個對象:HttpServletResponse和HttpServletRequest。並將它們的引用(注意是引用)傳給剛分配的線程;

③線程開始着手接洽servlet;

④servlet根據傳來的是GET和POST,分別調用doGet()和doPost()方法進行處理;

⑤和⑥servlet將處理后的結果通過線程傳回Tomcat,並在之后將這個線程銷毀或者送還線程池;

⑦Tomcat將處理后的結果變成一個HTTP響應發送回客戶端,這樣,客戶端就可以接受到處理后的結果了。

二:簡單servlet實例 

//導入所需的包

 

import javax.servlet.http.*;

import javax.servlet.*;

import java.io.*;

public class FirstServlet extends HttpServlet {

//處理請求的方法

public void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, java.io.IOException {

//數據發送給客戶端—>控制台方式輸出

//System.out.println(“Hello Servlet”);

//數據發送給客戶端—>HTML頁面輸出

resp.setContentType(“text/html”);

resp.getWriter().print(“<html>”);

resp.getWriter().print(“<head>”);

resp.getWriter().print(“</head>”);

resp.getWriter().print(“<body>”);

resp.getWriter().print(“Hello World”);

resp.getWriter().print(“</body>”);

resp.getWriter().print(“</html>”);

}

}

三:servlet配置到Tomcat中去

Tomcat是一個web容器,也叫web服務器。我們都知道J2EE有十三個標准,這些標准大部分都是接口,Tomcat只是實現了JSP 和 servlet 開發標准。實現了所有的開發標准,就是應用程序服務器,比如Jboss。

編譯好的servlet類只能運行在tomcat容器中,客戶端瀏覽器不可以直接訪問Servlet,需要在web.xml中配置一下 

<servlet>

 

<servlet-name>FirstServlet</servlet-name>

<servlet-class>FirstServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>FirstServlet</servlet-name>

<url-pattern>/servlet/FirstServlet</url-pattern>

</servlet-mapping>

 Tomcat啟動成功后,在瀏覽器中輸入:http://localhost:8080/abingtest/servlet/FirstServlet

四:servlet運行原理

當我們在瀏覽器中輸入http://localhost:8080/abingtest/servlet/FirstServlet的時候,Tomcat是如何找到我們的servlet ,運行,並返回我們想看到的頁面的呢。

1. WebApplication的標准目錄結構:

WEB-INF/classes

/lib

Web.xml

也就是一個完整的web應用程序目錄下,必須包含以上的目錄結構。

Classes 文件夾下是項目中用到的類文件,均由JDK編譯成了.class文件

Lib文件夾是我們項目中引用的jar包

Web.xml是整個web應用程序的配置文檔。

了解了這些,我們再來看Tomcat的工作流程。

2. Tomcat解析URL

a) 首先來看URL中包含的信息:”協議” + “端口號” + “路徑(項目名稱+文件路徑)”

Tomcat啟動后,監聽我們的8080端口,當有Url請求發過來之后,解析出項目名稱 abingtest,然后到webapps目錄下搜索到該項目文件夾。

b) 項目文件找到后,開始尋找類文件。

這個時候Tomcat去Web.xml文件中尋找<servlet-mapping> 配置節中包含”servlet/FirstServlet”字符串,進而找到該類文件所在的位置。 

3. Servelt中的doGet() 和 doPost() 方法

我們寫的FirstServlet 繼承了HttpServlet ,重寫了HttpServlet中的doGet() 方法,HttpServlet中還有一個doPost()方法。這兩個方法都是用來處理Http請求的。Servlet會根據我們提交表單的方法(method=post/get)調用service方法來自動選擇(我們在下一篇文章中來詳細說明一下Service方法是如何自動調用FirstServlet中的doGet()方法的)

4. Servlet如何接收數據

import javax.servlet.http.*;

 

import javax.servlet.*;

import java.io.*;

public class FirstServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, java.io.IOException {

//獲取表單數據

String userName = request.getParameter(“userName”);

}

}

 Http協議會將網頁中的所有內容包裝成為一個request對象傳遞給servlet ,Servlet通過這個對象拿到表單中的所有數據,處理完成之后,通過Response對象返回給客戶端瀏覽器。

5. Servlet的生命周期

Servlet的生命周期是由Tomcat容器管理的

a) 客戶發出請求—>Web 服務器轉發到Web容器Tomcat;

b) Tomcat主線程對轉發來用戶的請求做出響應創建兩個對象:HttpServletRequest和HttpServletResponse;

c) 從請求中的URL中找到正確Servlet,Tomcat為其創建或者分配一個線程,同時把2創建的兩個對象傳遞給該線程;

d) Tomcat調用Servlet的servic()方法,根據請求參數的不同調用doGet()或者doPost()方法;

e) 假設是HTTP GET請求,doGet()方法生成靜態頁面,並組合到響應對象里;
Servlet線程結束,Tomcat將響應對象轉換為HTTP響應發回給客戶,同時刪除請求和響應對象。
從該過程中,我們可以理解Servlet的生命周期:Servlet類加載(對應3步);Servlet實例化(對應3步);調用init方法(對應3步);調用service()方法(對應4、5步);;調用destroy()方法(對應6步)。

五:Servlet生命周期的各個階段

Servlet運行在Servlet容器中,其生命周期由容器來管理。Servlet的生命周期通過javax.servlet.Servlet接口中的init()、service()和destroy()方法來表示。

Servlet的生命周期包含了下面4個階段: 

(1)加載和實例化

Servlet容器負責加載和實例化Servlet。當Servlet容器啟動時,或者在容器檢測到需要這個Servlet來響應第一個請求時,創建Servlet實例。當Servlet容器啟動后,它必須要知道所需的Servlet類在什么位置,Servlet容器可以從本地文件系統、遠程文件系統或者其他的網絡服務中通過類加載器加載Servlet類,成功加載后,容器創建Servlet的實例。因為容器是通過Java的反射API來創建Servlet實例,調用的是Servlet的默認構造方法(即不帶參數的構造方法),所以我們在編寫Servlet類的時候,不應該提供帶參數的構造方法。

(2)初始化

在Servlet實例化之后,容器將調用Servlet的init()方法初始化這個對象。初始化的目的是為了讓Servlet對象在處理客戶端請求前完成一些初始化的工作,如建立數據庫的連接,獲取配置信息等。對於每一個Servlet實例,init()方法只被調用一次。在初始化期間,Servlet實例可以使用容器為它准備的ServletConfig對象從Web應用程序的配置信息(在web.xml中配置)中獲取初始化的參數信息。在初始化期間,如果發生錯誤,Servlet實例可以拋出ServletException異常或者UnavailableException異常來通知容器。ServletException異常用於指明一般的初始化失敗,例如沒有找到初始化參數;而UnavailableException異常用於通知容器該Servlet實例不可用。例如,數據庫服務器沒有啟動,數據庫連接無法建立,Servlet就可以拋出UnavailableException異常向容器指出它暫時或永久不可用。

(3)請求處理

Servlet容器調用Servlet的service()方法對請求進行處理。要注意的是,在service()方法調用之前,init()方法必須成功執行。在service()方法中,Servlet實例通過ServletRequest對象得到客戶端的相關信息和請求信息,在對請求進行處理后,調用ServletResponse對象的方法設置響應信息。在service()方法執行期間,如果發生錯誤,Servlet實例可以拋出ServletException異常或者UnavailableException異常。如果UnavailableException異常指示了該實例永久不可用,Servlet容器將調用實例的destroy()方法,釋放該實例。此后對該實例的任何請求,都將收到容器發送的HTTP 404(請求的資源不可用)響應。如果UnavailableException異常指示了該實例暫時不可用,那么在暫時不可用的時間段內,對該實例的任何請求,都將收到容器發送的HTTP 503(服務器暫時忙,不能處理請求)響應。

(4)服務終止

當容器檢測到一個Servlet實例應該從服務中被移除的時候,容器就會調用實例的destroy()方法,以便讓該實例可以釋放它所使用的資源,保存數據到持久存儲設備中。當需要釋放內存或者容器關閉時,容器就會調用Servlet實例的destroy()方法。在destroy()方法調用之后,容器會釋放這個Servlet實例,該實例隨后會被Java的垃圾收集器所回收。如果再次需要這個Servlet處理請求,Servlet容器會創建一個新的Servlet實例。

在整個Servlet的生命周期過程中,創建Servlet實例、調用實例的init()和destroy()方法都只進行一次,當初始化完成后,Servlet容器會將該實例保存在內存中,通過調用它的service()方法,為接收到的請求服務。下面給出Servlet整個生命周期過程的UML序列圖 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM